From 9aba155aee7892c54708b9352d3d4af81bb7c919 Mon Sep 17 00:00:00 2001 From: Weswit Date: Mon, 27 Apr 2015 12:34:22 +0200 Subject: [PATCH] First commit --- .gitattributes | 1 + .gitignore | 1 + README.md | 134 +++++ .../adapters/MarketDepthDemo/Constants.java | 61 ++ .../MarketDepthDataAdapter.java | 302 ++++++++++ .../MarketDepthMetadataAdapter.java | 107 ++++ .../adapters/MarketDepthDemo/MarketMaker.java | 31 ++ .../PriceOutOfBoundException.java | 34 ++ .../QtyOutOfBoundException.java | 47 ++ .../RejectProposalException.java | 30 + .../orders_simulator/Execution.java | 48 ++ .../orders_simulator/OrderBase.java | 52 ++ .../orders_simulator/OrderBuy.java | 25 + .../orders_simulator/OrderGenerator.java | 187 +++++++ .../orders_simulator/OrderSell.java | 25 + .../orders_simulator/Stock.java | 527 ++++++++++++++++++ 16 files changed, 1612 insertions(+) create mode 100644 README.md create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/Constants.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthDataAdapter.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthMetadataAdapter.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/MarketMaker.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/PriceOutOfBoundException.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/QtyOutOfBoundException.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/RejectProposalException.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Execution.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBase.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBuy.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderGenerator.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderSell.java create mode 100644 src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Stock.java diff --git a/.gitattributes b/.gitattributes index bdb0cab..619ff34 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,6 +4,7 @@ # Custom for Visual Studio *.cs diff=csharp + # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain diff --git a/.gitignore b/.gitignore index 96374c4..2bf1528 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ ehthumbs.db # Folder config file Desktop.ini + # Recycle Bin used on file shares $RECYCLE.BIN/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..112d215 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# Lightstreamer - Market Depth Demo - Java Adapter + +The *Market Depth Demo* is a very simple market depth application based on Lightstreamer for its real-time communication needs.
+ +This project includes the server-side part of this demo, the Metadata and Data Adapters implementation. +The client-side part of this demo, a web client front-end, is covered in this project: [Lightstreamer - Market Depth Demo - HTML Client](https://github.com/Weswit/Lightstreamer-example-MarketDepth-client-javascript). + +## Details + +Market depth is an electronic list of buy and sell orders, organized by price level and updated to reflect real-time market activity.
+Market depth data are also known as Level II, Depth Of Market (DOM), and the order book.
+
+Lightstreamer has semi-native support for market depth, which can be managed very easily via COMMAND mode subscriptions. Basically, the bid and ask lists for an instrument will be two items subscribed to in COMMAND mode.
+You will be able to add, update, and remove rows, where each row is identified by a key, which is the price. Lightstreamer will take care of aggregating updates to market depth in the most optimized way, based on bandwidth and frequency constraints.
+This way, you will be able to manage full market depths even on unreliable networks and also benefit from update resampling with conflation, if needed.
+ +### Dig the Code + +The project is comprised of source code and a deployment example. The main elements of the code are the Data Adapter, the MetaData Adapter, and the simulator of market orders. + +#### The Data Adapter + +The Data Adpter is implemented by the *MarketDepthDataAdapter* class. The class implement two interfaces: +- *SmartDataProvider* that concerns relations with Lightstreamer server; +- *MarketMaker* that models a very simple set of possible events from the trading market. + +#### The Metadata Adapter + +The Metadata Adpter is implemented by the *MarketDepthMetadataAdapter* class. The class inherits from the reusable [LiteralBasedProvider](https://github.com/Weswit/Lightstreamer-example-ReusableMetadata-adapter-java) and just adds a simple support for order entry by implementing the NotifyUserMessage method, to handle "sendMessage" requests from the client. +The communication to the Market Orders Simulator, through the Data Adapter, is handled here. + +It should not be used as a reference for a real case of client-originated message handling, as no guaranteed delivery and no clustering support is shown. + +#### The Market Orders Simulator + +The *com.lightstreamer.adapters.MarketDepthDemo.orders_simulator* package contains the implementation of a very simple market orders simulator.
+Orders are randomly generated for ask or bid around a reference price that is also randomly determined. Four stocks are managed, each with a different order generation frequency. +The stocks are listed below in ascending order (from slower to faster): +- *Old&Slow Company ...*, Item name: "OS_C"; +- *BTTQ Industries Sa* Item name: "BTTQ_I"; +- *AXY COMPANY Ltd*, Item name: "AXY_COMP"; +- *SuperFast Tech.*, Item name: "SF_TECH". + +Once a fixed number of executions has been reached, the stock simulates the "End of Day" phase, in which both the order books, for buy and sell, are cleaned up, and the reference price is recalculated (this phase lasts 15 seconds). +
+See the source code comments for further details. + +### The Adapter Set Configuration + +This Adapter Set is configured and will be referenced by the clients as `MARKETDEPTH`. + +The `adapters.xml` file for the Market Depth Demo, should look like: +```xml + + + + + + Y + + + com.lightstreamer.adapters.MarketDepthDemo.MarketDepthMetadataAdapter + + + adapters_log_conf.xml + 10 + + 70 + 700 + 7000 + 35000 + + + 5000 + + + + + + com.lightstreamer.adapters.MarketDepthDemo.MarketDepthDataAdapter + + + +``` + +NOTE: not all configuration options of an Adapter Set are exposed by the file suggested above. +You can easily expand your configurations using the generic template, `DOCS-SDKs/sdk_adapter_java_inprocess/doc/adapter_conf_template/adapters.xml`, as a reference.
+
+Please refer [here](http://www.lightstreamer.com/docs/base/General%20Concepts.pdf) for more details about Lightstreamer Adapters. + +## Install + +If you want to install a version of this demo in your local Lightstreamer Server, follow these steps: +* Download *Lightstreamer Server* (Lightstreamer Server comes with a free non-expiring demo license for 20 connected users) from [Lightstreamer Download page](http://www.lightstreamer.com/download.htm), and install it, as explained in the `GETTING_STARTED.TXT` file in the installation home directory. +* Get the `deploy.zip` file of the [latest release](https://github.com/Weswit/Lightstreamer-example-MarketDepth-adapter-java/releases), unzip it, and copy the just unzipped `MarketDepth` folder into the `adapters` folder of your Lightstreamer Server installation. +* Launch Lightstreamer Server. +* Test the Adapter, launching one of the clients listed in [Clients Using This Adapter](https://github.com/Weswit/Lightstreamer-example-MarketDepth-adapter-java#clients-using-this-adapter). + + +## Build + +To build your own version of `LS_MarketDepth_adapters.jar`, instead of using the one provided in the `deploy.zip` file from the [Install](https://github.com/Weswit/Lightstreamer-example-MarketDepth-adapter-java#install) section above, follow these steps: +* Clone this project. +* Get the `ls-adapter-interface.jar` file from the [latest Lightstreamer distribution](http://www.lightstreamer.com/download), and copy it into the `lib` folder. +* Get the `log4j-1.2.17.jar` file from [Apache log4j](https://logging.apache.org/log4j/1.2/) and copy it into the `lib` folder. +* Create the jar `LS_MarketDepth_adapters.jar` with something like these commands: +```sh + >javac -source 1.7 -target 1.7 -nowarn -g -classpath compile_libs/log4j-1.2.17.jar;compile_libs/ls-adapter-interface/ls-adapter-interface.jar -sourcepath src -d tmp_classes src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthMetadataAdapter.java + + >jar cvf LS_MarketDepth_adapters.jar -C tmp_classes com +``` +* Stop Lightstreamer Server; copy the just compiled `LS_MarketDepth_adapters.jar` in the `/adapters/MarketDepth/lib` folder of your Lightstreamer Server installation; restart Lightstreamer Server. + +## See Also + +### Clients Using This Adapter + +* [Lightstreamer - Basic Market Depth Demo - JavaScript Client](https://github.com/Weswit/Lightstreamer-example-MarketDepth-client-javascript) + +### Related Projects + +* [Lightstreamer - Reusable Metadata Adapters - Java Adapter](https://github.com/Weswit/Lightstreamer-example-ReusableMetadata-adapter-java) +* [Lightstreamer - Stock-List Demos - HTML Clients](https://github.com/Weswit/Lightstreamer-example-StockList-client-javascript) +* [Lightstreamer - Portfolio Demos - HTML Clients](https://github.com/Weswit/Lightstreamer-example-Portfolio-client-javascript) + +## Lightstreamer Compatibility Notes + +* Compatible with Lightstreamer SDK for Java In-Process Adapters since 6.0 diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/Constants.java b/src/com/lightstreamer/adapters/MarketDepthDemo/Constants.java new file mode 100644 index 0000000..33f5162 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/Constants.java @@ -0,0 +1,61 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo; + +import java.util.Map; + +public class Constants { + public static final String LOGGER_CAT = "LS_demos_Logger.MarketDepthDemo.adapters"; + + public static final long MAX_QTY = 100000; + + public static final String BUY_SUFFIX = "_BUYSIDE"; + + public static final String SELL_SUFFIX = "_SELLSIDE"; + + public static long TRADING_DAY_LASTING = 750; + + public static long RND_GENERATOR_1 = 70; + + public static long RND_GENERATOR_2 = 700; + + public static long RND_GENERATOR_3 = 7000; + + public static long RND_GENERATOR_4 = 25000; + + public static void init(Map params) { + + if (params.containsKey("TRADING_DAY_LASTING")) { + TRADING_DAY_LASTING = new Long((String)params.get("TRADING_DAY_LASTING")).longValue(); + } + if (params.containsKey("RND_GENERATOR_SUPERFAST")) { + RND_GENERATOR_1 = new Long((String)params.get("RND_GENERATOR_SUPERFAST")).longValue(); + } + if (params.containsKey("RND_GENERATOR_FAST")) { + RND_GENERATOR_2 = new Long((String)params.get("RND_GENERATOR_FAST")).longValue(); + } + if (params.containsKey("RND_GENERATOR_REGULAR")) { + RND_GENERATOR_3 = new Long((String)params.get("RND_GENERATOR_REGULAR")).longValue(); + } + if (params.containsKey("RND_GENERATOR_SLOW")) { + RND_GENERATOR_3 = new Long((String)params.get("RND_GENERATOR_SLOW")).longValue(); + } + + return ; + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthDataAdapter.java b/src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthDataAdapter.java new file mode 100644 index 0000000..a4c25d9 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthDataAdapter.java @@ -0,0 +1,302 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +package com.lightstreamer.adapters.MarketDepthDemo; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.log4j.Logger; + +import com.lightstreamer.interfaces.data.DataProviderException; +import com.lightstreamer.interfaces.data.ItemEventListener; +import com.lightstreamer.interfaces.data.SmartDataProvider; +import com.lightstreamer.interfaces.data.SubscriptionException; +import com.lightstreamer.adapters.MarketDepthDemo.orders_simulator.*; + +public class MarketDepthDataAdapter implements SmartDataProvider, MarketMaker { + + private Logger logger; + + private ItemEventListener listener; + + private Map availableStocks; + + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + + @Override + public void init(Map params, File configDir) throws DataProviderException { + + logger = Logger.getLogger(Constants.LOGGER_CAT); + + availableStocks = new HashMap(); + + availableStocks.put("SF_TECH", new Stock("SF_TECH","SuperFast Tech.",logger, this, Constants.RND_GENERATOR_1)); + + availableStocks.put("AXY_COMP", new Stock("AXY_COMP","AXY COMPANY Ltd",logger, this, Constants.RND_GENERATOR_2)); + + availableStocks.put("BTTQ_I", new Stock("BTTQ_I", "BTTQ Industries Sa",logger, this, Constants.RND_GENERATOR_3)); + + availableStocks.put("OS_C", new Stock("OS_C", "Old&Slow Company ...",logger, this, Constants.RND_GENERATOR_4)); + + + MarketDepthMetadataAdapter.setDataDadapter(this); + + logger.info("MarketDepth Demo Data Adapter start."); + } + + @Override + public void setListener(ItemEventListener listener) { + // Save the update listener + this.listener = listener; + } + + @Override + public boolean isSnapshotAvailable(String arg0) throws SubscriptionException { + // We have always the snapshot available from our feed + return true; + } + + @Override + public void subscribe(String item, Object handle, boolean arg2) + throws SubscriptionException { + + logger.debug("Received subscribe request for item: " + item + "."); + + if (availableStocks.containsKey(item)) { + Stock myStock = availableStocks.get(item); + this.listener.smartUpdate(handle, myStock.synUpdate() , true); + this.listener.smartEndOfSnapshot(handle); + myStock.setLs_handle(handle); + } else if (item.endsWith(Constants.BUY_SUFFIX)) { + String k = item.substring(0, item.indexOf(Constants.BUY_SUFFIX)); + if (availableStocks.containsKey(k)) { + Stock myStock = availableStocks.get(k); + sendBuyOrderSnapshot(myStock.getBuyOrders(),handle); + myStock.setLs_buy_handle(handle); + + myStock.startSimulation(); + } + } else if (item.endsWith(Constants.SELL_SUFFIX)) { + String k = item.substring(0, item.indexOf(Constants.SELL_SUFFIX)); + if (availableStocks.containsKey(k)) { + Stock myStock = availableStocks.get(k); + sendSellOrderSnapshot(myStock.getSellOrders(),handle); + myStock.setLs_sell_handle(handle); + } + } else { + logger.warn("" + item + " item unrecognized."); + } + + return ; + } + + @Override + public void unsubscribe(String item) + throws SubscriptionException { + + logger.debug("Received unsubscribe request for item: " + item + "."); + + if (availableStocks.containsKey(item)) { + (availableStocks.get(item)).setLs_handle(null); + (availableStocks.get(item)).stopSimulator(); + } + + return ; + } + + @Override + public void subscribe(String portfolioId, boolean arg1) { + // Never called on a SmartDataProvider + assert(false); + } + + private void sendBuyOrderSnapshot(Iterator list, Object handle) { + final Object h = handle; + if ( listener == null ) { + return ; + } + + while ( list.hasNext() ) { + OrderBuy ob = list.next(); + final HashMap update = new HashMap(); + + update.put("command", "ADD"); + update.put("key", ""+ob.getPrice()); + update.put("qty", ""+ob.getQuantity()); + + logger.debug("ADD command for buy order " + ob.getPrice() + " x " + ob.getQuantity()); + + // If we have a listener create a new Runnable to be used as a task to pass the new update to the listener + Runnable updateTask = new Runnable() { + @Override + public void run() { + listener.smartUpdate(h, update, true); + } + }; + + // We add the task on the executor to pass to the listener the actual status + executor.execute(updateTask); + } + + } + + private void sendSellOrderSnapshot(Iterator list, Object handle) { + final Object h = handle; + if ( listener == null ) { + return ; + } + + while ( list.hasNext() ) { + OrderSell os = list.next(); + final HashMap update = new HashMap(); + + update.put("command", "ADD"); + update.put("key", ""+os.getPrice()); + update.put("qty", ""+os.getQuantity()); + + logger.debug("ADD command for sell order " + os.getPrice() + " x " + os.getQuantity()); + + //If we have a listener create a new Runnable to be used as a task to pass the new update to the listener + Runnable updateTask = new Runnable() { + @Override + public void run() { + listener.smartUpdate(h, update, true); + } + }; + + //We add the task on the executor to pass to the listener the actual status + executor.execute(updateTask); + } + + } + + public void newTradingProposal(String symbol, double price, long qty, boolean buy) throws PriceOutOfBoundException, RejectProposalException, QtyOutOfBoundException { + if (availableStocks.containsKey(symbol)) { + Stock myStock = availableStocks.get(symbol); + + myStock.newTradingProposal(qty, price, buy); + } + + return ; + } + + @Override + public void newOrderLevel(String symbol, OrderBase ob) { + if (availableStocks.containsKey(symbol)) { + Stock myStock = availableStocks.get(symbol); + final HashMap update = new HashMap(); + + update.put("command", "ADD"); + update.put("key", ""+ob.getPrice()); + update.put("qty", ""+ob.getQuantity()); + + if (ob instanceof OrderBuy ) { + logger.debug("ADD command for buy order " + ob.getPrice() + " x " + ob.getQuantity()); + + listener.smartUpdate(myStock.getLs_buy_handle(), update, false); + } else { + logger.debug("ADD command for sell order " + ob.getPrice() + " x " + ob.getQuantity()); + + listener.smartUpdate(myStock.getLs_sell_handle(), update, false); + } + + listener.smartUpdate(myStock.getLs_handle(), myStock.synUpdate(), false); + } + } + + @Override + public void orderLevelChange(String symbol, OrderBase ob) { + if (availableStocks.containsKey(symbol)) { + Stock myStock = availableStocks.get(symbol); + final HashMap update = new HashMap(); + + update.put("command", "UPDATE"); + update.put("key", ""+ob.getPrice()); + update.put("qty", ""+ob.getQuantity()); + + if (ob instanceof OrderBuy ) { + logger.debug("UPDATE command for buy order " + ob.getPrice() + " x " + ob.getQuantity()); + + listener.smartUpdate(myStock.getLs_buy_handle(), update, false); + } else { + + logger.debug("UPDATE command for sell order " + ob.getPrice() + " x " + ob.getQuantity()); + + listener.smartUpdate(myStock.getLs_sell_handle(), update, false); + } + } + } + + @Override + public void deleteOrderLevel(String symbol, OrderBase ob) { + if (availableStocks.containsKey(symbol)) { + Stock myStock = availableStocks.get(symbol); + final HashMap update = new HashMap(); + + update.put("command", "DELETE"); + update.put("key", ""+ ob.getPrice()); + + if (ob instanceof OrderSell ) { + logger.debug("DELETE command for sell order " + ob.getPrice() + " x " + ob.getQuantity()); + + listener.smartUpdate(myStock.getLs_sell_handle(), update, false); + } else { + logger.debug("DELETE command for buy order " + ob.getPrice() + " x " + ob.getQuantity()); + + listener.smartUpdate(myStock.getLs_buy_handle(), update, false); + } + + listener.smartUpdate(myStock.getLs_handle(), myStock.synUpdate(), false); + } + } + + @Override + public void newExecution(String symbol) { + Stock myStock = availableStocks.get(symbol); + + logger.debug("Syn update for " + symbol + "."); + + listener.smartUpdate(myStock.getLs_handle(), myStock.synUpdate(), false); + // For Time&Sales push of the new deal. + } + + @Override + public void endOfDay(String symbol) { + Stock myStock = availableStocks.get(symbol); + + if (myStock != null ) { + listener.smartUpdate(myStock.getLs_handle(), myStock.synUpdate(), false); + + if ( myStock.getLs_buy_handle() != null ) { + logger.info("Buy side " + myStock.getSymbol() + ": clearSnapshot."); + + listener.smartClearSnapshot(myStock.getLs_buy_handle()); + } + if ( myStock.getLs_sell_handle() != null ) { + logger.info("Sell side " + myStock.getSymbol() + ": clearSnapshot."); + + listener.smartClearSnapshot(myStock.getLs_sell_handle()); + } + } + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthMetadataAdapter.java b/src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthMetadataAdapter.java new file mode 100644 index 0000000..314bdd2 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/MarketDepthMetadataAdapter.java @@ -0,0 +1,107 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo; + +import java.util.Map; +import java.io.File; + +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; + +import com.lightstreamer.adapters.metadata.LiteralBasedProvider; +import com.lightstreamer.interfaces.metadata.CreditsException; +import com.lightstreamer.interfaces.metadata.MetadataProviderException; +import com.lightstreamer.interfaces.metadata.NotificationException; + +public class MarketDepthMetadataAdapter extends LiteralBasedProvider { + + private Logger logger; + + private static MarketDepthDataAdapter dataAdapter; + + @Override + public void init(Map params, File configDir) throws MetadataProviderException { + String logConfig = (String) params.get("log_config"); + if (logConfig != null) { + File logConfigFile = new File(configDir, logConfig); + String logRefresh = (String) params.get("log_config_refresh_seconds"); + if (logRefresh != null) { + DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), Integer.parseInt(logRefresh) * 1000); + } else { + DOMConfigurator.configure(logConfigFile.getAbsolutePath()); + } + } //else the bridge to logback is expected + + logger = Logger.getLogger(Constants.LOGGER_CAT); + + Constants.init(params); + + logger.debug("Rnd generator time (1): " + Constants.RND_GENERATOR_1); + logger.debug("Rnd generator time (2):" + Constants.RND_GENERATOR_2); + logger.debug("Rnd generator time (3): " + Constants.RND_GENERATOR_3); + logger.debug("Rnd generator time (4): " + Constants.RND_GENERATOR_4); + + logger.info("MarketDepth Demo Metadata Adapter start."); + } + + public static void setDataDadapter(MarketDepthDataAdapter d) { + dataAdapter = d; + } + + + @Override + public boolean wantsTablesNotification(java.lang.String user) { + return false; + } + + @Override + public void notifyUserMessage(String user, String session, String message) throws CreditsException, NotificationException { + + if (message == null) { + logger.warn("Null message received"); + throw new NotificationException("Null message received"); + } + + String[] pieces = message.split("\\|"); + + try { + this.handlePortfolioMessage(pieces); + } catch (PriceOutOfBoundException poobe) { + throw new CreditsException(-10025, poobe.getMessage()); + } catch (QtyOutOfBoundException qoobe) { + throw new CreditsException(-10026, qoobe.getMessage()); + } catch (RejectProposalException rpe) { + throw new CreditsException(-10027, rpe.getMessage()); + } + + return ; + } + + private void handlePortfolioMessage(String[] splits) throws PriceOutOfBoundException, RejectProposalException, QtyOutOfBoundException { + + if ( splits.length == 4 ) { + logger.info("New trading proposal: " + splits[0] + ", " + splits[1] + ", " + splits[2]); + dataAdapter.newTradingProposal(splits[1], new Double(splits[3]).doubleValue(), new Long(splits[2]).longValue(), (splits[0].equals("BUY"))); + } else { + logger.warn("Wrong formatted message received. No pieces: " + splits.length); + } + + return ; + } + +} + diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/MarketMaker.java b/src/com/lightstreamer/adapters/MarketDepthDemo/MarketMaker.java new file mode 100644 index 0000000..73f9196 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/MarketMaker.java @@ -0,0 +1,31 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo; + +import com.lightstreamer.adapters.MarketDepthDemo.orders_simulator.*; + +public interface MarketMaker { + public void newOrderLevel(String symbol, OrderBase ob); + + public void orderLevelChange(String symbol, OrderBase ob); + + public void deleteOrderLevel(String symbol, OrderBase ob); + + public void newExecution(String symbol); + + public void endOfDay(String symbol); +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/PriceOutOfBoundException.java b/src/com/lightstreamer/adapters/MarketDepthDemo/PriceOutOfBoundException.java new file mode 100644 index 0000000..30b9a11 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/PriceOutOfBoundException.java @@ -0,0 +1,34 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo; + +public class PriceOutOfBoundException extends Exception { + + private double ref = 0.0; + + public PriceOutOfBoundException(double price) { + super(); + this.ref = price; + } + + @Override + public String getMessage() { + + return "REJECT: invalid limit price (reference price:" + this.ref +")."; + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/QtyOutOfBoundException.java b/src/com/lightstreamer/adapters/MarketDepthDemo/QtyOutOfBoundException.java new file mode 100644 index 0000000..ec625fd --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/QtyOutOfBoundException.java @@ -0,0 +1,47 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo; + +public class QtyOutOfBoundException extends Exception { + + public QtyOutOfBoundException() { + + } + + public QtyOutOfBoundException(String message) { + super(message); + } + + public QtyOutOfBoundException(Throwable cause) { + super(cause); + } + + public QtyOutOfBoundException(String message, Throwable cause) { + super(message, cause); + } + + public QtyOutOfBoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + @Override + public String getMessage() { + + return "REJECT: Quantity exceeds limit (" + Constants.MAX_QTY +")."; + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/RejectProposalException.java b/src/com/lightstreamer/adapters/MarketDepthDemo/RejectProposalException.java new file mode 100644 index 0000000..b2d6557 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/RejectProposalException.java @@ -0,0 +1,30 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo; + +public class RejectProposalException extends Exception { + + public RejectProposalException() { + super(); + } + + @Override + public String getMessage() { + return "REJECT: market not open."; + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Execution.java b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Execution.java new file mode 100644 index 0000000..f5ba214 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Execution.java @@ -0,0 +1,48 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo.orders_simulator; + +import java.util.Date; + +public class Execution { + + public Execution(long qty, double price, Date ts) { + super(); + this.qty = qty; + this.price = price; + this.ts = ts; + } + + public long getQty() { + return qty; + } + + public double getPrice() { + return price; + } + + public Date getTs() { + return ts; + } + + private long qty; + + private double price; + + private Date ts; + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBase.java b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBase.java new file mode 100644 index 0000000..a68b0fa --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBase.java @@ -0,0 +1,52 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo.orders_simulator; + +public class OrderBase { + + private double price; + + private long quantity; + + public double getPrice() { + return price; + } + public void setPrice(double price) { + this.price = price; + } + + public long getQuantity() { + return quantity; + } + public void setQuantity(long quantity) { + this.quantity = quantity; + } + + public OrderBase(double price, long quantity) { + super(); + this.price = price; + this.quantity = quantity; + } + + public void add(OrderBase newOrder) { + this.quantity += newOrder.getQuantity(); + } + + public void remove(OrderBase newOrder) { + this.quantity -= newOrder.getQuantity(); + } +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBuy.java b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBuy.java new file mode 100644 index 0000000..45274ce --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderBuy.java @@ -0,0 +1,25 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo.orders_simulator; + +public class OrderBuy extends OrderBase { + + public OrderBuy(double price, long quantity) { + super(price, quantity); + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderGenerator.java b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderGenerator.java new file mode 100644 index 0000000..56af231 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderGenerator.java @@ -0,0 +1,187 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo.orders_simulator; + +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; + +import com.lightstreamer.adapters.MarketDepthDemo.Constants; + +public class OrderGenerator { + + private Random myGenerator; + + private Stock stockbase; + + private double basePrice = 0.0; + + private Timer dispatcher = null; + + private long frequencyFactor = 50; + + private boolean active = false; + + private boolean restart = true; + + public void setBasePrice(double basePrice) { + this.basePrice = basePrice; + } + + public OrderGenerator(Stock s) { + this.basePrice = s.getReferencePrice(); + this.frequencyFactor = s.getRnd_gen(); + this.stockbase = s; + + this.myGenerator = new Random(System.currentTimeMillis()*this.frequencyFactor); + } + + public OrderBuy initBuyOrder() { + double rnd; + long base = (long)Math.ceil(this.basePrice * 100); + + rnd = myGenerator.nextDouble(); + while ( (rnd < 0.75) || (rnd > 0.98) ) { + rnd = myGenerator.nextDouble(); + } + + return new OrderBuy(Math.ceil(base*rnd)/100, myGenerator.nextInt(((int)Constants.MAX_QTY))); + } + + public OrderSell initSellOrder() { + double rnd; + long base = (long)Math.ceil(this.basePrice * 100); + + rnd = myGenerator.nextDouble() + 1.0; + while ( (rnd < 1.02) || (rnd > 1.25) ) { + rnd = myGenerator.nextDouble() + 1.0; + } + + return new OrderSell(Math.ceil(base*rnd)/100, myGenerator.nextInt(((int)Constants.MAX_QTY))); + } + + public OrderBase negOrder() { + double rnd,newPrice; + long base = (long)Math.ceil(this.basePrice * 100); + + if ( basePrice == 0 ) { + return null; + } + + if ( this.myGenerator.nextDouble() < 0.5 ) { + // BUY ORDER + + rnd = myGenerator.nextDouble() + 0.1; + while ( (rnd > 1.1) || (rnd < 0.75) ) { + rnd = myGenerator.nextDouble() + 0.1; + } + newPrice = Math.ceil(base*rnd)/100; + if (newPrice < 0.20) { + newPrice = 0.20; + } else if (newPrice > 200.0) { + newPrice = 200.0; + } + + return new OrderBuy(newPrice, myGenerator.nextInt(((int)Constants.MAX_QTY))); + } else { + // SELL ORDER + + rnd = myGenerator.nextDouble() + 0.25; + while ( (rnd > 1.25) || (rnd < 0.9) ) { + rnd = myGenerator.nextDouble() + 0.25; + } + newPrice = Math.ceil(base*rnd)/100; + if (newPrice < 0.20) { + newPrice = 0.20; + } else if (newPrice > 200.0) { + newPrice = 200.0; + } + + return new OrderSell(newPrice, myGenerator.nextInt(((int)Constants.MAX_QTY))); + } + } + + public void startSimulation(boolean eod) { + + if (eod) { + restart = true; + } else { + + if (dispatcher == null) { + dispatcher = new Timer(); + } + + active = true; + + scheduleGenerator((int)Math.ceil(myGenerator.nextDouble()*this.frequencyFactor)+1500); + + } + return ; + } + + public void stopSimulation(boolean eod) { + if (eod) { + restart = false; + } else { + + active = false; + + if (dispatcher != null) { + dispatcher.cancel(); + dispatcher = null; + } + + } + } + + public void overnight() { + dispatcher = new Timer(); + restart = true; + + dispatcher.schedule(new TimerTask() { + @Override + public void run() { + synchronized (this) { + stockbase.restartTradingDay(); + if (restart) { + startSimulation(false); + } + } + } + }, 15000); + } + + private void scheduleGenerator(int waitTime) { + + if ( active ) { + dispatcher.schedule(new TimerTask() { + @Override + public void run() { + int nextWaitTime; + synchronized (this) { + OrderBase ob = negOrder(); + stockbase.newDemoProposal(ob); + + nextWaitTime = (int)Math.ceil(myGenerator.nextDouble()*frequencyFactor); + } + scheduleGenerator(nextWaitTime); + } + }, waitTime); + } + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderSell.java b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderSell.java new file mode 100644 index 0000000..6ed6ed9 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/OrderSell.java @@ -0,0 +1,25 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo.orders_simulator; + +public class OrderSell extends OrderBase { + + public OrderSell(double price, long quantity) { + super(price, quantity); + } + +} diff --git a/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Stock.java b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Stock.java new file mode 100644 index 0000000..d4f2e04 --- /dev/null +++ b/src/com/lightstreamer/adapters/MarketDepthDemo/orders_simulator/Stock.java @@ -0,0 +1,527 @@ +/* + Copyright 2015 Weswit Srl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.lightstreamer.adapters.MarketDepthDemo.orders_simulator; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.log4j.Logger; + +import com.lightstreamer.adapters.MarketDepthDemo.Constants; +import com.lightstreamer.adapters.MarketDepthDemo.MarketMaker; +import com.lightstreamer.adapters.MarketDepthDemo.PriceOutOfBoundException; +import com.lightstreamer.adapters.MarketDepthDemo.QtyOutOfBoundException; +import com.lightstreamer.adapters.MarketDepthDemo.RejectProposalException; + +public class Stock { + + private Logger logger; + + private String symbol; + + private String shortDescription; + + private double referencePrice; + + private String tradingPhase; + + private double lastPrice = 0; + + private long lastQty = 0; + + private Date last = null; + + private long rnd_gen = 10; + + private Map BuyOrders; + + private Map SellOrders; + + private ArrayList ExecList; + + private Random generator; + + private final OrderGenerator myGenerator; + + private Object ls_handle = null; + + private Object ls_buy_handle = null; + + private Object ls_sell_handle = null; + + private MarketMaker listener = null; + + public String getSymbol() { + return symbol; + } + + public double getReferencePrice() { + return referencePrice; + } + + public long getRnd_gen() { + return rnd_gen; + } + + public Object getLs_handle() { + return ls_handle; + } + + public void setLs_handle(Object ls_handle) { + this.ls_handle = ls_handle; + } + + public Object getLs_buy_handle() { + return ls_buy_handle; + } + + public void setLs_buy_handle(Object ls_buy_handle) { + this.ls_buy_handle = ls_buy_handle; + } + + public Object getLs_sell_handle() { + return ls_sell_handle; + } + + public void setLs_sell_handle(Object ls_sell_handle) { + this.ls_sell_handle = ls_sell_handle; + } + + public Stock(String sym, String desc, Logger logger, MarketMaker mm, long rnd_gen) { + this.symbol = sym; + this.shortDescription = desc; + this.logger = logger; + this.listener = mm; + this.rnd_gen = rnd_gen; + this.tradingPhase = "Opening Auction"; + + generator = new Random(System.currentTimeMillis()*rnd_gen); + + this.referencePrice = Math.floor(generator.nextDouble() * 15000)/100; + + this.logger.info("Stock " + this.shortDescription + " reference price: " + this.referencePrice); + + this.myGenerator = new OrderGenerator(this); + + this.BuyOrders = new HashMap(); + this.SellOrders = new HashMap(); + this.ExecList = new ArrayList(); + + int i = 0; + int max = generator.nextInt(10) + 1; + while ( i++ < max ) { + OrderBuy ob = myGenerator.initBuyOrder(); + Double k = new Double(ob.getPrice()); + if (!BuyOrders.containsKey(k)) { + BuyOrders.put(k,ob); + } else { + ((OrderBuy)(BuyOrders.get(k))).add(ob); + } + + this.logger.debug("New initial buy order - " + ob.getPrice() + " x " + ob.getQuantity()); + } + + i = 0; + max = generator.nextInt(10) + 1; + while ( i++ < max ) { + OrderSell os = myGenerator.initSellOrder(); + Double k = new Double(os.getPrice()); + if (!SellOrders.containsKey(k)) { + SellOrders.put(k,os); + } else { + ((OrderSell)(SellOrders.get(k))).add(os); + } + + this.logger.debug("New initial sell order - " + os.getPrice() + " x " + os.getQuantity()); + } + } + + public void startSimulation() { + + if ( !this.tradingPhase.equals("End Of Day")) { + myGenerator.startSimulation(false); + } else { + myGenerator.startSimulation(true); + } + + } + + public void stopSimulator() { + + if ( !this.tradingPhase.equals("End Of Day")) { + myGenerator.stopSimulation(false); + } else { + myGenerator.stopSimulation(true); + } + + } + + public HashMap synUpdate() { + HashMap update = new HashMap(); + + // Data summary + update.put("symbol", this.symbol); + update.put("short_description", this.shortDescription); + update.put("reference_price", ""+this.referencePrice); + update.put("trading_phase", this.tradingPhase); + + // Last execution + update.put("last_price", ""+this.lastPrice); + update.put("last_qty", ""+this.lastQty); + if ( this.last == null ) { + update.put("last", null); + } else { + update.put("last", ""+this.last.getTime()); + } + + // Market Depth + update.put("buy_depth",""+this.BuyOrders.size()); + update.put("sell_depth",""+this.SellOrders.size()); + + return update; + } + + public Iterator getBuyOrders() { + return BuyOrders.values().iterator(); + } + + public Iterator getSellOrders() { + return SellOrders.values().iterator(); + } + + public void newDemoProposal(OrderBase newOrder) { + + mathcingEngine(newOrder); + + } + + public void newTradingProposal(long qty, double price, boolean buy) throws PriceOutOfBoundException, RejectProposalException, QtyOutOfBoundException { + synchronized (myGenerator) { + OrderBase newOrder; + + if ( this.tradingPhase.equals("End Of Day") ) { + throw new RejectProposalException(); + } + + checkPriceOrder(price); + checkQtyOrder(qty); + if ( buy ) { + newOrder = new OrderBuy(price, qty); + } else { + newOrder = new OrderSell(price, qty); + } + + mathcingEngine(newOrder); + } + return ; + } + + + public void restartTradingDay() { + this.tradingPhase = "Opening Auction"; + + logger.info("New trading day for " + symbol); + + this.last = null; + this.lastPrice = 0.0; + this.lastQty = 0; + + listener.newExecution(symbol); + } + + private void checkPriceOrder(double orderPrice) throws PriceOutOfBoundException { + if ( orderPrice > (this.referencePrice*1.26) ) { + throw new PriceOutOfBoundException(this.referencePrice); + } else if ( orderPrice < (this.referencePrice*0.74) ) { + throw new PriceOutOfBoundException(this.referencePrice); + } else { + return ; + } + } + + private void checkQtyOrder(long qty) throws QtyOutOfBoundException { + + if (qty > Constants.MAX_QTY) { + throw new QtyOutOfBoundException(); + } + + return ; + } + + private void mathcingEngine(OrderBase newOrder) { + if ( newOrder instanceof OrderBuy ) { + TreeSet sellLevels = new TreeSet(SellOrders.keySet()); + long remainQty = newOrder.getQuantity(); + + while (remainQty > 0) { + if (sellLevels.isEmpty()) { + Double key = new Double(newOrder.getPrice()); + + if ( BuyOrders.containsKey(key)) { + if ( ((OrderBuy)(BuyOrders.get(key))).getQuantity() < 1000000 ) { + ((OrderBuy)(BuyOrders.get(key))).add(newOrder); + listener.orderLevelChange(symbol, (OrderBuy)(BuyOrders.get(key))); + } + } else { + BuyOrders.put(key, (OrderBuy)newOrder); + listener.newOrderLevel(symbol, newOrder); + } + + remainQty = 0; + } else { + long execQty; + double sellLevel = sellLevels.first().doubleValue(); + + logger.debug("High Sell level: " + sellLevels.first()); + + if ( sellLevel <= newOrder.getPrice() ) { + execQty = executionS(sellLevels.first(), newOrder); + + if ( newExecution(execQty, sellLevel) ) { + + remainQty -= execQty; + newOrder.setQuantity(remainQty); + + logger.debug("Remain qty: " + remainQty); + sellLevels = new TreeSet(SellOrders.keySet()); + } else { + remainQty = 0; + } + } else { + Double key = new Double(newOrder.getPrice()); + + if ( BuyOrders.containsKey(key)) { + if ( ((OrderBuy)(BuyOrders.get(key))).getQuantity() < 1000000 ) { + ((OrderBuy)(BuyOrders.get(key))).add(newOrder); + listener.orderLevelChange(symbol, (OrderBuy)(BuyOrders.get(key))); + } + } else { + BuyOrders.put(key, (OrderBuy)newOrder); + listener.newOrderLevel(symbol, newOrder); + } + remainQty = 0; + } + } + } + } else if ( newOrder instanceof OrderSell ) { + TreeSet buyLevels = new TreeSet(BuyOrders.keySet()); + long remainQty = newOrder.getQuantity(); + + while (remainQty > 0) { + + if (buyLevels.isEmpty()) { + Double key = new Double(newOrder.getPrice()); + + if ( SellOrders.containsKey(key)) { + if ( ((OrderSell)(SellOrders.get(key))).getQuantity() < 1000000 ) { + ((OrderSell)(SellOrders.get(key))).add(newOrder); + listener.orderLevelChange(symbol, (OrderSell)(SellOrders.get(key))); + } + } else { + SellOrders.put(key, (OrderSell)newOrder); + listener.newOrderLevel(symbol, newOrder); + } + + remainQty = 0; + } else { + long execQty; + double buyLevel = buyLevels.last().doubleValue(); + + if ( buyLevel >= newOrder.getPrice() ) { + execQty = executionB(buyLevels.last(), newOrder); + + if ( newExecution(execQty, buyLevel) ) { + remainQty -= execQty; + newOrder.setQuantity(remainQty); + buyLevels = new TreeSet(BuyOrders.keySet()); + } else { + remainQty = 0; + } + } else { + Double key = new Double(newOrder.getPrice()); + + if ( SellOrders.containsKey(key)) { + if ( ((OrderSell)(SellOrders.get(key))).getQuantity() < 1000000 ) { + ((OrderSell)(SellOrders.get(key))).add(newOrder); + listener.orderLevelChange(symbol, (OrderSell)(SellOrders.get(key))); + } + } else { + SellOrders.put(key, (OrderSell)newOrder); + listener.newOrderLevel(symbol, newOrder); + } + remainQty = 0; + } + } + } + + } else { + return ; + } + + return ; + } + + private long executionS(Double sellLevel, OrderBase newOrder) { + long qty = newOrder.getQuantity(); + + if ( SellOrders.containsKey(sellLevel) ) { + OrderSell os = SellOrders.get(sellLevel); + long sellLevelQty = os.getQuantity(); + + logger.debug("High Sell level Qty: " + sellLevelQty); + + if ( sellLevelQty <= qty ) { + + SellOrders.remove(sellLevel); + listener.deleteOrderLevel(symbol, os); + + return sellLevelQty; + } else { + SellOrders.get(sellLevel).remove(newOrder); + listener.orderLevelChange(symbol, os); + } + } + + return qty; + } + + private long executionB(Double buyLevel, OrderBase newOrder) { + long qty = newOrder.getQuantity(); + + if ( BuyOrders.containsKey(buyLevel) ) { + OrderBuy ob = BuyOrders.get(buyLevel); + long buyLevelQty = ob.getQuantity(); + + logger.debug("High Buy level Qty: " + buyLevelQty); + + if ( buyLevelQty <= qty ) { + + BuyOrders.remove(buyLevel); + listener.deleteOrderLevel(symbol, ob); + + return buyLevelQty; + } else { + BuyOrders.get(buyLevel).remove(newOrder); + listener.orderLevelChange(symbol, ob); + } + } + + return qty; + } + + private void deleteDueToRefPriceChng() { + double buyLevel = 0.0; + double sellLevel = 0.0; + Set buyKeys = BuyOrders.keySet(); + Set sellKeys = SellOrders.keySet(); + Iterator iterator = buyKeys.iterator(); + + while (iterator.hasNext()) { + buyLevel = (Double)(iterator.next()).doubleValue(); + logger.debug("deleteDueToRefPrice: " + buyLevel + " ... "); + if ( buyLevel < (this.referencePrice*0.75) ) { + OrderBase ob = BuyOrders.get(buyLevel); + iterator.remove(); + listener.deleteOrderLevel(symbol, ob); + + logger.debug(" ... deleted."); + } else if ( buyLevel > (this.referencePrice*1.25) ) { + OrderBase ob = BuyOrders.get(buyLevel); + iterator.remove(); + listener.deleteOrderLevel(symbol, ob); + + logger.debug(" ... deleted."); + } + } + + iterator = sellKeys.iterator(); + + while (iterator.hasNext()) { + sellLevel = (Double)(iterator.next()).doubleValue(); + if ( sellLevel < (this.referencePrice*0.75) ) { + OrderBase ob = SellOrders.get(sellLevel); + iterator.remove(); + listener.deleteOrderLevel(symbol,ob); + } else if ( sellLevel > (this.referencePrice*1.25) ) { + OrderBase ob = SellOrders.get(sellLevel); + iterator.remove(); + listener.deleteOrderLevel(symbol, ob); + } + } + + return ; + } + + private boolean newExecution(long execQty, double price) { + + if ( ExecList.size() > Constants.TRADING_DAY_LASTING) { + // The duration of the trading day depends on the number of deals!?!?!?!? + // Ok, it's just a demo. + + this.tradingPhase = "End Of Day"; + listener.newExecution(symbol); + + this.myGenerator.stopSimulation(false); + ExecList.clear(); + + BuyOrders.clear(); + SellOrders.clear(); + + listener.endOfDay(symbol); + + logger.info("For " + symbol + " starts overnight phase."); + + this.myGenerator.overnight(); + + return false; + } else { + if ( ExecList.isEmpty() ) { + this.tradingPhase = "Trading"; + } + Execution exec = new Execution(execQty, price, new Date(System.currentTimeMillis())); + + logger.debug("New execution for " + symbol + ": " + execQty + " x" + price); + + this.last = exec.getTs(); + this.lastPrice = exec.getPrice(); + this.lastQty = exec.getQty(); + ExecList.add(exec); + + + this.myGenerator.setBasePrice(price); + + if ( ((this.referencePrice*1.15) < price ) || ((this.referencePrice*0.85) > price ) ) { + // Update reference price. + this.referencePrice = price; + + // Delete all trading proposals out of range. + deleteDueToRefPriceChng(); + } + + listener.newExecution(symbol); + + return true; + } + + } +}