/* * Copyright (c) 2017, 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.old_sesim; import opensesim.util.idgenerator.LongIDGenerator; import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONArray; import org.json.JSONObject; import opensesim.old_sesim.Order.OrderStatus; import opensesim.old_sesim.Order.OrderType; /** * @desc Exchange class * @author 7u83 */ public class Exchange { //private HashMap> order_books; HashMap stocks; final String DEFAULT_STOCK = "def"; public Stock getStock(String symbol) { return stocks.get(symbol); } public Stock getDefaultStock() { return getStock(DEFAULT_STOCK); } public String getDefaultStockSymbol() { return DEFAULT_STOCK; } private double money_df = 10000; private int money_decimals = 2; DecimalFormat money_formatter; /** * Set the number of decimals used with money * * @param n number of decimals */ public void setMoneyDecimals(int n) { money_df = Math.pow(10, n); money_decimals = n; money_formatter = getFormatter(n); } private double shares_df = 1; private double shares_decimals = 0; private DecimalFormat shares_formatter; /** * Set the number of decimals for shares * * @param n number of decimals */ public void setSharesDecimals(int n) { shares_df = Math.pow(10, n); shares_decimals = n; shares_formatter = getFormatter(n); } public double roundToDecimals(double val, double f) { return Math.floor(val * f) / f; } public double roundShares(double shares) { return roundToDecimals(shares, shares_df); } public double roundMoney(double money) { return roundToDecimals(money, money_df); } public DecimalFormat getFormatter(int n) { DecimalFormat formatter; String s = "#0."; if (n == 0) { s = "#"; } else { for (int i = 0; i < n; i++) { s = s + "0"; } } return new DecimalFormat(s); } public DecimalFormat getMoneyFormatter() { return money_formatter; } public DecimalFormat getSharesFormatter() { return shares_formatter; } LongIDGenerator account_id_generator = new LongIDGenerator(); //public static Timer timer = new Timer(); public Scheduler timer; // = new Scheduler(); public ArrayList traders; /** * */ public interface AccountListener { public void accountUpdated(Account a, Order o); } public interface OrderListener { public void orderUpdated(Order o); } //HashMap ohlc_data = new HashMap<>(); /* public OHLCData buildOHLCData(int timeFrame) { Stock stock = getDefaultStock(); OHLCData data = new OHLCData(timeFrame); if (stock.quoteHistory == null) { return data; } Iterator it = stock.quoteHistory.iterator(); while (it.hasNext()) { Quote q = it.next(); data.realTimeAdd(q.time, (float) q.price, (float) q.volume); } return data; } */ public void injectMoney() { accounts.forEach(new BiConsumer() { @Override public void accept(Object t, Object u) { Account a = (Account) u; // a.money += 20000.0; a.addMoney(20000.0); } }); } public void pointZero() { accounts.forEach(new BiConsumer() { @Override public void accept(Object t, Object u) { Account a = (Account) u; // a.money = 20000.0; a.setMoney(20000.0); } }); } public OHLCData getOHLCdata(Stock stock,Integer timeFrame) { return stock.getOHLCdata(timeFrame); /* OHLCData data; data = stock.ohlc_data.get(timeFrame); if (data == null) { synchronized (executor) { data = this.buildOHLCData(timeFrame); stock.ohlc_data.put(timeFrame, data); } } return data; */ } /* void updateOHLCData(Stock stock,Quote q) { Iterator it = stock.ohlc_data.values().iterator(); while (it.hasNext()) { OHLCData data = it.next(); data.realTimeAdd(q.time, (float) q.price, (float) q.volume); } } */ ArrayList indicators; void updateIndicators(Quote q) { } public void addIndicator(Indicator i) { this.indicators.add(i); } public void createTraders(JSONArray traderdefs) { for (int i = 0; i < traderdefs.length(); i++) { JSONObject o = traderdefs.getJSONObject(i); } // this.traders.add(randt); // randt.setName("Bob"); // randt.start(); } // private final ConcurrentHashMap accounts = new ConcurrentHashMap<>(); private ConcurrentHashMap accounts; public double createAccount(double money, double shares) { double id = (random.nextDouble() + (account_id_generator.getNext())); Account a = new Account(id, money, shares); accounts.put(a.id, a); return a.id; } static class OrderComparator implements Comparator { OrderType type; OrderComparator(OrderType type) { this.type = type; } @Override public int compare(Order left, Order right) { double d; switch (this.type) { case BUYLIMIT: case STOPBUY: case BUY: d = right.limit - left.limit; break; case SELLLIMIT: case STOPLOSS: case SELL: d = left.limit - right.limit; break; default: d = 0; } if (d != 0) { return d > 0 ? 1 : -1; } d = right.initial_volume - left.initial_volume; if (d != 0) { return d > 0 ? 1 : -1; } if (left.id < right.id) { return -1; } if (left.id > right.id) { return 1; } return 0; } } LongIDGenerator order_id_generator = new LongIDGenerator(); final void initExchange() { Stock defstock = new Stock(DEFAULT_STOCK); stocks = new HashMap(); stocks.put(defstock.getSymbol(), defstock); buy_orders = 0; sell_orders = 0; timer = new Scheduler(); // timer = new Scheduler(); random = new Random(12); //random = new Random(); // quoteHistory = new TreeSet(); getDefaultStock().reset(); accounts = new ConcurrentHashMap<>(); traders = new ArrayList(); statistics = new Statistics(); //num_trades = 0; // getDefaultStock().ohlc_data = new HashMap(); // Create order books /* order_books = new HashMap(); for (OrderType type : OrderType.values()) { order_books.put(type, new TreeSet(new OrderComparator(type))); } */ } /** * Constructor */ public Exchange() { qrlist = (new CopyOnWriteArrayList<>()); initExchange(); //executor.start(); } public class Statistics { public long trades; public long orders; public Double heigh; public Double low; public final void reset() { trades = 0; heigh = null; low = null; } Statistics() { reset(); } }; Statistics statistics; // long num_trades = 0; // long num_orders = 0; public Statistics getStatistics() { return statistics; /* Statistics s = new Statistics(); s.trades = num_trades; s.orders = num_orders; return s; */ } /** * Start the exchange */ public void start() { timer.start(); } public void reset() { initExchange(); } public void terminate() { timer.terminate(); } public final String CFG_MONEY_DECIMALS = "money_decimals"; public final String CFG_SHARES_DECIMALS = "shares_decimals"; public void putConfig(JSONObject cfg) { try { this.setMoneyDecimals(cfg.getInt(CFG_MONEY_DECIMALS)); this.setSharesDecimals(cfg.getInt(CFG_SHARES_DECIMALS)); } catch (Exception e) { } } public Double getBestPrice(Stock stock) { SortedSet bid = stock.order_books.get(OrderType.BUYLIMIT); SortedSet ask = stock.order_books.get(OrderType.SELLLIMIT); Quote lq = this.getLastQuoete(); Order b = null, 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; } public Quote getBestPrice_0(Stock stock) { synchronized (stock) { SortedSet bid = stock.order_books.get(OrderType.BUYLIMIT); SortedSet ask = stock.order_books.get(OrderType.SELLLIMIT); Quote lq = this.getLastQuoete(); Order b = null, 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(); // if there is no last quote calculate from bid and ask if (lq == null) { q.price = (bid.first().limit + ask.first().limit) / 2.0; return q; } if (lq.price < b.limit) { q.price = b.limit; return q; } if (lq.price > a.limit) { q.price = a.limit; return q; } return lq; } if (a != null) { Quote q = new Quote(); if (lq == null) { q.price = a.limit; return q; } if (lq.price > a.limit) { q.price = a.limit; return q; } return lq; } if (b != null) { Quote q = new Quote(); if (lq == null) { q.price = b.limit; return q; } if (lq.price < b.limit) { q.price = b.limit; return q; } return lq; } return lq; } } public Quote getBestPrice_0() { return getBestPrice_0(getDefaultStock()); } // Class to describe an executed order // QuoteReceiver has to be implemented by objects that wants // to receive quote updates public interface QuoteReceiver { void UpdateQuote(Quote q); } /** * Bookreceiver Interface */ public interface BookReceiver { void UpdateOrderBook(); } final private ArrayList ask_bookreceivers = new ArrayList<>(); final private ArrayList bid_bookreceivers = new ArrayList<>(); private ArrayList selectBookReceiver(OrderType t) { switch (t) { case SELLLIMIT: return ask_bookreceivers; case BUYLIMIT: return bid_bookreceivers; } return null; } public void addBookReceiver(OrderType t, BookReceiver br) { if (br == null) { // System.out.printf("Br is null\n"); } else { // System.out.printf("Br is not Nukk\n"); } ArrayList bookreceivers; bookreceivers = selectBookReceiver(t); if (bookreceivers == null) { // System.out.printf("null in bookreceivers\n"); } bookreceivers.add(br); } void updateBookReceivers(OrderType t) { ArrayList bookreceivers; bookreceivers = selectBookReceiver(t); Iterator i = bookreceivers.iterator(); while (i.hasNext()) { i.next().UpdateOrderBook(); } } // Here we store the list of quote receivers private final List qrlist; /** * * @param qr */ public void addQuoteReceiver(QuoteReceiver qr) { qrlist.add(qr); } // send updated quotes to all quote receivers private void updateQuoteReceivers(Quote q) { Iterator i = qrlist.iterator(); while (i.hasNext()) { i.next().UpdateQuote(q); } } public ArrayList getOrderBook(OrderType type, int depth) { return getDefaultStock().getOrderBook(type, depth); } /** * * @return */ public Quote getLastQuoete() { Stock stock = getDefaultStock(); if (stock.quoteHistory.isEmpty()) { return null; } return stock.quoteHistory.last(); } private void transferMoneyAndShares(Account src, Account dst, double money, double shares) { // src.money -= money; src.addMoney(-money); // dst.money += money; dst.addMoney(money); // src.shares -= shares; src.addShares(-shares); // dst.shares += shares; src.addShares(shares); } public boolean cancelOrder(Stock stock, double account_id, long order_id) { Account a = accounts.get(account_id); if (a == null) { return false; } boolean ret = false; Order o; synchronized (stock) { o = a.orders.get(order_id); if (o != null) { SortedSet ob = stock.order_books.get(o.type); boolean rc = ob.remove(o); a.orders.remove(o.id); a.update(o); ret = true; } } if (ret) { this.updateBookReceivers(o.type); } // System.out.printf("Levave executor %d\n", Thread.currentThread().getId()); return ret; } public boolean cancelOrder(double account_id, Order order) { return cancelOrder(order.stock, account_id, order.getID()); } Random random; public int randNextInt() { return random.nextInt(); } public int randNextInt(int bounds) { return random.nextInt(bounds); } public double randNextDouble() { return random.nextDouble(); } /** * * @param o */ // long nextQuoteId = 0; public double fairValue = 0; private void removeOrderIfExecuted(Stock stock, Order o) { if (o.volume != 0) { o.status = OrderStatus.PARTIALLY_EXECUTED; o.account.update(o); return; } o.account.orders.remove(o.id); SortedSet book = stock.order_books.get(o.type); book.remove(book.first()); o.status = OrderStatus.CLOSED; o.account.update(o); } private void removeOrderIfExecuted(Order o) { removeOrderIfExecuted(getDefaultStock(), o); } void checkSLOrders(Stock stock, double price) { SortedSet sl = stock.order_books.get(OrderType.STOPLOSS); SortedSet ask = stock.order_books.get(OrderType.SELLLIMIT); if (sl.isEmpty()) { return; } Order s = sl.first(); if (price <= s.limit) { sl.remove(s); s.type = OrderType.SELL; stock.addOrderToBook(s); } } void checkSLOrders(double price) { checkSLOrders(getDefaultStock(), price); } public void executeUnlimitedOrders() { } private void finishTrade(Order b, Order a, double price, double volume) { // Transfer money and shares transferMoneyAndShares(b.account, a.account, volume * price, -volume); // Update volume b.volume -= volume; a.volume -= volume; b.cost += price * volume; a.cost += price * volume; removeOrderIfExecuted(a); removeOrderIfExecuted(b); } void addQuoteToHistory(Quote q) { if (statistics.heigh == null) { statistics.heigh = q.price; } else if (statistics.heigh < q.price) { statistics.heigh = q.price; } if (statistics.low == null) { statistics.low = q.price; } else if (statistics.low > q.price) { statistics.low = q.price; } Stock stock = getDefaultStock(); stock.quoteHistory.add(q); stock.updateOHLCData(q); updateQuoteReceivers(q); } /** * */ private void executeOrders(Stock stock) { SortedSet bid = stock.order_books.get(OrderType.BUYLIMIT); SortedSet ask = stock.order_books.get(OrderType.SELLLIMIT); SortedSet ul_buy = stock.order_books.get(OrderType.BUY); SortedSet ul_sell = stock.order_books.get(OrderType.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 against limited orders if (bid.isEmpty() || ask.isEmpty()) { 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 < a.id ? 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); } long buy_orders = 0; long sell_orders = 0; long buy_failed = 0; long sell_failed = 0; /** * * @param account_id * @param type * @param volume * @param limit * @return order_id */ public Order createOrder(double account_id, String stocksymbol, OrderType type, double volume, double limit) { Stock stock = getStock(stocksymbol); Account a = accounts.get(account_id); if (a == null) { return null; } Order o = new Order(order_id_generator.getNext(), timer.currentTimeMillis(), a, stock, type, roundShares(volume), roundMoney(limit)); if (o.volume <= 0 || o.limit <= 0) { switch (o.type) { case SELL: case SELLLIMIT: sell_failed++; break; case BUY: case BUYLIMIT: buy_failed++; break; } return o; } synchronized (stock) { //num_orders++; statistics.orders++; stock.addOrderToBook(o); a.orders.put(o.id, o); a.update(o); executeOrders(getDefaultStock()); updateBookReceivers(OrderType.SELLLIMIT); updateBookReceivers(OrderType.BUYLIMIT); // System.out.printf("Order to Queeue %s %f %f\n",o.type.toString(),o.volume,o.limit); // order_queue.add(o); // executor.notify(); } // a.update(o); return o; } public double getBestLimit(Stock stock, OrderType type) { Order o = stock.order_books.get(type).first(); if (o == null) { return -1; } return o.limit; } public double getBestLimit(OrderType type) { return getBestLimit(getDefaultStock(), type); } public int getNumberOfOpenOrders(double account_id) { Account a = accounts.get(account_id); if (a == null) { return 0; } return a.orders.size(); } public Account getAccount(double account_id) { return accounts.get(account_id); } }