From c2080924526d955932fbd152e8e45104525bd32b Mon Sep 17 00:00:00 2001 From: 7u83 <7u83@maiil.ru> Date: Wed, 26 Dec 2018 13:11:35 +0100 Subject: [PATCH] Work on separated trading engine. --- src/opensesim/gui/SeSimApplication.java | 10 +- .../gui/orderbook/OrderBookPanel.java | 4 +- src/opensesim/trader/SimpleTrader.java | 4 +- src/opensesim/world/AbstractAsset.java | 54 ++-- src/opensesim/world/Exchange.java | 93 ++----- src/opensesim/world/GodWorld.java | 9 +- src/opensesim/world/Order.java | 37 ++- src/opensesim/world/Quote.java | 71 +++++ src/opensesim/world/TradingAPI.java | 4 +- src/opensesim/world/TradingEngine.java | 258 ++++++++++++++++++ 10 files changed, 431 insertions(+), 113 deletions(-) create mode 100644 src/opensesim/world/Quote.java create mode 100644 src/opensesim/world/TradingEngine.java diff --git a/src/opensesim/gui/SeSimApplication.java b/src/opensesim/gui/SeSimApplication.java index 179f1be..ce65c1c 100644 --- a/src/opensesim/gui/SeSimApplication.java +++ b/src/opensesim/gui/SeSimApplication.java @@ -613,15 +613,7 @@ public class SeSimApplication extends javax.swing.JFrame { opensesim.world.Exchange ex = godworld.getDefaultExchange(); TradingAPI api = ex.getAPI(p); - Set ob; - - ob = api.getOrderBook(Order.Type.BUY); - - for (Order o : ob) { - double v = o.getVolume(); - System.out.printf("Volume: %f\n", o.getVolume()); - } - + opensesim.world.scheduler.Scheduler s = godworld.getScheduler(); class MyListener implements EventListener { diff --git a/src/opensesim/gui/orderbook/OrderBookPanel.java b/src/opensesim/gui/orderbook/OrderBookPanel.java index 09e016d..c543cd5 100644 --- a/src/opensesim/gui/orderbook/OrderBookPanel.java +++ b/src/opensesim/gui/orderbook/OrderBookPanel.java @@ -212,8 +212,8 @@ public class OrderBookPanel extends javax.swing.JPanel implements EventListener void oupdater() { // ArrayList ob = Globals.se.getOrderBook(type, depth); - Collection ob = api.getOrderBook(Order.Type.BUY); - + Collection ob = api.getBidBook(); + model.setRowCount(ob.size()); int row = 0; for (Order ob1 : ob) { diff --git a/src/opensesim/trader/SimpleTrader.java b/src/opensesim/trader/SimpleTrader.java index 49be553..6efc96f 100644 --- a/src/opensesim/trader/SimpleTrader.java +++ b/src/opensesim/trader/SimpleTrader.java @@ -118,7 +118,7 @@ public class SimpleTrader extends AbstractTrader implements EventListener { long last_time = 0; - double limit = 100; + double limit = 253.871239; @Override public long receive(Event task) { // System.out.printf("Here we are !!! %f\n", getWorld().randNextFloat(12f, 27f)); @@ -133,7 +133,7 @@ public class SimpleTrader extends AbstractTrader implements EventListener { ex = getWorld().getDefaultExchange(); api = ex.getAPI(p); - Order o = api.createOrder(account, Order.Type.BUY, 100, limit); + Order o = api.createOrder(account, Order.Type.BUY, 112.987123, limit); limit += 12; return -1; diff --git a/src/opensesim/world/AbstractAsset.java b/src/opensesim/world/AbstractAsset.java index 2fe1da2..02f18a6 100644 --- a/src/opensesim/world/AbstractAsset.java +++ b/src/opensesim/world/AbstractAsset.java @@ -25,6 +25,7 @@ */ package opensesim.world; +import java.text.DecimalFormat; import javax.swing.JPanel; import opensesim.sesim.interfaces.GetJson; import org.json.JSONObject; @@ -40,7 +41,9 @@ public abstract class AbstractAsset implements GetJson { private String symbol; private String name; private String description; - int decimals; + private int decimals; + private double decimals_df; + private DecimalFormat formatter; /** * Constructor @@ -54,25 +57,40 @@ public abstract class AbstractAsset implements GetJson { } symbol = cfg.getString("symbol"); name = cfg.getString("name"); - decimals = cfg.optInt("decimals", 0); + setDecimals(cfg.optInt("decimals", 0)); this.world = world; } - - - public AbstractAsset(){ - + + public AbstractAsset() { + } - public abstract String getTypeName(); + public double roundToDecimals(double val) { + return Math.floor(val * decimals_df) / decimals_df; + } + + protected void setDecimals(int n) { + decimals = n; + decimals_df = Math.pow(10, n); + + // create formatter string + String fs = "#"; + if (n > 0) { + fs = fs + "0."; + for (int i = 0; i < n; i++) { + fs = fs + "0"; + } + } + // create fromatter from string + formatter = new DecimalFormat(fs); + } public final int getDecimals() { return decimals; } - - protected final void setDecimals(int decimals){ - this.decimals=decimals; - } + + public abstract String getTypeName(); protected void setDescription(String description) { this.description = description; @@ -81,17 +99,17 @@ public abstract class AbstractAsset implements GetJson { public final String getSymbol() { return symbol; } - - protected final void setSymbol(String symbol){ - this.symbol=symbol; + + protected final void setSymbol(String symbol) { + this.symbol = symbol; } public final String getName() { return name; } - - protected final void setName(String name){ - this.name=name; + + protected final void setName(String name) { + this.name = name; } public String getDescription() { @@ -114,7 +132,6 @@ public abstract class AbstractAsset implements GetJson { public static final String JSON_DECIMALS = "decimals"; public static final int DECIMALS_DEFAULT = 2; - @Override public JSONObject getJson() { JSONObject cfg = new JSONObject(); @@ -128,7 +145,6 @@ public abstract class AbstractAsset implements GetJson { return cfg; } - public JPanel getEditGui() { return null; } diff --git a/src/opensesim/world/Exchange.java b/src/opensesim/world/Exchange.java index 4acd740..88e4c2c 100644 --- a/src/opensesim/world/Exchange.java +++ b/src/opensesim/world/Exchange.java @@ -25,16 +25,15 @@ */ package opensesim.world; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import opensesim.world.RealWorld; import opensesim.sesim.interfaces.Configurable; import opensesim.sesim.interfaces.GetJson; +import opensesim.util.idgenerator.IDGenerator; import opensesim.world.scheduler.FiringEvent; import opensesim.world.scheduler.EventListener; import org.json.JSONObject; @@ -86,7 +85,7 @@ public class Exchange implements Configurable, GetJson { public Order createOrder(Account account, AssetPair pair, Order.Type type, double volume, double limit) { -// Order o = new Order(world,account,pair,type,volume,limit); +// Order o = new Order(world,account,assetpair,type,volume,limit); return null; } @@ -112,77 +111,27 @@ public class Exchange implements Configurable, GetJson { return cfg; } - class TradingEnv implements TradingAPI { - - protected HashMap> order_books; - AssetPair pair; - - TradingEnv(AssetPair p) { - pair = p; - reset(); - } - - protected final void reset() { - order_books = new HashMap(); - - // Create an order book for each order type - for (Order.Type type : Order.Type.values()) { - order_books.put(type, new TreeSet<>()); - } - - // quoteHistory = new TreeSet(); - // ohlc_data = new HashMap(); - } - - protected void addOrderToBook(Order o) { - order_books.get(o.type).add(o); - switch (o.type) { - case BUY: - case BUYLIMIT: - break; - case SELL: - case SELLLIMIT: - break; - - } - - } - - - - - @Override - public Order createOrder(Account account, Order.Type type, double volume, double limit) { - - - Order o = new opensesim.world.Order(world, account, pair, type, volume, limit); - synchronized (this){ - order_books.get(o.type).add(o); - } - for (FiringEvent e:book_listener){ - e.fire(); - } - - return o; - } - - @Override - public Set getOrderBook(Order.Type type) { - return Collections.unmodifiableSet(order_books.get(type)); - } - - HashSet book_listener = new HashSet<>(); - @Override - public void addOrderBookListener(EventListener listener) { - book_listener.add(new FiringEvent(listener)); - } - - - - } + + + + + + + + + + + + + + + + + + private TradingAPI add(AssetPair pair) { - TradingEnv e = new TradingEnv(pair); + TradingEngine e = new TradingEngine(pair, this); asset_pairs.put(pair, e); return e; } diff --git a/src/opensesim/world/GodWorld.java b/src/opensesim/world/GodWorld.java index ef108cd..3a133bb 100644 --- a/src/opensesim/world/GodWorld.java +++ b/src/opensesim/world/GodWorld.java @@ -32,12 +32,14 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Random; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import opensesim.sesim.interfaces.GetJson; import opensesim.util.SeSimException; import opensesim.util.idgenerator.IDGenerator; +import opensesim.world.TradingEngine; import opensesim.world.scheduler.Event; import opensesim.world.scheduler.EventListener; @@ -76,7 +78,7 @@ public class GodWorld implements GetJson, World { /* HashSet assetsById = new HashSet<>(); HashMap assetsBySymbol = new HashMap<>(); */ - IDGenerator orderIdGenerator = new IDGenerator(); + //IDGenerator orderIdGenerator = new IDGenerator(); private Scheduler scheduler = new Scheduler(); @@ -437,5 +439,10 @@ public class GodWorld implements GetJson, World { l.receive(e); } } + + + public Set getOrderBook(Exchange ex,AssetPair pair,Order.Type type){ + return ((TradingEngine)ex.getAPI(pair)).getOrderBook(type); + } } diff --git a/src/opensesim/world/Order.java b/src/opensesim/world/Order.java index bd523a8..65d27a4 100644 --- a/src/opensesim/world/Order.java +++ b/src/opensesim/world/Order.java @@ -91,21 +91,45 @@ public class Order implements Comparable { double cost; GodWorld world; - Order(GodWorld world, Account account, AssetPair pair, Type type, + Order(opensesim.world.TradingEngine engine, Account account, Type type, double volume, double limit) { + AssetPair pair = engine.getAssetPair(); + + // round asset and currency + double v, l; + switch (type) { + case BUY: + l = pair.getCurrency().roundToDecimals(limit); + v = pair.getAsset().roundToDecimals(volume); + break; + case SELL: + l = pair.getCurrency().roundToDecimals(limit); + v = pair.getAsset().roundToDecimals(volume); + break; + + default: + l = limit; + v = volume; + + } + + // assign rounded volume and limit + this.volume = v; + this.initial_volume = v; + this.limit = l; + this.account = account; this.type = type; - this.limit = limit; - this.volume = volume; - this.initial_volume = this.volume; + this.created = 0; this.status = Status.OPEN; this.cost = 0; // id = Order.idGenerator.getNext(); - this.world = world; + // this.world = world; // id = world. - id = world.orderIdGenerator.getNext(); + // id = world.orderIdGenerator.getNext(); + id = engine.id_generator.getNext(); } public Id getID() { @@ -156,5 +180,4 @@ public class Order implements Comparable { return created; } - } diff --git a/src/opensesim/world/Quote.java b/src/opensesim/world/Quote.java new file mode 100644 index 0000000..04af146 --- /dev/null +++ b/src/opensesim/world/Quote.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016, 7u83 <7u83@mail.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package opensesim.world; + +/** + * + * @author 7u83 <7u83@mail.ru> + */ +public class Quote implements Comparable { + + public double bid; + public double bid_volume; + public double ask; + public double ask_volume; + + public double price; + public double volume; + public long time; + +// Locker lock = new Locker(); + + public void print() { + System.out.print("Quote (" + + time + + ") :" + + price + + " / " + + volume + + "\n" + ); + + } + + public long id; + + @Override + public int compareTo(Object o) { + int ret; + Quote q = (Quote)o; + + ret = (int)(this.time-q.time); + if (ret !=0) + return ret; + + return (int)(this.id-q.id); + } + +} diff --git a/src/opensesim/world/TradingAPI.java b/src/opensesim/world/TradingAPI.java index 326cd24..e3cb9d4 100644 --- a/src/opensesim/world/TradingAPI.java +++ b/src/opensesim/world/TradingAPI.java @@ -38,5 +38,7 @@ public interface TradingAPI { public Order createOrder(Account account, Order.Type type, double volume, double limit); - public Set getOrderBook(Order.Type type); + + public Set getBidBook(); + public Set getAskBook(); } diff --git a/src/opensesim/world/TradingEngine.java b/src/opensesim/world/TradingEngine.java new file mode 100644 index 0000000..03b63a8 --- /dev/null +++ b/src/opensesim/world/TradingEngine.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2018, tube + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package opensesim.world; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import opensesim.util.idgenerator.IDGenerator; +import opensesim.world.scheduler.EventListener; +import opensesim.world.scheduler.FiringEvent; + +/** + * + * @author tube + */ +class TradingEngine implements TradingAPI { + + private final Exchange outer; + + TradingEngine(AssetPair p, final Exchange outer) { + this.outer = outer; + assetpair = p; + reset(); + } + + protected AssetPair getAssetPair() { + return assetpair; + } + IDGenerator id_generator = new IDGenerator(); + private HashMap> order_books; + private SortedSet bidbook; + private SortedSet askbook; + AssetPair assetpair; + + protected final void reset() { + order_books = new HashMap(); + // Create an order book for each order type + for (Order.Type type : Order.Type.values()) { + order_books.put(type, new TreeSet<>()); + } + // Save bidbook and askboo for quicker access + bidbook = order_books.get(Order.Type.BUYLIMIT); + askbook = order_books.get(Order.Type.SELLLIMIT); + // quoteHistory = new TreeSet(); + // ohlc_data = new HashMap(); + } + + /** + * + */ + private void executeOrders() { + SortedSet bid = order_books.get(Order.Type.BUYLIMIT); + SortedSet ask = order_books.get(Order.Type.SELLLIMIT); + SortedSet ul_buy = order_books.get(Order.Type.BUY); + SortedSet ul_sell = order_books.get(Order.Type.SELL); + double volume_total = 0; + double money_total = 0; + while (true) { + /* // Match unlimited sell orders against unlimited buy orders + if (!ul_sell.isEmpty() && !ul_buy.isEmpty()) { + Order a = ul_sell.first(); + Order b = ul_buy.first(); + Double price = getBestPrice(stock); + if (price == null) { + break; + } + double volume = b.volume >= a.volume ? a.volume : b.volume; + finishTrade(b, a, price, volume); + volume_total += volume; + money_total += price * volume; + this.checkSLOrders(price); + } + while (!ul_buy.isEmpty() && !ask.isEmpty()) { + Order a = ask.first(); + Order b = ul_buy.first(); + double price = a.limit; + double volume = b.volume >= a.volume ? a.volume : b.volume; + finishTrade(b, a, price, volume); + volume_total += volume; + money_total += price * volume; + this.checkSLOrders(price); + } + // Match unlimited sell orders against limited buy orders + while (!ul_sell.isEmpty() && !bid.isEmpty()) { + Order b = bid.first(); + Order a = ul_sell.first(); + double price = b.limit; + double volume = b.volume >= a.volume ? a.volume : b.volume; + finishTrade(b, a, price, volume); + volume_total += volume; + money_total += price * volume; + this.checkSLOrders(price); + } + */ + // Match limited orders against limited orders + if (bid.isEmpty() || ask.isEmpty()) { + // there is nothing to do + break; + } + Order b = bid.first(); + Order a = ask.first(); + if (b.limit < a.limit) { + break; + } + // There is a match, calculate price and volume + double price = b.id.compareTo(a.id) < 0 ? b.limit : a.limit; + double volume = b.volume >= a.volume ? a.volume : b.volume; + // finishTrade(b, a, price, volume); + volume_total += volume; + money_total += price * volume; + // statistics.trades++; + // this.checkSLOrders(price); + } + if (volume_total == 0) { + return; + } + Quote q = new Quote(); + q.price = money_total / volume_total; + q.volume = volume_total; + // q.time = timer.currentTimeMillis(); + // addQuoteToHistory(q); + } + + protected void addOrderToBook(Order o) { + order_books.get(o.type).add(o); + switch (o.type) { + case BUY: + case BUYLIMIT: + break; + case SELL: + case SELLLIMIT: + break; + } + } + + public Double getBestPrice() { + SortedSet bid = order_books.get(Order.Type.BUYLIMIT); + SortedSet ask = order_books.get(Order.Type.SELLLIMIT); + Quote lq = null; //this.getLastQuoete(); + Order b = null; + Order a = null; + if (!bid.isEmpty()) { + b = bid.first(); + } + if (!ask.isEmpty()) { + a = ask.first(); + } + // If there is neither bid nor ask and no last quote + // we can't return a quote + if (lq == null && b == null && a == null) { + return null; + } + // there is bid and ask + if (a != null && b != null) { + Quote q = new Quote(); + System.out.printf("aaaaa bbbbb %f %f \n", a.limit, b.limit); + // if there is no last quote calculate from bid and ask + //if (lq == null) { + double rc = (bid.first().limit + ask.first().limit) / 2.0; + System.out.printf("RCRC2.0: %f\n", rc); + return rc; + // } + /* + if (lq.price < b.limit) { + return b.limit; + } + if (lq.price > a.limit) { + return a.limit; + } + return lq.price; + */ + } + if (a != null) { + Quote q = new Quote(); + if (lq == null) { + return a.limit; + } + if (lq.price > a.limit) { + return a.limit; + } + return lq.price; + } + if (b != null) { + Quote q = new Quote(); + if (lq == null) { + return b.limit; + } + if (lq.price < b.limit) { + return b.limit; + } + return lq.price; + } + if (lq == null) { + return null; + } + return lq.price; + } + + @Override + public Order createOrder(Account account, Order.Type type, double volume, double limit) { + Order o = new opensesim.world.Order(this, account, type, volume, limit); + System.out.printf("The new Order has: volume: %f limit: %f\n", o.getVolume(), o.getLimit()); + synchronized (this) { + order_books.get(o.type).add(o); + } + for (FiringEvent e : book_listener) { + e.fire(); + } + return o; + } + HashSet book_listener = new HashSet<>(); + + @Override + public void addOrderBookListener(EventListener listener) { + book_listener.add(new FiringEvent(listener)); + } + + protected Set getOrderBook(Order.Type type) { + return Collections.unmodifiableSet(order_books.get(type)); + } + + @Override + public Set getBidBook() { + return getOrderBook(Order.Type.BUY); + } + + @Override + public Set getAskBook() { + return getOrderBook(Order.Type.SELL); + } + +}