├── .gitignore
├── README.md
├── assets
├── FIXToMatchingEngineFlows0MQ.png
├── basic.jpg
└── complex.jpg
├── docs
├── Fix to Matching Cluster Architecture.md
└── SettingUpDevEnvironment.md
├── libs
├── jzmq
│ └── jzmq
│ │ └── 3.3.0
│ │ ├── jzmq-3.3.0-sources.jar
│ │ ├── jzmq-3.3.0-sources.jar.md5
│ │ ├── jzmq-3.3.0-sources.jar.sha1
│ │ ├── jzmq-3.3.0.jar
│ │ ├── jzmq-3.3.0.jar.md5
│ │ ├── jzmq-3.3.0.jar.sha1
│ │ ├── jzmq-3.3.0.pom
│ │ ├── jzmq-3.3.0.pom.md5
│ │ └── jzmq-3.3.0.pom.sha1
├── org
│ └── quickfixj
│ │ ├── quickfixj-core
│ │ ├── 1.5.2
│ │ │ ├── quickfixj-core-1.5.2-sources.jar
│ │ │ ├── quickfixj-core-1.5.2.jar
│ │ │ └── quickfixj-core-1.5.2.pom
│ │ └── maven-metadata-local.xml
│ │ └── quickfixj-msg-fix42
│ │ ├── 1.5.2
│ │ ├── quickfixj-msg-fix42-1.5.2.jar
│ │ └── quickfixj-msg-fix42-1.5.2.pom
│ │ └── maven-metadata-local.xml
└── reactive4java
│ └── reactive4java
│ ├── 0.96.2
│ ├── _maven.repositories
│ ├── reactive4java-0.96.2.jar
│ └── reactive4java-0.96.2.pom
│ └── maven-metadata-local.xml
├── pom.xml
├── script
└── rt-linux
│ ├── .config
│ ├── build-rt-kernel
│ ├── build-utils
│ └── install-rt-kernel
└── src
├── main
├── java
│ └── com
│ │ └── euronextclone
│ │ ├── IndicativeMatchPriceCalculator.java
│ │ ├── MatchingUnit.java
│ │ ├── Order.java
│ │ ├── OrderBook.java
│ │ ├── OrderEntry.java
│ │ ├── OrderSide.java
│ │ ├── OrderType.java
│ │ ├── Trade.java
│ │ ├── TradingMode.java
│ │ ├── TradingPhase.java
│ │ ├── engine
│ │ ├── MatchingEngine.java
│ │ └── ValueEvent.java
│ │ ├── fix
│ │ ├── FixAdapter.java
│ │ ├── client
│ │ │ ├── FixClient.java
│ │ │ ├── FixClientApp.java
│ │ │ ├── OrderBuilder.java
│ │ │ └── commands
│ │ │ │ ├── ClientCommand.java
│ │ │ │ ├── PlaceLimitOrder.java
│ │ │ │ └── PlaceMarketOrder.java
│ │ └── server
│ │ │ ├── FixServer.java
│ │ │ └── FixServerApp.java
│ │ ├── framework
│ │ ├── Action.java
│ │ └── Factory.java
│ │ ├── messaging
│ │ ├── Publisher.java
│ │ └── Subscriber.java
│ │ └── ordertypes
│ │ ├── Limit.java
│ │ ├── Market.java
│ │ ├── MarketToLimit.java
│ │ ├── Peg.java
│ │ └── PegWithLimit.java
└── resources
│ ├── FixBrokerA.cfg
│ ├── FixBrokerB.cfg
│ ├── FixServer.cfg
│ └── logback.xml
└── test
├── java
└── com
│ └── euronextclone
│ ├── ContinuousMatchingStepDefinitions.java
│ ├── MatchingUnitStepDefinitions.java
│ ├── RunAllCukesTest.java
│ ├── RunFocusCukesTest.java
│ └── World.java
└── resources
└── com
└── euronextclone
├── Euronext - market-to-limit-orders.feature
├── Euronext - pure market order.feature
├── Euronext - the peg orders.feature
├── _intellij-support.feature
├── auction-market-order.feature
├── basic-order-book-construction.feature
├── indicative-market-price.feature
└── pure-market-orders.feature
/.gitignore:
--------------------------------------------------------------------------------
1 | # Java
2 | *.class
3 |
4 | # OS
5 | .DS_Store
6 |
7 | # Maven
8 | target/
9 |
10 | # IDEA
11 | *.iml
12 | .idea/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Euronext Clone
2 | Inspired by github Price-Time Matching [Engine](https://gist.github.com/2855852), based on Euronext documentation publically available, and the madness of too many years in finance:
3 |
4 | * [The Peg Orders](http://www.euronext.com/fic/000/041/609/416094.pdf)
5 | * [Pure Market Order](http://www3.production.euronext.com/fic/000/041/480/414808.pdf)
6 | * [Auction](http://www.nyse.com/pdfs/5653_NYSEArca_Auctions.pdf)
7 | * [The Market to limit order](http://www3.production.euronext.com/fic/000/041/480/414806.pdf)
8 | * [Trading](https://europeanequities.nyx.com/en/trading/continuous-trading-process)
9 | * [The Stop Order](http://www3.production.euronext.com/fic/000/041/480/414809.pdf)
10 | * [Stop Order](http://www.euronext.com/fic/000/010/550/105509.pdf)
11 | * [IMP/TOP calculation](http://www.asx.com.au/products/calculate-open-close-prices.htm)
12 |
13 | ## Quick Links
14 | [Setting up dev environment](https://github.com/mattdavey/EuronextClone/blob/master/docs/SettingUpDevEnvironment.md)
15 |
16 | [FIX to matching eninge flows over 0MQ](https://github.com/mattdavey/EuronextClone/blob/master/docs/Fix%20to%20Matching%20Cluster%20Architecture.md)
17 |
18 | ## History
19 | The project initially started in the distant past as a Proof Of Concept (PoC) (see below) mainly aimed at attempting to model the data flows of a matching server in Java.
20 |
21 | Over time, work and thought has been put in to add ancillary services around the core order book with the aim of moving towards the high level architecture detailed below, offering Master/Slave with heart-beating from a resiliance perspective.
22 |
23 | It is hoped that at some point in the future performance numbers can be provided on suitable hardware to allow appropriate tuning and improve the architecture from a latency perspective.
24 |
25 | ## Proof Of Concept (PoC)
26 | Primarily this PoC architecture is aimed at joining all the dots together to ensure a client can submit an FIX order, and receive appropriate ExecutionReports
27 |
28 | 
29 |
30 | ## High Level Architecture
31 | The architecture below is based on the [LMAX](http://martinfowler.com/articles/lmax.html) architecture, but leveraging ZeroMQ instead of [Informatica](http://www.informatica.com/us/products/messaging/) Ultra Messaging. Further reading available [here](http://mdavey.wordpress.com/2012/08/01/financial-messaging-zeromq-random-reading/)
32 |
33 | .
34 |
--------------------------------------------------------------------------------
/assets/FIXToMatchingEngineFlows0MQ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/assets/FIXToMatchingEngineFlows0MQ.png
--------------------------------------------------------------------------------
/assets/basic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/assets/basic.jpg
--------------------------------------------------------------------------------
/assets/complex.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/assets/complex.jpg
--------------------------------------------------------------------------------
/docs/Fix to Matching Cluster Architecture.md:
--------------------------------------------------------------------------------
1 | # FIX to Matching Engine Cluster Architecture
2 |
3 | ## Notes
4 | * The following architecture diagram does not capture high availability and/or failover aspect of the overall design; only the main messaging flow is presented
5 | * FIX server communicates with the clients over standard FIX protocol (clients not shown on the diagram)
6 | * Each FIX server is responsoble for a configured subset of clients (brokers)
7 | * When orders come in over FIX from brokers they are published using multicast with the instrument name as the topic (routing key)
8 | * Each FIX server subscribes for execution updates related to its subset of brokers and relays them over FIX to the clients
9 | * Each matching engine (ME) is responsible for its range of instruments (configured statically or dynamically)
10 | * Each ME subscribes for order events related to its range of instruments on a multicast channel, published to by the FIX servers. Orders and other instructions are then routed (based on instrument) to their respective matching unit (MU) for execution/processing.
11 | * When trades and other events are generated by MU they are published by ME using multicast with the affected broker as the topic (routing key)
12 |
13 | ## Diagram
14 | 
--------------------------------------------------------------------------------
/docs/SettingUpDevEnvironment.md:
--------------------------------------------------------------------------------
1 | # Setting up development environment
2 |
3 | ## Prerequisites
4 | The only prerequisite required is that your system has [0MQ library](http://www.zeromq.org/) built and installed. 0MQ is a native dependecy used for messaging and is not automatically resolved by Maven build.
5 |
6 | Detailed instructions on how to build and install 0MQ on your system are available on [ZeroMQ site](http://www.zeromq.org/intro:get-the-software).
7 |
8 | ## Build
9 | First use [git](http://git-scm.com/) to clone this repo:
10 |
11 | git clone https://github.com/mattdavey/EuronextClone.git
12 | cd EuronextClone
13 |
14 | EuronextClone is built with [Maven](http://maven.apache.org/).
15 |
16 | mvn clean install
17 |
18 | The above will build the prototype and run all unit tests (cucumber-jvm over JUnit runner).
19 |
20 | ## Run
21 |
22 | There are currently two application available in the prototype: [FIX Server](#fix-server) and [FIX client](#fix-client). You can run a sinle copy of FIX server and one or more copies of FIX Client.
23 | This is changing soon though, so check this page often. We are moving to architecture where multiple FIX Servers may run, and we are moving to architecture where matching engine (ME)
24 | is a separate from FIX Server, and you can run multiple copies of those too.
25 |
26 | ### FIX Server
27 |
28 | FIX Server entry point is located at [FixServerApp](../src/main/java/com/euronextclone/fix/server/FixServerApp.java). FIX Server is preconfigured to accept connections from BROKER-A and BROKER-B clients.
29 | (See [FixServer.cfg](../src/main/resources/FixServer.cfg))
30 |
31 | To run FIX Server using Maven:
32 |
33 | mvn exec:exec -Prun-fix-server
34 |
35 | This assumes that native 0MQ library is available at **/usr/local/lib** unless you have overriden its location using **zmq.library.path** environment variable.
36 |
37 | If you are running FIX Server manually or from your favorite IDE make sure that **-Djava.library.path=...** is set to the location where you have installed 0MQ library to.
38 |
39 | ### FIX Client
40 |
41 | FIX Client entry point is located at [FixClientApp](../src/main/java/com/euronextclone/fix/client/FixClientApp.java).
42 |
43 | To run FIX Client using Maven (as broker A):
44 |
45 | mvn exec:exec -Prun-fix-client-a
46 |
47 | To run FIX Client using Maven (as broker B):
48 |
49 | mvn exec:exec -Prun-fix-client-b
50 |
51 | If you running FIX Client manually or from your favorite IDE then you can pass the following command line arguments:
52 |
53 | **--broker=BROKER**
54 |
55 | The broker argument configures the FIX client for a specific broker configuration.
56 | For example, passing **--broker=A** uses [FixBrokerA.cfg](../src/main/resources/FixBrokerA.cfg) configuration. And passing **--broker=B** uses [FixBrokerB.cfg](../src/main/resources/FixBrokerB.cfg) configuration.
57 |
58 | You can start multiple copies of FIX Client, with different **"--broker"** configured. When the FIX client starts it is ready to accept user's input.
59 |
60 | * typing "**q**" terminates the FIX Client
61 | * typing "**h**" lists all available commands
62 |
63 | The following commands are currently supported:
64 |
65 | #### Place Limit Order
66 |
67 | buy MSFT 10@34
68 |
69 | #### Place Market Order
70 |
71 | sell MSFT 5
72 |
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0-sources.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0-sources.jar
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0-sources.jar.md5:
--------------------------------------------------------------------------------
1 | c1b94f9cc737fa3d24890efc0bc6e309
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0-sources.jar.sha1:
--------------------------------------------------------------------------------
1 | 4fe79f286ae37ea360d54adf965371b521166b4d
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0.jar
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0.jar.md5:
--------------------------------------------------------------------------------
1 | 4d856c82d70062f3b1bece3963b93f1d
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0.jar.sha1:
--------------------------------------------------------------------------------
1 | d6df4bda6ade31111fa4de7d2002b6f2a3115a8d
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | jzmq
6 | jzmq
7 | 3.3.0
8 | POM was created from install:install-file
9 |
10 |
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0.pom.md5:
--------------------------------------------------------------------------------
1 | 954a7689ea6b8f1d836336f4ca8523f2
--------------------------------------------------------------------------------
/libs/jzmq/jzmq/3.3.0/jzmq-3.3.0.pom.sha1:
--------------------------------------------------------------------------------
1 | fab405161cd464cb338dc4b392dba34f0079a8ea
--------------------------------------------------------------------------------
/libs/org/quickfixj/quickfixj-core/1.5.2/quickfixj-core-1.5.2-sources.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/libs/org/quickfixj/quickfixj-core/1.5.2/quickfixj-core-1.5.2-sources.jar
--------------------------------------------------------------------------------
/libs/org/quickfixj/quickfixj-core/1.5.2/quickfixj-core-1.5.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/libs/org/quickfixj/quickfixj-core/1.5.2/quickfixj-core-1.5.2.jar
--------------------------------------------------------------------------------
/libs/org/quickfixj/quickfixj-core/1.5.2/quickfixj-core-1.5.2.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | org.quickfixj
6 | quickfixj-core
7 | 1.5.2
8 | POM was created from install:install-file
9 |
10 |
--------------------------------------------------------------------------------
/libs/org/quickfixj/quickfixj-core/maven-metadata-local.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | org.quickfixj
4 | quickfixj-core
5 |
6 | 1.5.2
7 |
8 | 1.5.2
9 |
10 | 20120325210829
11 |
12 |
13 |
--------------------------------------------------------------------------------
/libs/org/quickfixj/quickfixj-msg-fix42/1.5.2/quickfixj-msg-fix42-1.5.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/libs/org/quickfixj/quickfixj-msg-fix42/1.5.2/quickfixj-msg-fix42-1.5.2.jar
--------------------------------------------------------------------------------
/libs/org/quickfixj/quickfixj-msg-fix42/1.5.2/quickfixj-msg-fix42-1.5.2.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | org.quickfixj
6 | quickfixj-msg-fix42
7 | 1.5.2
8 | POM was created from install:install-file
9 |
10 |
--------------------------------------------------------------------------------
/libs/org/quickfixj/quickfixj-msg-fix42/maven-metadata-local.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | org.quickfixj
4 | quickfixj-msg-fix42
5 |
6 | 1.5.2
7 |
8 | 1.5.2
9 |
10 | 20120325210948
11 |
12 |
13 |
--------------------------------------------------------------------------------
/libs/reactive4java/reactive4java/0.96.2/_maven.repositories:
--------------------------------------------------------------------------------
1 | #NOTE: This is an internal implementation file, its format can be changed without prior notice.
2 | #Tue Jul 10 17:18:32 EDT 2012
3 | reactive4java-0.96.2.jar>=
4 | reactive4java-0.96.2.pom>=
5 |
--------------------------------------------------------------------------------
/libs/reactive4java/reactive4java/0.96.2/reactive4java-0.96.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdavey/EuronextClone/0ae7d79a2c3832b8404d3f09709074c3f0df1657/libs/reactive4java/reactive4java/0.96.2/reactive4java-0.96.2.jar
--------------------------------------------------------------------------------
/libs/reactive4java/reactive4java/0.96.2/reactive4java-0.96.2.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | reactive4java
6 | reactive4java
7 | 0.96.2
8 | POM was created from install:install-file
9 |
10 |
--------------------------------------------------------------------------------
/libs/reactive4java/reactive4java/maven-metadata-local.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | reactive4java
4 | reactive4java
5 |
6 | 0.96.2
7 |
8 | 0.96.2
9 |
10 | 20120710211832
11 |
12 |
13 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | 4.0.0
6 |
7 | com.mattdavey.matching
8 | EuronextClone
9 | 1.0-SNAPSHOT
10 | jar
11 | Euronext Clone
12 |
13 |
14 | 1.0.11
15 |
16 | env.zmq.library.path
17 |
18 |
19 |
20 |
21 | reactive4java
22 | reactive4java
23 | 0.96.2
24 |
25 |
26 | com.google.guava
27 | guava
28 | 12.0.1
29 |
30 |
31 |
32 | info.cukes
33 | cucumber-java
34 | ${cucumber-jvm.version}
35 | test
36 |
37 |
38 | info.cukes
39 | cucumber-picocontainer
40 | ${cucumber-jvm.version}
41 | test
42 |
43 |
44 | info.cukes
45 | cucumber-junit
46 | ${cucumber-jvm.version}
47 | test
48 |
49 |
50 | junit
51 | junit
52 | 4.13.1
53 | test
54 |
55 |
56 | org.hamcrest
57 | hamcrest-all
58 | 1.3
59 | test
60 |
61 |
62 |
63 |
64 | org.quickfixj
65 | quickfixj-core
66 | 1.5.2
67 |
68 |
69 | org.quickfixj
70 | quickfixj-msg-fix42
71 | 1.5.2
72 |
73 |
74 | org.apache.mina
75 | mina-core
76 | 1.1.0
77 |
78 |
79 |
80 |
81 | org.slf4j
82 | slf4j-api
83 | 1.6.3
84 |
85 |
86 | ch.qos.logback
87 | logback-classic
88 | 1.0.0
89 | runtime
90 |
91 |
92 |
93 |
94 | com.googlecode.disruptor
95 | disruptor
96 | 2.10.1
97 |
98 |
99 |
100 |
101 | jzmq
102 | jzmq
103 | 3.3.0
104 |
105 |
106 |
107 |
108 | com.dyuproject.protostuff
109 | protostuff-runtime
110 | 1.0.7
111 |
112 |
113 | com.dyuproject.protostuff
114 | protostuff-core
115 | 1.0.7
116 |
117 |
118 |
119 |
120 |
121 |
122 | local-libs
123 | file://${basedir}/libs
124 |
125 |
126 |
127 |
128 |
129 |
130 | default-zmq-library-path
131 |
132 |
133 | !zmq.library.path
134 |
135 |
136 |
137 |
138 | /usr/local/lib
139 |
140 |
141 |
142 |
143 | run-fix-server
144 |
145 |
146 |
147 | org.codehaus.mojo
148 | exec-maven-plugin
149 | 1.2.1
150 |
151 |
152 |
153 | exec
154 |
155 |
156 |
157 |
158 | java
159 |
160 | -classpath
161 |
162 | -Djava.library.path=${zmq.library.path}
163 | com.euronextclone.fix.server.FixServerApp
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | run-fix-client-a
174 |
175 |
176 |
177 | org.codehaus.mojo
178 | exec-maven-plugin
179 | 1.2.1
180 |
181 |
182 |
183 | exec
184 |
185 |
186 |
187 |
188 | java
189 |
190 | -classpath
191 |
192 | com.euronextclone.fix.client.FixClientApp
193 | --broker=A
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 | run-fix-client-b
203 |
204 |
205 |
206 | org.codehaus.mojo
207 | exec-maven-plugin
208 | 1.2.1
209 |
210 |
211 |
212 | exec
213 |
214 |
215 |
216 |
217 | java
218 |
219 | -classpath
220 |
221 | com.euronextclone.fix.client.FixClientApp
222 | --broker=B
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
--------------------------------------------------------------------------------
/script/rt-linux/build-rt-kernel:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | EC2_KEY=~/.ssh/[your_key].pem
4 |
5 | . ./build-utils
6 |
7 | echo 'Spawning new ec2 instance...'
8 | EC2_HOST=`spawn-instance "matching-engine" 20 m1.xlarge`
9 | [ $? != 0 ] && echo 'spawning ec2 instance failed' && exit 1
10 |
11 | echo "Copying installation scripts to ${EC2_HOST}..."
12 | scp -i ${EC2_KEY} * .config root@${EC2_HOST}:/root/ >> /dev/null 2>&1
13 |
14 | echo "Installing ${EC2_HOST}..."
15 | ssh -i ${EC2_KEY} root@${EC2_HOST} './install-rt-kernel'
16 | [ $? != 0 ] && echo 'installing ec2 instance failed' && exit 1
17 |
18 | echo 'Build is done.'
19 |
--------------------------------------------------------------------------------
/script/rt-linux/build-utils:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function create-group {
4 | GROUP=$1
5 | DESCRIPTION=$2
6 |
7 | ec2-create-group "${GROUP}" -d "${DESCRIPTION}"
8 | ec2-authorize "${GROUP}" -P tcp -p 22 -s 0.0.0.0/0
9 | }
10 |
11 | function spawn-instance {
12 | SECURITY_GROUPS=$1
13 | for group in ${SECURITY_GROUPS}; do
14 | GRP_OPTIONS="${GRP_OPTIONS} -g ${group}"
15 | done
16 | ROOT_DEVICE_SIZE=$2
17 | INSTANCE_TYPE=${3:-t1.micro}
18 |
19 | RESERVATION=`ec2-run-instances ami-41d00528 ${GRP_OPTIONS} -t ${INSTANCE_TYPE} -k gsmarquee -b /dev/sda1=:${ROOT_DEVICE_SIZE}:true \
20 | | grep INSTANCE`
21 | [ $? != 0 ] && echo 'ec2-run-instances failed' >&2 && return 1
22 | INSTANCE=`echo "${RESERVATION}" | awk '{print $2}'`
23 |
24 | while [[ -z "${EC2_HOST}" ]]; do
25 | EC2_HOST=`ec2-describe-instances ${INSTANCE} --filter instance-state-name=running \
26 | | grep '^INSTANCE' | awk '{print $4}'`
27 | [ $? != 0 ] && echo 'ec2-describe-instances failed' >&2 && return 1
28 | done
29 |
30 | while [[ true ]]; do
31 | ssh -o StrictHostKeyChecking=no -i ~/.ssh/gsmarquee.pem root@${EC2_HOST} 'echo OK >> /dev/null' >> /dev/null 2>&1
32 | [ $? = 0 ] && break
33 | sleep 1
34 | done
35 |
36 | echo "${EC2_HOST}"
37 | return 0
38 | }
39 |
40 | function get-instance-field {
41 | GROUP_NAME=$1
42 | FIELD_POS=$2
43 |
44 | ec2-describe-instances --show-empty-fields \
45 | --filter instance-state-name=running \
46 | --filter group-name=${GROUP_NAME} \
47 | | grep INSTANCE | cut -f ${FIELD_POS}
48 | }
49 |
50 | function get-public-dns {
51 | SECURITY_GROUPS=$1
52 | for group in ${SECURITY_GROUPS}; do
53 | get-instance-field ${group} 4
54 | done | sort -u
55 | }
56 |
57 | function get-private-dns {
58 | SECURITY_GROUPS=$1
59 | for group in ${SECURITY_GROUPS}; do
60 | get-instance-field ${group} 5
61 | done | sort -u
62 | }
63 |
64 | function build-uri {
65 | HOSTS=$1
66 | PORT=$2
67 | PROTOCOL=$3
68 | URI=''
69 |
70 | for host in ${HOSTS}; do
71 | if [[ -n ${URI} ]]; then URI=${URI}", "; fi
72 | URI=$URI$PROTOCOL${host}
73 | if [[ -n ${PORT} ]]; then URI=${URI}:${PORT}; fi
74 | done
75 |
76 | echo ${URI}
77 | }
78 |
--------------------------------------------------------------------------------
/script/rt-linux/install-rt-kernel:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | LOG=/var/log/matching-engine-install-rt-kernel.log
4 | KERNEL_VERSION='2.6.33.9'
5 | KERNEL_SOURCE="linux-${KERNEL_VERSION}"
6 | RT_PATCH_REV='31'
7 |
8 | echo 'Resizing the root device...'
9 | resize2fs /dev/xvde1 >> $LOG 2>&1
10 | [ $? != 0 ] && echo 'resizing root device failed' && exit 1
11 |
12 | echo 'Installing gcc...'
13 | yum -y install gcc >> $LOG 2>&1
14 | [ $? != 0 ] && echo 'yum gcc failed' && exit 1
15 |
16 | echo 'Downloding kernel...'
17 | wget http://www.kernel.org/pub/linux/kernel/v2.6/longterm/v2.6.33/linux-${KERNEL_VERSION}.tar.bz2 >> $LOG 2>&1
18 | [ $? != 0 ] && echo 'downloading linux kernel sources failed' && exit 1
19 |
20 | echo 'Downloding RT kernel patch...'
21 | wget http://www.kernel.org/pub/linux/kernel/projects/rt/2.6.33/patch-${KERNEL_VERSION}-rt${RT_PATCH_REV}.bz2 >> $LOG 2>&1
22 | [ $? != 0 ] && echo 'downloading linux kernel RT patch failed' && exit 1
23 |
24 | echo 'Patching the kernel...'
25 | tar xfj linux-${KERNEL_VERSION}.tar.bz2 >> $LOG 2>&1
26 | [ $? != 0 ] && echo 'un-tar on linux kernel sources failed' && exit 1
27 | cd ${KERNEL_SOURCE} >> $LOG 2>&1
28 | [ $? != 0 ] && echo 'cd to linux kernel sources failed' && exit 1
29 | bzcat ../patch-${KERNEL_VERSION}-rt${RT_PATCH_REV}.bz2 | patch -p1 >> $LOG 2>&1
30 | [ $? != 0 ] && echo 'patching linux kernel sources with RT failed' && exit 1
31 |
32 | echo 'Deploying kernel config...'
33 | cp ../.config . >> $LOG 2>&1
34 | [ $? != 0 ] && echo 'Deploying kernel config failed' && exit 1
35 |
36 | echo 'Building and installing the kernel...'
37 | make >> $LOG 2>&1
38 | [ $? != 0 ] && echo 'Building the kernel failed' && exit 1
39 | make modules >> $LOG 2>&1
40 | [ $? != 0 ] && echo 'Building the modules failed' && exit 1
41 | make modules_install >> $LOG 2>&1
42 | [ $? != 0 ] && echo 'Installing the modules failed' && exit 1
43 | make install >> $LOG 2>&1
44 | [ $? != 0 ] && echo 'Installing the kernel failed' && exit 1
45 | cp .config /boot/config-${KERNEL_VERSION}-rt${RT_PATCH_REV} >> $LOG 2>&1
46 | [ $? != 0 ] && echo 'Installing the kernel config failed' && exit 1
47 |
48 | echo 'Done installing.'
49 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/IndicativeMatchPriceCalculator.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class IndicativeMatchPriceCalculator
7 | {
8 | private final Double referencePrice;
9 | private final OrderBook sellBook;
10 | private final OrderBook buyBook;
11 |
12 | public IndicativeMatchPriceCalculator(final Double referencePrice,
13 | final OrderBook sellBook,
14 | final OrderBook buyBook)
15 | {
16 | this.referencePrice = referencePrice;
17 | this.sellBook = sellBook;
18 | this.buyBook = buyBook;
19 | }
20 |
21 | public final Double get()
22 | {
23 | List calculations = getMaximumExecutableVolume();
24 | if (calculations == null)
25 | {
26 | return null;
27 | }
28 | if (calculations.size() == 1)
29 | {
30 | return calculations.get(0).getPrice();
31 | }
32 |
33 | calculations = getMinimumSurplus(calculations);
34 | if (calculations == null)
35 | {
36 | return null;
37 | }
38 | if (calculations.size() == 1)
39 | {
40 | return calculations.get(0).getPrice();
41 | }
42 |
43 | calculations = getMarketPressure(calculations);
44 | if (calculations == null)
45 | {
46 | return null;
47 | }
48 | if (calculations.size() == 1)
49 | {
50 | return calculations.get(0).getPrice();
51 | }
52 |
53 | return getReferencePrice(calculations);
54 | }
55 |
56 |
57 | private List getMaximumExecutableVolume()
58 | {
59 | return new ArrayList();
60 | }
61 |
62 | private List getMinimumSurplus(List calculations)
63 | {
64 | int minSurplus = Integer.MAX_VALUE;
65 | final List filtered = new ArrayList();
66 | for (final CalculationInfo ci : calculations)
67 | {
68 | final int localMinSurplus = Math.abs(ci.getMinimumSurplus());
69 | if (localMinSurplus < minSurplus)
70 | {
71 | minSurplus = localMinSurplus;
72 | filtered.clear();
73 | filtered.add(ci);
74 | }
75 | else if (localMinSurplus == minSurplus)
76 | {
77 | filtered.add(ci);
78 | }
79 | }
80 | return filtered;
81 | }
82 |
83 | private List getMarketPressure(List calculations)
84 | {
85 | CalculationInfo buyCalculationInfo = null;
86 | CalculationInfo sellCalculationInfo = null;
87 |
88 | for (final CalculationInfo ci : calculations)
89 | {
90 | final int surplus = ci.getMinimumSurplus();
91 | if (surplus > 0)
92 | {
93 | if (buyCalculationInfo == null || (buyCalculationInfo.getPrice() < ci.getPrice()))
94 | {
95 | buyCalculationInfo = ci;
96 | }
97 | }
98 | else if (surplus < 0)
99 | {
100 | if (buyCalculationInfo == null || (sellCalculationInfo.getPrice() > ci.getPrice()))
101 | {
102 | sellCalculationInfo = ci;
103 | }
104 | }
105 | //return earlier if we have market pressure on both sides (buy/sell)
106 | if (buyCalculationInfo != null && sellCalculationInfo != null)
107 | {
108 | return calculations;
109 | }
110 | }
111 |
112 | if (buyCalculationInfo != null)
113 | {
114 | final ArrayList c = new ArrayList();
115 | c.add(buyCalculationInfo);
116 | return c;
117 | }
118 |
119 | if (sellCalculationInfo != null)
120 | {
121 | final ArrayList c = new ArrayList();
122 | c.add(sellCalculationInfo);
123 | return c;
124 | }
125 | return calculations;
126 | }
127 |
128 | private double getReferencePrice(List calculations)
129 | {
130 | //get two prices
131 | CalculationInfo[] twoPrices = getTwoPrices(calculations);
132 |
133 |
134 | double max = Math.max(twoPrices[0].getPrice(), twoPrices[1].getPrice());
135 | double min = Math.min(twoPrices[0].getPrice(), twoPrices[1].getPrice());
136 |
137 | if (referencePrice == null)
138 | {
139 | return min;
140 | }
141 |
142 | if (referencePrice >= max)
143 | {
144 | return max;
145 | }
146 | if (referencePrice <= min)
147 | {
148 | return min;
149 | }
150 | return referencePrice;
151 | }
152 |
153 | private CalculationInfo[] getTwoPrices(final List calculations)
154 | {
155 | assert (calculations.size() > 1);
156 | //check all prices have minimum surplus zero
157 | boolean allZero = true;
158 | for (final CalculationInfo ci : calculations)
159 | {
160 | allZero = ci.getMinimumSurplus() == 0;
161 | if (!allZero)
162 | {
163 | break;
164 | }
165 | }
166 | if (allZero)
167 | {
168 | return new CalculationInfo[]{calculations.get(0),
169 | calculations.get(calculations.size() - 1)};
170 | }
171 |
172 | CalculationInfo first = calculations.get(0);
173 | CalculationInfo second = null;
174 | for (int i = 1; i < calculations.size(); ++i)
175 | {
176 | final CalculationInfo temp = calculations.get(i);
177 | //the items are orderd and all the items minimum surplus are the same value
178 | if (first.getMinimumSurplus() != temp.getMinimumSurplus())
179 | {
180 | second = temp;
181 | break;
182 | }
183 | first = temp;
184 | }
185 | return new CalculationInfo[]{first, second};
186 | }
187 |
188 | private final class CalculationInfo
189 | {
190 |
191 | private final double price;
192 | private final int cumulativeBuyQuantity;
193 | private final int cumulativeSellQuantity;
194 | private final int maxExecutableVolume;
195 |
196 | private CalculationInfo(final double price,
197 | final int cumulativeBuyQuantity,
198 | final int cumulativeSellQuantity,
199 | final int maxExecutableVolume)
200 | {
201 | this.price = price;
202 | this.cumulativeBuyQuantity = cumulativeBuyQuantity;
203 | this.cumulativeSellQuantity = cumulativeSellQuantity;
204 | this.maxExecutableVolume = maxExecutableVolume;
205 | }
206 |
207 |
208 | public double getPrice()
209 | {
210 | return price;
211 | }
212 |
213 | public int getCumulativeBuyQuantity()
214 | {
215 | return cumulativeBuyQuantity;
216 | }
217 |
218 | public int getCumulativeSellQuantity()
219 | {
220 | return cumulativeSellQuantity;
221 | }
222 |
223 | public int getMaxExecutableVolume()
224 | {
225 | return maxExecutableVolume;
226 | }
227 |
228 | public int getMinimumSurplus()
229 | {
230 | return cumulativeBuyQuantity - cumulativeSellQuantity;
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/MatchingUnit.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import com.euronextclone.ordertypes.Limit;
4 | import com.google.common.base.Function;
5 | import com.google.common.base.Optional;
6 | import com.google.common.base.Predicate;
7 | import com.google.common.collect.FluentIterable;
8 | import com.google.common.collect.Lists;
9 | import hu.akarnokd.reactive4java.reactive.DefaultObservable;
10 | import hu.akarnokd.reactive4java.reactive.Observable;
11 | import hu.akarnokd.reactive4java.reactive.Observer;
12 |
13 | import javax.annotation.Nonnull;
14 | import java.io.Closeable;
15 | import java.util.*;
16 |
17 | public class MatchingUnit implements Observable {
18 |
19 | private final OrderBook buyOrderBook;
20 | private final OrderBook sellOrderBook;
21 | private TradingPhase tradingPhase = TradingPhase.CoreContinuous;
22 | private TradingMode tradingMode = TradingMode.Continuous;
23 | private Double referencePrice;
24 | private Double indicativeMatchingPrice;
25 |
26 | /**
27 | * The observable helper.
28 | */
29 | private final DefaultObservable notifier = new DefaultObservable();
30 | private final String instrument;
31 |
32 | public MatchingUnit(String instrument) {
33 | this.instrument = instrument;
34 | buyOrderBook = new OrderBook();
35 | sellOrderBook = new OrderBook();
36 | }
37 |
38 | public void setReferencePrice(Double referencePrice) {
39 | this.referencePrice = referencePrice;
40 | }
41 |
42 | public Double getIndicativeMatchingPrice() {
43 |
44 | final List eligiblePrices = getListOfEligiblePrices();
45 | final List cumulativeBuy = getCumulativeQuantity(eligiblePrices, buyOrderBook, OrderSide.Buy);
46 | final List cumulativeSell = getCumulativeQuantity(eligiblePrices, sellOrderBook, OrderSide.Sell);
47 | final List totalTradeableVolume = getTotalTradeableVolume(eligiblePrices, cumulativeBuy, cumulativeSell);
48 |
49 | final List maximumExecutableVolume = determineMaximumExecutableValue(totalTradeableVolume);
50 | if (maximumExecutableVolume.size() == 1) {
51 | return maximumExecutableVolume.get(0).price;
52 | }
53 |
54 | final List minimumSurplus = establishMinimumSurplus(maximumExecutableVolume);
55 | if (minimumSurplus.size() == 1) {
56 | return minimumSurplus.get(0).price;
57 | }
58 |
59 | final Optional pressurePrice = ascertainWhereMarketPressureExists(minimumSurplus);
60 | if (pressurePrice.isPresent()) {
61 | return pressurePrice.get();
62 | }
63 |
64 | return consultReferencePrice(minimumSurplus);
65 | }
66 |
67 | private double consultReferencePrice(List minimumSurplus) {
68 | final FluentIterable minimumSurplusIterable = FluentIterable.from(minimumSurplus);
69 |
70 | if (!minimumSurplus.isEmpty()) {
71 | double minPotentialPrice;
72 | double maxPotentialPrice;
73 |
74 | if (minimumSurplusIterable.allMatch(VolumeAtPrice.NO_PRESSURE)) {
75 | minPotentialPrice = minimumSurplusIterable.first().get().price;
76 | maxPotentialPrice = minimumSurplusIterable.last().get().price;
77 | } else {
78 | minPotentialPrice = minimumSurplusIterable.filter(VolumeAtPrice.BUYING_PRESSURE).last().get().price;
79 | maxPotentialPrice = minimumSurplusIterable.filter(VolumeAtPrice.SELLING_PRESSURE).first().get().price;
80 | }
81 |
82 | if (referencePrice == null) {
83 | return minPotentialPrice;
84 | }
85 |
86 | if (referencePrice >= maxPotentialPrice) {
87 | return maxPotentialPrice;
88 | }
89 |
90 | if (referencePrice <= minPotentialPrice) {
91 | return minPotentialPrice;
92 | }
93 | }
94 |
95 | return referencePrice;
96 | }
97 |
98 | private List establishMinimumSurplus(List maximumExecutableVolume) {
99 |
100 | if (maximumExecutableVolume.isEmpty()) {
101 | return maximumExecutableVolume;
102 | }
103 |
104 | final int minSurplus = Collections.min(maximumExecutableVolume, VolumeAtPrice.ABSOLUTE_SURPLUS_COMPARATOR).getAbsoluteSurplus();
105 |
106 | FluentIterable minSurplusOnly = FluentIterable.from(maximumExecutableVolume).filter(new Predicate() {
107 | @Override
108 | public boolean apply(VolumeAtPrice input) {
109 | return input.getAbsoluteSurplus() == minSurplus;
110 | }
111 | });
112 |
113 | return minSurplusOnly.toImmutableList();
114 | }
115 |
116 | private List determineMaximumExecutableValue(List totalTradeableVolume) {
117 |
118 | if (totalTradeableVolume.isEmpty()) {
119 | return totalTradeableVolume;
120 | }
121 |
122 | final int maxVolume = Collections.max(totalTradeableVolume, VolumeAtPrice.TRADEABLE_VOLUME_COMPARATOR).getTradeableVolume();
123 |
124 | FluentIterable maxVolumeOnly = FluentIterable.from(totalTradeableVolume).filter(new Predicate() {
125 | @Override
126 | public boolean apply(VolumeAtPrice input) {
127 | return input.getTradeableVolume() == maxVolume;
128 | }
129 | });
130 |
131 | return maxVolumeOnly.toImmutableList();
132 | }
133 |
134 | public void setTradingMode(TradingMode tradingMode) {
135 | this.tradingMode = tradingMode;
136 | }
137 |
138 | public void setTradingPhase(TradingPhase tradingPhase) {
139 | this.tradingPhase = tradingPhase;
140 | }
141 |
142 | private static class VolumeAtPrice {
143 | private double price;
144 | private int buyVolume;
145 | private int sellVolume;
146 |
147 | public VolumeAtPrice(double price, int buyVolume, int sellVolume) {
148 | this.price = price;
149 | this.buyVolume = buyVolume;
150 | this.sellVolume = sellVolume;
151 | }
152 |
153 | public int getTradeableVolume() {
154 | return Math.min(buyVolume, sellVolume);
155 | }
156 |
157 | public int getSurplus() {
158 | return buyVolume - sellVolume;
159 | }
160 |
161 | public int getAbsoluteSurplus() {
162 | return Math.abs(getSurplus());
163 | }
164 |
165 | public static final Comparator TRADEABLE_VOLUME_COMPARATOR = new Comparator() {
166 |
167 | @Override
168 | public int compare(VolumeAtPrice volumeAtPrice1, VolumeAtPrice volumeAtPrice2) {
169 | Integer tradeableVolume1 = volumeAtPrice1.getTradeableVolume();
170 | Integer tradeableVolume2 = volumeAtPrice2.getTradeableVolume();
171 | return tradeableVolume1.compareTo(tradeableVolume2);
172 | }
173 | };
174 |
175 | public static final Comparator ABSOLUTE_SURPLUS_COMPARATOR = new Comparator() {
176 |
177 | @Override
178 | public int compare(VolumeAtPrice volumeAtPrice1, VolumeAtPrice volumeAtPrice2) {
179 | Integer surplus1 = volumeAtPrice1.getAbsoluteSurplus();
180 | Integer surplus2 = volumeAtPrice2.getAbsoluteSurplus();
181 | return surplus1.compareTo(surplus2);
182 | }
183 | };
184 |
185 | public static Predicate super VolumeAtPrice> BUYING_PRESSURE = new Predicate() {
186 | @Override
187 | public boolean apply(final VolumeAtPrice input) {
188 | return input.getSurplus() > 0;
189 | }
190 | };
191 |
192 | public static Predicate super VolumeAtPrice> SELLING_PRESSURE = new Predicate() {
193 | @Override
194 | public boolean apply(final VolumeAtPrice input) {
195 | return input.getSurplus() < 0;
196 | }
197 | };
198 |
199 | public static Predicate super VolumeAtPrice> NO_PRESSURE = new Predicate() {
200 | @Override
201 | public boolean apply(final VolumeAtPrice input) {
202 | return input.getSurplus() == 0;
203 | }
204 | };
205 | }
206 |
207 | private Optional ascertainWhereMarketPressureExists(List minimumSurplus) {
208 |
209 | if (!minimumSurplus.isEmpty()) {
210 | final FluentIterable minimumSurplusIterable = FluentIterable.from(minimumSurplus);
211 |
212 | final boolean buyingPressure = minimumSurplusIterable.allMatch(VolumeAtPrice.BUYING_PRESSURE);
213 | if (buyingPressure) {
214 | return Optional.of(minimumSurplusIterable.last().get().price);
215 | }
216 |
217 | final boolean sellingPressure = minimumSurplusIterable.allMatch(VolumeAtPrice.SELLING_PRESSURE);
218 | if (sellingPressure) {
219 | return Optional.of(minimumSurplusIterable.first().get().price);
220 | }
221 | }
222 |
223 | return Optional.absent();
224 | }
225 |
226 | private List getCumulativeQuantity(
227 | final List eligiblePrices,
228 | final OrderBook book,
229 | final OrderSide side) {
230 |
231 | final List quantities = new ArrayList(eligiblePrices.size());
232 |
233 | final Iterable priceIterable = side == OrderSide.Sell ?
234 | eligiblePrices :
235 | Lists.reverse(eligiblePrices);
236 | final ListIterator current = book.getOrders().listIterator();
237 | int cumulative = 0;
238 |
239 | for (final double price : priceIterable) {
240 | while (current.hasNext()) {
241 | final Order order = current.next();
242 | final OrderType orderType = order.getOrderType();
243 |
244 | Double orderPrice = orderType.price(side, book.getBestLimit());
245 | if (canTrade(side, orderPrice, price)) {
246 | cumulative += order.getQuantity();
247 | } else {
248 | current.previous();
249 | break;
250 | }
251 | }
252 | quantities.add(cumulative);
253 | }
254 |
255 | return side == OrderSide.Sell ? quantities : Lists.reverse(quantities);
256 | }
257 |
258 | private boolean canTrade(OrderSide orderSide, Double orderPrice, double testPrice) {
259 |
260 | if (orderPrice == null) {
261 | return true;
262 | }
263 |
264 | if (orderSide == OrderSide.Buy) {
265 | return testPrice <= orderPrice;
266 | }
267 |
268 | return testPrice >= orderPrice;
269 | }
270 |
271 | private List getTotalTradeableVolume(
272 | final List eligiblePrices,
273 | final List cumulativeBuy,
274 | final List cumulativeSell) {
275 |
276 | final List tradeableVolume = new ArrayList();
277 | final Iterator priceIterator = eligiblePrices.iterator();
278 | final Iterator buy = cumulativeBuy.iterator();
279 | final Iterator sell = cumulativeSell.iterator();
280 |
281 | while (priceIterator.hasNext()) {
282 | final double price = priceIterator.next();
283 | final int buyVolume = buy.next();
284 | final int sellVolume = sell.next();
285 | tradeableVolume.add(new VolumeAtPrice(price, buyVolume, sellVolume));
286 | }
287 |
288 | return tradeableVolume;
289 | }
290 |
291 | private List getListOfEligiblePrices() {
292 |
293 | final TreeSet prices = new TreeSet();
294 | if (referencePrice != null) {
295 | prices.add(referencePrice);
296 | }
297 | prices.addAll(getLimitPrices(buyOrderBook));
298 | prices.addAll(getLimitPrices(sellOrderBook));
299 |
300 | return new ArrayList(prices);
301 | }
302 |
303 | private Collection extends Double> getLimitPrices(final OrderBook book) {
304 |
305 | return FluentIterable.from(book.getOrders()).filter(new Predicate() {
306 | @Override
307 | public boolean apply(Order input) {
308 | return input.getOrderType().providesLimit();
309 | }
310 | }).transform(new Function() {
311 | @Override
312 | public Double apply(Order input) {
313 | return input.getOrderType().getLimit();
314 | }
315 | }).toImmutableSet();
316 | }
317 |
318 | public void auction() {
319 | tradingPhase = TradingPhase.CoreAuction;
320 | indicativeMatchingPrice = getIndicativeMatchingPrice();
321 |
322 | while (!buyOrderBook.getOrders().isEmpty()) {
323 | if (!tryMatchOrder(buyOrderBook.getOrders().get(0))) {
324 | break;
325 | }
326 | }
327 |
328 | upgradeMarketToLimitOrders(buyOrderBook);
329 | upgradeMarketToLimitOrders(sellOrderBook);
330 | }
331 |
332 | private void upgradeMarketToLimitOrders(OrderBook orderBook) {
333 |
334 | if (tradingMode == TradingMode.Continuous) {
335 |
336 | List orders = orderBook.getOrders();
337 | List mtlOrders = FluentIterable.from(orders).filter(new Predicate() {
338 | @Override
339 | public boolean apply(Order input) {
340 | return input.getOrderType().convertsToLimit();
341 | }
342 | }).toImmutableList();
343 | orders.removeAll(mtlOrders);
344 |
345 | for (Order mtlOrder : mtlOrders) {
346 | Order limit = mtlOrder.convertTo(new Limit(indicativeMatchingPrice));
347 | orderBook.add(limit);
348 | }
349 | }
350 | }
351 |
352 | public List getOrders(final OrderSide side) {
353 | return getBook(side).getOrders();
354 | }
355 |
356 | public void addOrder(final OrderEntry orderEntry) {
357 | final Order order = new Order(orderEntry.getOrderId(), orderEntry.getBroker(), orderEntry.getQuantity(), orderEntry.getOrderType(), orderEntry.getSide());
358 | final OrderSide side = orderEntry.getSide();
359 | boolean topOfTheBook = getBook(side).add(order) == 0;
360 |
361 | if (tradingPhase == TradingPhase.CoreContinuous && topOfTheBook) {
362 | tryMatchOrder(order);
363 | }
364 | }
365 |
366 | private boolean tryMatchOrder(final Order newOrder) {
367 | final OrderSide side = newOrder.getSide();
368 | final OrderBook book = getBook(side);
369 | final int startQuantity = newOrder.getQuantity();
370 | final OrderBook counterBook = getCounterBook(side);
371 |
372 | Double newOrderPrice = newOrder.getOrderType().providesLimit() ? newOrder.getOrderType().getLimit() : null;
373 |
374 | List toRemove = new ArrayList();
375 | List toAdd = new ArrayList();
376 |
377 | Order currentOrder = newOrder;
378 |
379 | for (final Order order : counterBook.getOrders()) {
380 |
381 | // Determine the price at which the trade happens
382 | final Double bookOrderPrice = order.getOrderType().price(order.getSide(), counterBook.getBestLimit());
383 |
384 | Double tradePrice = determineTradePrice(newOrderPrice, bookOrderPrice, order.getSide(), counterBook.getBestLimit());
385 | if (tradePrice == null) {
386 | break;
387 | }
388 |
389 | if (tradingPhase == TradingPhase.CoreAuction) {
390 | tradePrice = indicativeMatchingPrice;
391 | }
392 |
393 | // Determine the amount to trade
394 | int tradeQuantity = determineTradeQuantity(currentOrder, order);
395 |
396 | // Trade
397 | currentOrder.decrementQuantity(tradeQuantity);
398 | order.decrementQuantity(tradeQuantity);
399 | generateTrade(currentOrder, order, tradeQuantity, tradePrice);
400 |
401 | if (order.getQuantity() == 0) {
402 | toRemove.add(order);
403 | } else {
404 | if (order.getOrderType().convertsToLimit()) {
405 | toRemove.add(order);
406 | toAdd.add(order.convertTo(new Limit(tradePrice)));
407 | }
408 | break;
409 | }
410 |
411 | if (currentOrder.getQuantity() == 0) {
412 | break;
413 | }
414 |
415 | if (currentOrder.getOrderType().convertsToLimit()) {
416 | book.remove(currentOrder);
417 | currentOrder = currentOrder.convertTo(new Limit(tradePrice));
418 | book.add(currentOrder);
419 | newOrderPrice = tradePrice;
420 | }
421 | }
422 |
423 | counterBook.removeAll(toRemove);
424 | for (Order order : toAdd) {
425 | counterBook.add(order);
426 | }
427 |
428 | if (currentOrder.getQuantity() == 0) {
429 | book.remove(currentOrder);
430 | }
431 |
432 | return startQuantity != currentOrder.getQuantity();
433 | }
434 |
435 | private void generateTrade(final Order newOrder, final Order order, final int tradeQuantity, final double price) {
436 | Order buyOrder = newOrder.getSide() == OrderSide.Buy ? newOrder : order;
437 | Order sellOrder = newOrder == buyOrder ? order : newOrder;
438 |
439 | notifier.next(new Trade(
440 | buyOrder.getOrderId(), buyOrder.getBroker(),
441 | sellOrder.getOrderId(), sellOrder.getBroker(),
442 | tradeQuantity, price));
443 | }
444 |
445 | private int determineTradeQuantity(Order newOrder, Order order) {
446 | return Math.min(newOrder.getQuantity(), order.getQuantity());
447 | }
448 |
449 | private Double determineTradePrice(Double newOrderPrice, Double counterBookOrderPrice, OrderSide counterBookSide, Double counterBookBestLimit) {
450 |
451 | if (newOrderPrice == null && counterBookOrderPrice == null) {
452 | if (counterBookBestLimit != null) {
453 | return counterBookBestLimit;
454 | }
455 |
456 | return referencePrice;
457 | }
458 |
459 | if (newOrderPrice == null) {
460 | return counterBookOrderPrice;
461 | }
462 |
463 | if (counterBookOrderPrice == null) {
464 | return tryImprovePrice(newOrderPrice, counterBookSide, counterBookBestLimit);
465 | }
466 |
467 | if (counterBookSide == OrderSide.Buy && counterBookOrderPrice >= newOrderPrice) {
468 | return counterBookOrderPrice;
469 | }
470 |
471 | if (counterBookSide == OrderSide.Sell && counterBookOrderPrice <= newOrderPrice) {
472 | return counterBookOrderPrice;
473 | }
474 |
475 | return null; // Can't trade
476 | }
477 |
478 | private double tryImprovePrice(double price, OrderSide counterBookSide, Double counterBookBestLimit) {
479 | if (counterBookBestLimit == null) {
480 | return price; // can't improve, not best limit in counter book
481 | }
482 |
483 | if (counterBookSide == OrderSide.Buy && counterBookBestLimit > price) {
484 | return counterBookBestLimit;
485 | }
486 | if (counterBookSide == OrderSide.Sell && counterBookBestLimit < price) {
487 | return counterBookBestLimit;
488 | }
489 |
490 | return price; // can't improve
491 | }
492 |
493 | public Double getBestLimit(final OrderSide side) {
494 | return side != OrderSide.Buy ? sellOrderBook.getBestLimit() : buyOrderBook.getBestLimit();
495 | }
496 |
497 | @Nonnull
498 | public Closeable register(@Nonnull Observer super Trade> observer) {
499 | return notifier.register(observer);
500 | }
501 |
502 | private OrderBook getBook(final OrderSide side) {
503 | return side != OrderSide.Buy ? sellOrderBook : buyOrderBook;
504 | }
505 |
506 | private OrderBook getCounterBook(final OrderSide side) {
507 | return side != OrderSide.Buy ? buyOrderBook : sellOrderBook;
508 | }
509 | }
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/Order.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import java.util.concurrent.atomic.AtomicInteger;
4 |
5 | public class Order {
6 | private static AtomicInteger c = new AtomicInteger(0);
7 | private String orderId;
8 | private final String broker;
9 | private int quantity;
10 | private final OrderType orderType;
11 | private final OrderSide side;
12 | private final int id;
13 |
14 | public int compareTo(final Order anOrder, final Double bestLimit) {
15 | final int BEFORE = -1;
16 | final int EQUAL = 0;
17 | final int AFTER = 1;
18 |
19 | if (getId() == anOrder.getId())
20 | return EQUAL;
21 |
22 | final int compareOrderType = compareOrderType(anOrder);
23 | if (compareOrderType != EQUAL) {
24 | return compareOrderType;
25 | }
26 |
27 | final int comparePrice = comparePrice(anOrder, bestLimit);
28 | if (comparePrice == EQUAL) {
29 | // Time after orderType compare
30 | if (getId() > anOrder.getId()) {
31 | return BEFORE;
32 | } else if (getId() < anOrder.getId()) {
33 | return AFTER;
34 | }
35 | }
36 |
37 | return comparePrice;
38 | }
39 |
40 | private int compareOrderType(Order other) {
41 | final int BEFORE = -1;
42 | final int EQUAL = 0;
43 | final int AFTER = 1;
44 |
45 | if (this.orderType.acceptsMarketPrice() && !other.orderType.acceptsMarketPrice()) {
46 | return AFTER;
47 | }
48 | if (!this.orderType.acceptsMarketPrice() && other.orderType.acceptsMarketPrice()) {
49 | return BEFORE;
50 | }
51 |
52 | return EQUAL;
53 | }
54 |
55 | private int comparePrice(final Order anOrder, final Double bestLimit) {
56 | final int BEFORE = -1;
57 | final int EQUAL = 0;
58 | final int AFTER = 1;
59 |
60 | final Double thisOrderLimit = getOrderType().price(side, bestLimit);
61 | final Double anOrderLimit = anOrder.getOrderType().price(side, bestLimit);
62 |
63 | if (thisOrderLimit == null && anOrderLimit == null) {
64 | return EQUAL;
65 | }
66 |
67 | switch (side) {
68 | case Buy:
69 | if (thisOrderLimit == null || thisOrderLimit > anOrderLimit) {
70 | return AFTER;
71 | } else if (thisOrderLimit < anOrderLimit) {
72 | return BEFORE;
73 | }
74 | break;
75 | case Sell:
76 | if (thisOrderLimit == null || thisOrderLimit < anOrderLimit) {
77 | return AFTER;
78 | } else if (thisOrderLimit > anOrderLimit) {
79 | return BEFORE;
80 | }
81 | break;
82 | }
83 |
84 | return EQUAL;
85 | }
86 |
87 | public Order(final String orderId, final String broker, final int quantity, final OrderType orderType, final OrderSide side) {
88 | id = c.incrementAndGet();
89 | this.orderId = orderId;
90 | this.orderType = orderType;
91 | this.side = side;
92 | this.broker = broker;
93 | this.quantity = quantity;
94 | }
95 |
96 | public int getQuantity() {
97 | return quantity;
98 | }
99 |
100 | public String getOrderId() {
101 | return orderId;
102 | }
103 |
104 | public String getBroker() {
105 | return broker;
106 | }
107 |
108 | public OrderSide getSide() {
109 | return side;
110 | }
111 |
112 | public int getId() {
113 | return id;
114 | }
115 |
116 | public void decrementQuantity(final int quantity) {
117 | this.quantity -= quantity;
118 | }
119 |
120 | public OrderType getOrderType() {
121 | return orderType;
122 | }
123 |
124 | public Order convertTo(OrderType orderType) {
125 | return new Order(orderId, broker, quantity, orderType, side);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/OrderBook.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import com.google.common.base.Predicate;
4 | import com.google.common.collect.FluentIterable;
5 |
6 | import java.util.Collection;
7 | import java.util.LinkedList;
8 | import java.util.List;
9 |
10 | public class OrderBook {
11 | private final LinkedList orders = new LinkedList();
12 | private Double bestLimit = null;
13 |
14 | public Double getBestLimit() {
15 | return bestLimit;
16 | }
17 |
18 |
19 | public void removeAll(Collection ordersToRemove) {
20 | orders.removeAll(ordersToRemove);
21 | validatePegOrderPositions();
22 | }
23 |
24 | public void remove(Order order) {
25 | orders.remove(order);
26 | validatePegOrderPositions();
27 | }
28 |
29 | public int add(final Order order) {
30 | int position = placeOrderInBook(order);
31 | validatePegOrderPositions();
32 | return position;
33 | }
34 |
35 | private int placeOrderInBook(final Order newOrder) {
36 | int count = 0;
37 | for (final Order order : orders) {
38 |
39 | final int compare = order.compareTo(newOrder, bestLimit);
40 | if (compare < 0) {
41 | break;
42 | }
43 |
44 | count++;
45 | }
46 |
47 | orders.add(count, newOrder);
48 | return count;
49 | }
50 |
51 | private void validatePegOrderPositions() {
52 | if (orders.size() == 0)
53 | return;
54 |
55 | Double oldBestLimit = bestLimit;
56 | bestLimit = calculateBestLimit();
57 | boolean changed = oldBestLimit != null ? !oldBestLimit.equals(bestLimit) : bestLimit != null;
58 |
59 | if (changed) {
60 | List pegged = FluentIterable.from(orders)
61 | .filter(new Predicate() {
62 | @Override
63 | public boolean apply(final Order input) {
64 | return input.getOrderType().canPegLimit();
65 | }
66 | }).toImmutableList();
67 |
68 | orders.removeAll(pegged);
69 | for (Order order : pegged) {
70 | placeOrderInBook(order.convertTo(order.getOrderType()));
71 | }
72 | }
73 |
74 | // Cheat if the order book only has a PEG left
75 | if (orders.size() > 0 && orders.get(0).getOrderType().canPegLimit())
76 | orders.clear();
77 |
78 | }
79 |
80 | private Double calculateBestLimit() {
81 | // Order book should be good, just reset best
82 | for (final Order order : orders) {
83 | if (order.getOrderType().providesLimit()) {
84 | return order.getOrderType().getLimit();
85 | }
86 | }
87 |
88 | return bestLimit;
89 | }
90 |
91 | public int orderBookDepth() {
92 | return orders.size();
93 | }
94 |
95 | public List getOrders() {
96 | return orders;
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/OrderEntry.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import java.util.UUID;
4 |
5 | /**
6 | * Created with IntelliJ IDEA.
7 | * User: eprystupa
8 | * Date: 7/26/12
9 | * Time: 5:19 PM
10 | */
11 | public class OrderEntry {
12 | private final String orderId;
13 | private final OrderSide side;
14 | private final String broker;
15 | private final int quantity;
16 | private final OrderType orderType;
17 |
18 |
19 | public OrderEntry(OrderSide side, String broker, int quantity, OrderType orderType) {
20 | this.orderId = UUID.randomUUID().toString();
21 | this.side = side;
22 | this.broker = broker;
23 | this.quantity = quantity;
24 | this.orderType = orderType;
25 | }
26 |
27 | public String getOrderId() {
28 | return orderId;
29 | }
30 |
31 | public OrderSide getSide() {
32 | return side;
33 | }
34 |
35 | public String getBroker() {
36 | return broker;
37 | }
38 |
39 | public int getQuantity() {
40 | return quantity;
41 | }
42 |
43 | public OrderType getOrderType() {
44 | return orderType;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/OrderSide.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: eprystupa
6 | * Date: 7/22/12
7 | * Time: 10:19 AM
8 | */
9 | public enum OrderSide {
10 | Buy, Sell
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/OrderType.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | public interface OrderType {
4 |
5 | boolean acceptsMarketPrice();
6 |
7 | boolean providesLimit();
8 |
9 | double getLimit();
10 |
11 | boolean canPegLimit();
12 |
13 | boolean convertsToLimit();
14 |
15 | Double price(OrderSide side, final Double bestLimit);
16 |
17 | String displayPrice(Double price);
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/Trade.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | public class Trade {
4 | private final String buyOrderId;
5 | private final String buyBroker;
6 | private final String sellOrderId;
7 | private final String sellBroker;
8 | private final int quantity;
9 | private final double price;
10 |
11 |
12 |
13 | public Trade(String buyOrderId, final String buyBroker, String sellOrderId, final String sellBroker, final int quantity, final double price) {
14 | this.buyOrderId = buyOrderId;
15 | this.buyBroker = buyBroker;
16 | this.sellOrderId = sellOrderId;
17 | this.sellBroker = sellBroker;
18 | this.price = price;
19 | this.quantity = quantity;
20 | }
21 |
22 | public String getSellBroker() {
23 | return sellBroker;
24 | }
25 |
26 | public String getBuyBroker() {
27 | return buyBroker;
28 | }
29 |
30 | public int getQuantity() {
31 | return quantity;
32 | }
33 |
34 | public double getPrice() {
35 | return price;
36 | }
37 |
38 | public String getBuyOrderId() {
39 | return buyOrderId;
40 | }
41 |
42 | public String getSellOrderId() {
43 | return sellOrderId;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/TradingMode.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: eprystupa
6 | * Date: 7/19/12
7 | * Time: 7:24 AM
8 | */
9 | public enum TradingMode {
10 | ByAuction,
11 | Continuous
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/TradingPhase.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: eprystupa
6 | * Date: 7/19/12
7 | * Time: 7:12 AM
8 | */
9 | public enum TradingPhase {
10 | // PreOpeningPhase,
11 | CoreCall,
12 | CoreAuction,
13 | CoreContinuous,
14 | // PreCloseingPhase, ClosingAuction, TradingAtLastPhase, AfterHoursTrading
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/engine/MatchingEngine.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.engine;
2 |
3 | import com.euronextclone.MatchingUnit;
4 | import com.euronextclone.OrderEntry;
5 | import com.euronextclone.OrderSide;
6 | import com.euronextclone.ordertypes.Limit;
7 | import com.lmax.disruptor.*;
8 | import com.lmax.disruptor.dsl.Disruptor;
9 |
10 | import java.util.Scanner;
11 | import java.util.concurrent.ExecutorService;
12 | import java.util.concurrent.Executors;
13 |
14 | /**
15 | * Matching Engine will contain 1 or more matching units.
16 | *
17 | * Engine is responsible for listing to incoming requests, journal the requests (orderEntry),
18 | * replicate to slave Matching engine and then submitting request to Matching Unit for processing
19 | *
20 | * Engine also needs to heat-beat so slave can promote to master if required
21 | *
22 | * Engine will also handle networking - ZeroMQ
23 | */
24 | public class MatchingEngine {
25 | private final static int RING_SIZE = 1024 * 8;
26 | private final ExecutorService EXECUTOR = Executors.newFixedThreadPool(3);
27 | private final RingBuffer ringBuffer;
28 | private final MatchingUnit matchingUnit;
29 |
30 | final EventHandler journalHandler = new EventHandler()
31 | {
32 | public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception
33 | {
34 | System.out.println(String.format("Journal %s", event.getValue()));
35 | }
36 | };
37 | final EventHandler replicatorHandler = new EventHandler()
38 | {
39 | public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception
40 | {
41 | System.out.println(String.format("Replicator %s", event.getValue()));
42 | }
43 | };
44 | final EventHandler matchingUnitHandler = new EventHandler()
45 | {
46 | public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception
47 | {
48 | System.out.println(String.format("Process %s", event.getValue()));
49 | matchingUnit.addOrder(event.getValue());
50 | }
51 | };
52 |
53 | private final Disruptor disruptor;
54 |
55 | public MatchingEngine(final String instrument) {
56 | matchingUnit = new MatchingUnit(instrument);
57 |
58 | disruptor = new Disruptor(ValueEvent.EVENT_FACTORY, EXECUTOR,
59 | new SingleThreadedClaimStrategy(RING_SIZE),
60 | new SleepingWaitStrategy());
61 | disruptor.handleEventsWith(journalHandler, replicatorHandler).then(matchingUnitHandler);
62 | ringBuffer = disruptor.start();
63 | }
64 |
65 | public void publish(final int num) {
66 | // Publishers claim events in sequence
67 | long sequence = ringBuffer.next();
68 | ValueEvent event = ringBuffer.get(sequence);
69 | event.setValue(new OrderEntry(OrderSide.Buy, "A", num, new Limit(num))); // this could be more complex with multiple fields
70 |
71 | ringBuffer.publish(sequence);
72 | }
73 |
74 | public static void main(String args[]) {
75 | final MatchingEngine engine = new MatchingEngine("MSFT");
76 | engine.publish(1234);
77 | engine.publish(1235);
78 |
79 | System.out.println("[Enter] q to exit");
80 | Scanner scanner = new Scanner(System.in);
81 | while (!scanner.nextLine().trim().equals("q")) System.out.println("[Enter] q to exit");
82 |
83 | engine.halt();
84 | System.out.println("Exiting...");
85 | }
86 |
87 | private void halt() {
88 | disruptor.halt();
89 | disruptor.shutdown();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/engine/ValueEvent.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.engine;
2 |
3 | import com.euronextclone.OrderEntry;
4 | import com.lmax.disruptor.EventFactory;
5 |
6 | public final class ValueEvent
7 | {
8 | private OrderEntry value;
9 |
10 | public OrderEntry getValue()
11 | {
12 | return value;
13 | }
14 |
15 | public void setValue(final OrderEntry value)
16 | {
17 | this.value = value;
18 | }
19 |
20 | public final static EventFactory EVENT_FACTORY = new EventFactory()
21 | {
22 | public ValueEvent newInstance()
23 | {
24 | return new ValueEvent();
25 | }
26 | };
27 | }
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/FixAdapter.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import quickfix.*;
6 | import quickfix.fix42.MessageCracker;
7 |
8 | /**
9 | * Created with IntelliJ IDEA.
10 | * User: eprystupa
11 | * Date: 7/24/12
12 | * Time: 10:30 PM
13 | */
14 | public abstract class FixAdapter extends MessageCracker implements Application {
15 |
16 | private static Logger logger = LoggerFactory.getLogger(FixAdapter.class);
17 |
18 | @Override
19 | public void onLogon(SessionID sessionId) {
20 | logger.debug("Logon for session: {}", sessionId);
21 | }
22 |
23 | @Override
24 | public void onLogout(SessionID sessionId) {
25 | logger.debug("Logout for session: {}", sessionId);
26 | }
27 |
28 | @Override
29 | public void toAdmin(Message message, SessionID sessionId) {
30 | logger.debug("toAdmin for session: {}, message: {}", sessionId, message);
31 | }
32 |
33 | @Override
34 | public void fromAdmin(Message message, SessionID sessionId) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon {
35 | logger.debug("fromAdmin for session: {}, message: {}", sessionId, message);
36 | }
37 |
38 | @Override
39 | public void toApp(Message message, SessionID sessionId) throws DoNotSend {
40 | logger.debug("toApp for session: {}, message: {}", sessionId, message);
41 | }
42 |
43 | @Override
44 | public void fromApp(Message message, SessionID sessionId) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
45 | logger.debug("fromApp for session: {}, message: {}", sessionId, message);
46 | crack(message, sessionId);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/client/FixClient.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.client;
2 |
3 | import com.euronextclone.fix.FixAdapter;
4 | import com.lmax.disruptor.EventHandler;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import quickfix.*;
8 | import quickfix.fix42.ExecutionReport;
9 | import quickfix.fix42.NewOrderSingle;
10 |
11 |
12 | /**
13 | * Created with IntelliJ IDEA.
14 | * User: eprystupa
15 | * Date: 7/25/12
16 | * Time: 8:28 PM
17 | */
18 | public class FixClient extends FixAdapter {
19 |
20 | private static Logger logger = LoggerFactory.getLogger(FixClient.class);
21 | private final SocketInitiator socketInitiator;
22 | private SessionID sessionId;
23 | private EventHandler executionReportEventHandler;
24 |
25 | public FixClient(final SessionSettings settings) throws ConfigError {
26 |
27 | MessageStoreFactory messageStoreFactory = new FileStoreFactory(settings);
28 | SLF4JLogFactory logFactory = new SLF4JLogFactory(settings);
29 | MessageFactory messageFactory = new DefaultMessageFactory();
30 | socketInitiator = new SocketInitiator(this, messageStoreFactory, settings, logFactory, messageFactory);
31 | }
32 |
33 | @Override
34 | public void onCreate(SessionID sessionId) {
35 | logger.info("Session created: {}", sessionId);
36 |
37 | this.sessionId = sessionId;
38 | Session.lookupSession(sessionId).logon();
39 | }
40 |
41 | @Override
42 | public void onMessage(ExecutionReport message, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue {
43 | try {
44 | executionReportEventHandler.onEvent(message, 0L, true);
45 | } catch (Exception e) {
46 | logger.error(e.getMessage(), e);
47 | }
48 | }
49 |
50 | public void start() throws ConfigError {
51 | socketInitiator.start();
52 | }
53 |
54 | public void stop() {
55 | socketInitiator.stop();
56 | }
57 |
58 | public void submitOrder(NewOrderSingle order) throws SessionNotFound {
59 | Session.sendToTarget(order, sessionId);
60 | }
61 |
62 | public void handleExecutions(EventHandler handler) {
63 | executionReportEventHandler = handler;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/client/FixClientApp.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.client;
2 |
3 | import com.euronextclone.fix.client.commands.ClientCommand;
4 | import com.euronextclone.fix.client.commands.PlaceLimitOrder;
5 | import com.euronextclone.fix.client.commands.PlaceMarketOrder;
6 | import com.lmax.disruptor.EventHandler;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import quickfix.ConfigError;
10 | import quickfix.SessionNotFound;
11 | import quickfix.SessionSettings;
12 | import quickfix.field.ExecType;
13 | import quickfix.field.Side;
14 | import quickfix.fix42.ExecutionReport;
15 |
16 | import java.io.InputStream;
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import java.util.Scanner;
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 | /**
24 | * Created with IntelliJ IDEA.
25 | * User: eprystupa
26 | * Date: 7/24/12
27 | * Time: 8:24 PM
28 | */
29 | public class FixClientApp {
30 | private static Logger logger = LoggerFactory.getLogger(FixClientApp.class);
31 | private static List commands = new ArrayList();
32 |
33 | public static void main(String args[]) {
34 |
35 | commands.add(new PlaceLimitOrder());
36 | commands.add(new PlaceMarketOrder());
37 |
38 | try {
39 | final String broker = getBroker(args, "A");
40 | final FixClient client = createClient(broker);
41 |
42 | Scanner scanner = new Scanner(System.in);
43 | System.out.println("Enter [q] to exit, or [h] to list commands");
44 | while (true) {
45 | System.out.print("Broker " + broker + "> ");
46 | final String command = scanner.nextLine().trim();
47 | if (command.equals("q")) {
48 | break;
49 | }
50 |
51 | if (command.equals("h")) {
52 | listCommands();
53 | continue;
54 | }
55 |
56 | for (ClientCommand clientCommand : commands) {
57 | final Matcher matcher = clientCommand.pattern().matcher(command);
58 | if (matcher.matches()) {
59 | clientCommand.execute(client, matcher);
60 | break;
61 | }
62 | }
63 | }
64 |
65 | System.out.println("Exiting...");
66 |
67 | client.stop();
68 |
69 | } catch (ConfigError configError) {
70 | logger.error(configError.getMessage(), configError);
71 | } catch (SessionNotFound sessionNotFound) {
72 | logger.error(sessionNotFound.getMessage(), sessionNotFound);
73 | }
74 | }
75 |
76 | private static String getBroker(String[] args, String defaultBroker) {
77 | // TODO: replace with some Java library for command arg parsing
78 | String result = defaultBroker;
79 | for (String arg : args) {
80 | final Matcher matcher = Pattern.compile("--broker=(\\S)").matcher(arg);
81 | if (matcher.matches()) {
82 | result = matcher.group(1);
83 | }
84 | }
85 | return result;
86 | }
87 |
88 | private static void listCommands() {
89 | for (ClientCommand command : commands) {
90 | System.out.println(command.name() + ":\t" + command.pattern().pattern());
91 | }
92 | }
93 |
94 | private static FixClient createClient(final String broker) throws ConfigError {
95 | final InputStream config = FixClientApp.class.getClassLoader().getResourceAsStream("FixBroker" + broker + ".cfg");
96 | final FixClient client = new FixClient(new SessionSettings(config));
97 | client.start();
98 |
99 | client.handleExecutions(new EventHandler() {
100 |
101 | @Override
102 | public void onEvent(ExecutionReport report, long sequence, boolean endOfBatch) throws Exception {
103 | String orderId = report.getOrderID().getValue();
104 | String side = report.getSide().getValue() == Side.BUY ? "bought" : "sold";
105 | ExecType execType = report.getExecType();
106 | String symbol = report.getSymbol().getValue();
107 |
108 | if (execType.getValue() == ExecType.NEW) {
109 | logger.debug("Order {} accepted", orderId);
110 | } else {
111 | double tradeQty = report.getLastShares().getValue();
112 | double tradePrice = report.getLastPx().getValue();
113 | logger.debug("Broker {} {} {} shares of {} at {}", new Object[]{broker, side, tradeQty, symbol, tradePrice});
114 | }
115 | }
116 | });
117 | return client;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/client/OrderBuilder.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.client;
2 |
3 | import quickfix.field.*;
4 | import quickfix.fix42.NewOrderSingle;
5 |
6 | import java.util.Date;
7 | import java.util.UUID;
8 |
9 | /**
10 | * Created with IntelliJ IDEA.
11 | * User: eprystupa
12 | * Date: 7/25/12
13 | * Time: 10:02 PM
14 | */
15 | public class OrderBuilder {
16 | private String symbol;
17 | private char orderType;
18 | private double quantity;
19 | private char side;
20 | private double price;
21 |
22 | public OrderBuilder withSymbol(String symbol) {
23 | this.symbol = symbol;
24 | return this;
25 | }
26 |
27 | public OrderBuilder withOrderType(char orderType) {
28 | this.orderType = orderType;
29 | return this;
30 | }
31 |
32 | public OrderBuilder withQuantity(double quantity) {
33 | this.quantity = quantity;
34 | return this;
35 | }
36 |
37 | public OrderBuilder at(double price) {
38 | this.price = price;
39 | return this;
40 | }
41 |
42 | public NewOrderSingle buy() {
43 | this.side = Side.BUY;
44 | return build();
45 | }
46 |
47 | public NewOrderSingle sell() {
48 | this.side = Side.SELL;
49 | return build();
50 | }
51 |
52 | private NewOrderSingle build() {
53 | final NewOrderSingle order = new NewOrderSingle(
54 | new ClOrdID(UUID.randomUUID().toString()),
55 | new HandlInst(HandlInst.AUTOMATED_EXECUTION_ORDER_PUBLIC),
56 | new Symbol(symbol),
57 | new Side(side),
58 | new TransactTime(new Date()),
59 | new OrdType(orderType));
60 |
61 | order.set(new OrderQty(quantity));
62 | order.set(new Price(price));
63 | return order;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/client/commands/ClientCommand.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.client.commands;
2 |
3 | import com.euronextclone.fix.client.FixClient;
4 | import quickfix.SessionNotFound;
5 |
6 | import java.util.regex.Matcher;
7 | import java.util.regex.Pattern;
8 |
9 | /**
10 | * Created with IntelliJ IDEA.
11 | * User: eprystupa
12 | * Date: 8/1/12
13 | * Time: 7:34 PM
14 | */
15 | public interface ClientCommand {
16 | String name();
17 |
18 | Pattern pattern();
19 |
20 | void execute(FixClient client, Matcher input) throws SessionNotFound;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/client/commands/PlaceLimitOrder.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.client.commands;
2 |
3 | import com.euronextclone.fix.client.FixClient;
4 | import com.euronextclone.fix.client.OrderBuilder;
5 | import quickfix.SessionNotFound;
6 | import quickfix.field.OrdType;
7 | import quickfix.fix42.NewOrderSingle;
8 |
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | /**
13 | * Created with IntelliJ IDEA.
14 | * User: eprystupa
15 | * Date: 8/1/12
16 | * Time: 7:38 PM
17 | */
18 | public class PlaceLimitOrder implements ClientCommand {
19 |
20 | private static final Pattern INSTRUCTION = Pattern.compile("^(Buy|Sell) (\\S+) (\\d+)@(\\d+)$", Pattern.CASE_INSENSITIVE);
21 |
22 | @Override
23 | public String name() {
24 | return "Place limit order";
25 | }
26 |
27 | @Override
28 | public Pattern pattern() {
29 | return INSTRUCTION;
30 | }
31 |
32 | @Override
33 | public void execute(final FixClient client, final Matcher input) throws SessionNotFound {
34 |
35 | if (!input.matches()) {
36 | throw new RuntimeException("Bad input for place limit order command");
37 | }
38 | final String side = input.group(1);
39 | final String symbol = input.group(2);
40 | final String quantity = input.group(3);
41 | final String price = input.group(4);
42 |
43 | final OrderBuilder orderBuilder = new OrderBuilder()
44 | .withSymbol(symbol)
45 | .withOrderType(OrdType.LIMIT)
46 | .withQuantity(Double.parseDouble(quantity))
47 | .at(Double.parseDouble(price));
48 | final NewOrderSingle order = side.compareToIgnoreCase("Buy") == 0 ? orderBuilder.buy() : orderBuilder.sell();
49 | client.submitOrder(order);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/client/commands/PlaceMarketOrder.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.client.commands;
2 |
3 | import com.euronextclone.fix.client.FixClient;
4 | import com.euronextclone.fix.client.OrderBuilder;
5 | import quickfix.SessionNotFound;
6 | import quickfix.field.OrdType;
7 | import quickfix.fix42.NewOrderSingle;
8 |
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | /**
13 | * Created with IntelliJ IDEA.
14 | * User: eprystupa
15 | * Date: 8/1/12
16 | * Time: 10:22 PM
17 | */
18 | public class PlaceMarketOrder implements ClientCommand {
19 |
20 | private static final Pattern INSTRUCTION = Pattern.compile("^(Buy|Sell) (\\S+) (\\d+)$", Pattern.CASE_INSENSITIVE);
21 |
22 | @Override
23 | public String name() {
24 | return "Place market order";
25 | }
26 |
27 | @Override
28 | public Pattern pattern() {
29 | return INSTRUCTION;
30 | }
31 |
32 | @Override
33 | public void execute(FixClient client, Matcher input) throws SessionNotFound {
34 | if (!input.matches()) {
35 | throw new RuntimeException("Bad input for place market order command");
36 | }
37 | final String side = input.group(1);
38 | final String symbol = input.group(2);
39 | final String quantity = input.group(3);
40 |
41 | final OrderBuilder orderBuilder = new OrderBuilder()
42 | .withSymbol(symbol)
43 | .withOrderType(OrdType.MARKET)
44 | .withQuantity(Double.parseDouble(quantity));
45 | final NewOrderSingle order = side.compareToIgnoreCase("Buy") == 0 ? orderBuilder.buy() : orderBuilder.sell();
46 | client.submitOrder(order);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/server/FixServer.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.server;
2 |
3 | import com.euronextclone.*;
4 | import com.euronextclone.fix.FixAdapter;
5 | import com.euronextclone.messaging.Publisher;
6 | import com.euronextclone.ordertypes.Limit;
7 | import com.euronextclone.ordertypes.Market;
8 | import hu.akarnokd.reactive4java.reactive.Observer;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import quickfix.*;
12 | import quickfix.field.*;
13 | import quickfix.fix42.ExecutionReport;
14 | import quickfix.fix42.NewOrderSingle;
15 |
16 | import javax.annotation.Nonnull;
17 | import java.util.HashMap;
18 | import java.util.Map;
19 | import java.util.UUID;
20 |
21 | public class FixServer extends FixAdapter implements Observer {
22 |
23 | private static Logger logger = LoggerFactory.getLogger(FixServer.class);
24 |
25 | private final SocketAcceptor socketAcceptor;
26 | private Map sessionByBroker;
27 | // TODO: this is temporary. Matching units will be outside of FIX server, likely in a different process
28 | private final MatchingUnit matchingUnit;
29 | // TODO: this is temporary until symbol is added to OrderEntry
30 | private final Symbol symbol = new Symbol("MSFT");
31 | private final Publisher orderPublisher;
32 |
33 |
34 | // TODO: to correctly route execution reports to relevant party looks like I need:
35 | // a) a mapping from OrderId to Broker (TargetCompID)
36 | // b) a mapping from Broker (TargetCompId) to SessionID
37 |
38 | public FixServer(final SessionSettings settings, final Publisher orderPublisher) throws ConfigError {
39 | this.orderPublisher = orderPublisher;
40 | MessageStoreFactory messageStoreFactory = new FileStoreFactory(settings);
41 | SLF4JLogFactory logFactory = new SLF4JLogFactory(settings);
42 | MessageFactory messageFactory = new DefaultMessageFactory();
43 |
44 | socketAcceptor = new SocketAcceptor(this, messageStoreFactory, settings, logFactory, messageFactory);
45 | sessionByBroker = new HashMap();
46 |
47 | matchingUnit = new MatchingUnit("MSFT");
48 | matchingUnit.register(this);
49 | matchingUnit.setTradingMode(TradingMode.Continuous);
50 | matchingUnit.setTradingPhase(TradingPhase.CoreContinuous);
51 | }
52 |
53 | public void start() throws ConfigError {
54 | socketAcceptor.start();
55 | }
56 |
57 | public void stop() {
58 | socketAcceptor.stop();
59 | }
60 |
61 | @Override
62 | public void onCreate(SessionID sessionId) {
63 | sessionByBroker.put(sessionId.getTargetCompID(), sessionId);
64 | }
65 |
66 | @Override
67 | public void onMessage(NewOrderSingle order, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue {
68 |
69 | final String broker = sessionID.getTargetCompID();
70 | OrderEntry orderEntry = convertToOrderEntry(order, broker);
71 | acceptOrder(orderEntry);
72 |
73 | final String symbol = order.getSymbol().getValue();
74 | orderPublisher.publish(symbol, orderEntry, OrderEntry.class);
75 |
76 | // TODO: this is going away soon when subscribe is ready on the matching engine side
77 | matchingUnit.addOrder(orderEntry);
78 | }
79 |
80 | @Override
81 | public void next(Trade trade) {
82 | try {
83 | sendExecutionReport(trade, new Side(Side.BUY));
84 | sendExecutionReport(trade, new Side(Side.SELL));
85 | } catch (SessionNotFound sessionNotFound) {
86 | logger.error(sessionNotFound.getMessage(), sessionNotFound);
87 | }
88 | }
89 |
90 | @Override
91 | public void error(@Nonnull Throwable throwable) {
92 | throw new RuntimeError(throwable);
93 | }
94 |
95 | @Override
96 | public void finish() {
97 | // Nothing to do
98 | }
99 |
100 | private OrderEntry convertToOrderEntry(NewOrderSingle orderSingle, String broker) throws FieldNotFound {
101 | OrderSide side = orderSingle.getSide().getValue() == Side.BUY ? OrderSide.Buy : OrderSide.Sell;
102 | OrderType orderType = null;
103 | switch (orderSingle.getOrdType().getValue()) {
104 | case OrdType.MARKET:
105 | orderType = new Market();
106 | break;
107 | case OrdType.LIMIT:
108 | orderType = new Limit(orderSingle.getPrice().getValue());
109 | break;
110 | }
111 | return new OrderEntry(
112 | side,
113 | broker,
114 | (int) orderSingle.getOrderQty().getValue(),
115 | orderType);
116 | }
117 |
118 | private ExecutionReport buildExecutionReport(final String orderId,
119 | final ExecTransType execTransType,
120 | final ExecType execType,
121 | final OrdStatus ordStatus,
122 | final Side side) {
123 |
124 | return new ExecutionReport(
125 | new OrderID(orderId),
126 | generateExecId(),
127 | execTransType,
128 | execType,
129 | ordStatus,
130 | symbol,
131 | side,
132 | new LeavesQty(),
133 | new CumQty(),
134 | new AvgPx());
135 | }
136 |
137 | private void acceptOrder(final OrderEntry orderEntry) {
138 | final String broker = orderEntry.getBroker();
139 | final SessionID sessionID = sessionByBroker.get(broker);
140 |
141 | if (sessionID != null) {
142 | final Side side = new Side(orderEntry.getSide() == OrderSide.Buy ? Side.BUY : Side.SELL);
143 | final ExecutionReport report = buildExecutionReport(
144 | orderEntry.getOrderId(),
145 | new ExecTransType(ExecTransType.NEW),
146 | new ExecType(ExecType.NEW),
147 | new OrdStatus(OrdStatus.NEW),
148 | side);
149 | sendToTarget(report, sessionID);
150 | }
151 | }
152 |
153 | private boolean sendToTarget(ExecutionReport report, SessionID sessionID) {
154 | try {
155 | return Session.sendToTarget(report, sessionID);
156 | } catch (SessionNotFound sessionNotFound) {
157 | throw new RuntimeError(sessionNotFound);
158 | }
159 | }
160 |
161 | private void sendExecutionReport(Trade trade, Side side) throws SessionNotFound {
162 | final boolean buy = side.getValue() == Side.BUY;
163 | final String orderId = buy ? trade.getBuyOrderId() : trade.getSellOrderId();
164 | final String broker = buy ? trade.getBuyBroker() : trade.getSellBroker();
165 | final SessionID sessionID = sessionByBroker.get(broker);
166 | if (sessionID != null) {
167 | ExecutionReport executionReport = new ExecutionReport(
168 | new OrderID(orderId),
169 | generateExecId(),
170 | new ExecTransType(ExecTransType.STATUS),
171 | new ExecType(ExecType.PARTIAL_FILL),
172 | new OrdStatus(OrdStatus.PARTIALLY_FILLED),
173 | symbol,
174 | side,
175 | new LeavesQty(),
176 | new CumQty(),
177 | new AvgPx());
178 |
179 | executionReport.set(new LastShares(trade.getQuantity()));
180 | executionReport.set(new LastPx(trade.getPrice()));
181 | sendToTarget(executionReport, sessionID);
182 | }
183 | }
184 |
185 | private ExecID generateExecId() {
186 | return new ExecID(UUID.randomUUID().toString());
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/fix/server/FixServerApp.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.fix.server;
2 |
3 | import com.euronextclone.messaging.Publisher;
4 | import quickfix.ConfigError;
5 | import quickfix.SessionSettings;
6 |
7 | import java.io.InputStream;
8 | import java.util.Scanner;
9 |
10 | /**
11 | * Created with IntelliJ IDEA.
12 | * User: eprystupa
13 | * Date: 7/26/12
14 | * Time: 9:55 PM
15 | */
16 | public class FixServerApp {
17 |
18 | public static void main(String[] args) throws ConfigError {
19 |
20 | final InputStream config = FixServerApp.class.getClassLoader().getResourceAsStream("FixServer.cfg");
21 |
22 | FixServer fixServer = new FixServer(
23 | new SessionSettings(config),
24 | new Publisher("tcp://*:5555"));
25 | fixServer.start();
26 |
27 | Scanner scanner = new Scanner(System.in);
28 | System.out.println("[Enter] q to exit");
29 | while (!scanner.nextLine().trim().equals("q")) System.out.println("[Enter] q to exit");
30 |
31 | System.out.println("Exiting...");
32 | fixServer.stop();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/framework/Action.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.framework;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: eprystupa
6 | * Date: 8/6/12
7 | * Time: 8:39 AM
8 | */
9 | public interface Action {
10 | void invoke(final T t);
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/framework/Factory.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.framework;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: eprystupa
6 | * Date: 8/6/12
7 | * Time: 8:24 AM
8 | */
9 | public interface Factory {
10 | T build();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/messaging/Publisher.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.messaging;
2 |
3 | import com.dyuproject.protostuff.LinkedBuffer;
4 | import com.dyuproject.protostuff.ProtostuffIOUtil;
5 | import com.dyuproject.protostuff.Schema;
6 | import com.dyuproject.protostuff.runtime.RuntimeSchema;
7 | import org.zeromq.ZMQ;
8 |
9 | /**
10 | * Created with IntelliJ IDEA.
11 | * User: eprystupa
12 | * Date: 8/5/12
13 | * Time: 4:54 PM
14 | */
15 | public class Publisher {
16 |
17 | private final ZMQ.Socket publisher;
18 |
19 | public Publisher(final String endpoint) {
20 | final ZMQ.Context context = ZMQ.context(1);
21 | publisher = context.socket(ZMQ.PUB);
22 | publisher.bind(endpoint);
23 | }
24 |
25 | public boolean publish(final String topic, final T payload, final Class typeClass) {
26 |
27 | publisher.send(topic.getBytes(), ZMQ.SNDMORE);
28 | final Schema schema = RuntimeSchema.getSchema(typeClass);
29 | final LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
30 | final byte[] message = ProtostuffIOUtil.toByteArray(payload, schema, buffer);
31 | return publisher.send(message, 0);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/messaging/Subscriber.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.messaging;
2 |
3 | import com.dyuproject.protostuff.ProtobufIOUtil;
4 | import com.dyuproject.protostuff.Schema;
5 | import com.dyuproject.protostuff.runtime.RuntimeSchema;
6 | import com.euronextclone.framework.Action;
7 | import com.euronextclone.framework.Factory;
8 | import org.zeromq.ZMQ;
9 |
10 | import java.io.Closeable;
11 | import java.io.IOException;
12 |
13 | /**
14 | * Created with IntelliJ IDEA.
15 | * User: eprystupa
16 | * Date: 8/6/12
17 | * Time: 7:24 AM
18 | */
19 | public class Subscriber {
20 |
21 | private final ZMQ.Socket subscriber;
22 |
23 | public Subscriber(final String endpoint) {
24 | final ZMQ.Context context = ZMQ.context(1);
25 | subscriber = context.socket(ZMQ.SUB);
26 | subscriber.connect(endpoint);
27 | }
28 |
29 | public Closeable subscribe(final String topic) {
30 | final byte[] topicBytes = topic.getBytes();
31 | subscriber.subscribe(topicBytes);
32 |
33 | return new Closeable() {
34 | @Override
35 | public void close() throws IOException {
36 | subscriber.unsubscribe(topicBytes);
37 | }
38 | };
39 | }
40 |
41 | public void run(final Action handler, final Factory factory, final Class typeClass) {
42 | final Schema schema = RuntimeSchema.getSchema(typeClass);
43 |
44 | while (true) {
45 | final byte[] message = subscriber.recv(0);
46 | T event = factory.build();
47 | ProtobufIOUtil.mergeFrom(message, event, schema);
48 | handler.invoke(event);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/ordertypes/Limit.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.ordertypes;
2 |
3 | import com.euronextclone.OrderSide;
4 | import com.euronextclone.OrderType;
5 |
6 | import java.text.DecimalFormat;
7 |
8 | /**
9 | * Created with IntelliJ IDEA.
10 | * User: eprystupa
11 | * Date: 7/20/12
12 | * Time: 2:32 PM
13 | */
14 | public class Limit implements OrderType {
15 |
16 | private final double limit;
17 |
18 | public Limit(final double limit) {
19 | this.limit = limit;
20 | }
21 |
22 | @Override
23 | public boolean acceptsMarketPrice() {
24 | return false;
25 | }
26 |
27 | @Override
28 | public boolean providesLimit() {
29 | return true;
30 | }
31 |
32 | @Override
33 | public double getLimit() {
34 | return limit;
35 | }
36 |
37 | @Override
38 | public boolean canPegLimit() {
39 | return false;
40 | }
41 |
42 | @Override
43 | public boolean convertsToLimit() {
44 | return false;
45 | }
46 |
47 | @Override
48 | public Double price(OrderSide side, Double bestLimit) {
49 | return limit;
50 | }
51 |
52 | @Override
53 | public String displayPrice(Double price) {
54 | return new DecimalFormat("#.##").format(price);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/ordertypes/Market.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.ordertypes;
2 |
3 | import com.euronextclone.OrderSide;
4 | import com.euronextclone.OrderType;
5 |
6 | /**
7 | * Created with IntelliJ IDEA.
8 | * User: eprystupa
9 | * Date: 7/20/12
10 | * Time: 2:08 PM
11 | */
12 | public class Market implements OrderType {
13 |
14 | @Override
15 | public boolean acceptsMarketPrice() {
16 | return true;
17 | }
18 |
19 | @Override
20 | public boolean providesLimit() {
21 | return false;
22 | }
23 |
24 | @Override
25 | public double getLimit() {
26 | throw new RuntimeException("Market orders do not provide limit");
27 | }
28 |
29 | @Override
30 | public boolean canPegLimit() {
31 | return false;
32 | }
33 |
34 | @Override
35 | public boolean convertsToLimit() {
36 | return false;
37 | }
38 |
39 | @Override
40 | public Double price(OrderSide side, Double bestLimit) {
41 | return null;
42 | }
43 |
44 | @Override
45 | public String displayPrice(Double price) {
46 | return "MO";
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/ordertypes/MarketToLimit.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.ordertypes;
2 |
3 | import com.euronextclone.OrderSide;
4 | import com.euronextclone.OrderType;
5 |
6 | /**
7 | * Created with IntelliJ IDEA.
8 | * User: eprystupa
9 | * Date: 7/20/12
10 | * Time: 2:33 PM
11 | */
12 | public class MarketToLimit implements OrderType {
13 |
14 | @Override
15 | public boolean acceptsMarketPrice() {
16 | return true;
17 | }
18 |
19 | @Override
20 | public boolean providesLimit() {
21 | return false;
22 | }
23 |
24 | @Override
25 | public double getLimit() {
26 | throw new RuntimeException("MTL orders do not provide limit");
27 | }
28 |
29 | @Override
30 | public boolean canPegLimit() {
31 | return false;
32 | }
33 |
34 | @Override
35 | public boolean convertsToLimit() {
36 | return true;
37 | }
38 |
39 | @Override
40 | public Double price(OrderSide side, Double bestLimit) {
41 | return null;
42 | }
43 |
44 | @Override
45 | public String displayPrice(Double price) {
46 | return "MTL";
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/ordertypes/Peg.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.ordertypes;
2 |
3 | import com.euronextclone.OrderSide;
4 | import com.euronextclone.OrderType;
5 |
6 | import java.text.DecimalFormat;
7 |
8 | /**
9 | * Created with IntelliJ IDEA.
10 | * User: eprystupa
11 | * Date: 7/20/12
12 | * Time: 2:35 PM
13 | */
14 | public class Peg implements OrderType {
15 |
16 | private final static DecimalFormat priceFormat = new DecimalFormat("#.##");
17 |
18 | @Override
19 | public boolean acceptsMarketPrice() {
20 | return false;
21 | }
22 |
23 | @Override
24 | public boolean providesLimit() {
25 | return false;
26 | }
27 |
28 | @Override
29 | public double getLimit() {
30 | throw new RuntimeException("Peg orders do not provide limit");
31 | }
32 |
33 | @Override
34 | public boolean canPegLimit() {
35 | return true;
36 | }
37 |
38 | @Override
39 | public boolean convertsToLimit() {
40 | return false;
41 | }
42 |
43 | @Override
44 | public Double price(OrderSide side, Double bestLimit) {
45 | return bestLimit;
46 | }
47 |
48 | @Override
49 | public String displayPrice(Double price) {
50 | return String.format("Peg(%s)", priceFormat.format(price));
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/euronextclone/ordertypes/PegWithLimit.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone.ordertypes;
2 |
3 | import com.euronextclone.OrderSide;
4 | import com.euronextclone.OrderType;
5 |
6 | import java.text.DecimalFormat;
7 |
8 | /**
9 | * Created with IntelliJ IDEA.
10 | * User: eprystupa
11 | * Date: 7/20/12
12 | * Time: 2:36 PM
13 | */
14 | public class PegWithLimit implements OrderType {
15 |
16 | private final static DecimalFormat priceFormat = new DecimalFormat("#.##");
17 | private final double limit;
18 |
19 | public PegWithLimit(final double limit) {
20 | this.limit = limit;
21 | }
22 |
23 | @Override
24 | public boolean acceptsMarketPrice() {
25 | return false;
26 | }
27 |
28 | @Override
29 | public boolean providesLimit() {
30 | return false;
31 | }
32 |
33 | @Override
34 | public double getLimit() {
35 | return limit;
36 | }
37 |
38 | @Override
39 | public boolean canPegLimit() {
40 | return true;
41 | }
42 |
43 | @Override
44 | public boolean convertsToLimit() {
45 | return false;
46 | }
47 |
48 | @Override
49 | public Double price(OrderSide side, Double bestLimit) {
50 | if (side == OrderSide.Buy && bestLimit <= limit) {
51 | return bestLimit;
52 | } else if (side == OrderSide.Sell && bestLimit >= limit) {
53 | return bestLimit;
54 | } else {
55 | return limit;
56 | }
57 | }
58 |
59 | @Override
60 | public String displayPrice(Double price) {
61 | return String.format("Peg(%s)[%s]", priceFormat.format(price), priceFormat.format(limit));
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/resources/FixBrokerA.cfg:
--------------------------------------------------------------------------------
1 | [default]
2 | FileStorePath=target/data/fix/broker-a
3 | ConnectionType=initiator
4 | SenderCompID=BROKER-A
5 | TargetCompID=EXCHANGE
6 | SocketConnectHost=localhost
7 | StartTime=00:00:00
8 | EndTime=00:00:00
9 | HeartBtInt=30
10 | ReconnectInterval=5
11 |
12 | [session]
13 | BeginString=FIX.4.2
14 | SocketConnectPort=9878
15 |
--------------------------------------------------------------------------------
/src/main/resources/FixBrokerB.cfg:
--------------------------------------------------------------------------------
1 | [default]
2 | FileStorePath=target/data/fix/broker-b
3 | ConnectionType=initiator
4 | SenderCompID=BROKER-B
5 | TargetCompID=EXCHANGE
6 | SocketConnectHost=localhost
7 | StartTime=00:00:00
8 | EndTime=00:00:00
9 | HeartBtInt=30
10 | ReconnectInterval=5
11 |
12 | [session]
13 | BeginString=FIX.4.2
14 | SocketConnectPort=9879
15 |
--------------------------------------------------------------------------------
/src/main/resources/FixServer.cfg:
--------------------------------------------------------------------------------
1 | [default]
2 | FileStorePath=target/data/fix/exchange
3 | ConnectionType=acceptor
4 | StartTime=00:00:00
5 | EndTime=00:00:00
6 | HeartBtInt=30
7 | ValidOrderTypes=1,2,F
8 | SenderCompID=EXCHANGE
9 | UseDataDictionary=Y
10 | DefaultMarketPrice=12.30
11 |
12 | [session]
13 | BeginString=FIX.4.2
14 | TargetCompID=BROKER-A
15 | SocketAcceptPort=9878
16 |
17 | [session]
18 | BeginString=FIX.4.2
19 | TargetCompID=BROKER-B
20 | SocketAcceptPort=9879
21 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %date{ISO8601} %-5level[%logger{0}]- %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/test/java/com/euronextclone/ContinuousMatchingStepDefinitions.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import cucumber.annotation.en.Then;
4 | import cucumber.table.DataTable;
5 |
6 | import java.util.List;
7 |
8 | import static org.hamcrest.Matchers.is;
9 | import static org.junit.Assert.assertThat;
10 |
11 | public class ContinuousMatchingStepDefinitions {
12 |
13 | private final MatchingUnit matchingUnit;
14 |
15 | public ContinuousMatchingStepDefinitions(World world) {
16 | this.matchingUnit = world.getMatchingUnit();
17 | }
18 |
19 | @Then("^best limits are:$")
20 | public void best_limits_are(DataTable limitsTable) throws Throwable {
21 | final List bestLimitRows = limitsTable.asList(BestLimitRow.class);
22 | for (BestLimitRow bestLimitRow : bestLimitRows) {
23 | assertThat(matchingUnit.getBestLimit(bestLimitRow.side), is(bestLimitRow.limit));
24 | }
25 | }
26 |
27 | private static class BestLimitRow {
28 | private OrderSide side;
29 | private Double limit;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/com/euronextclone/MatchingUnitStepDefinitions.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import com.euronextclone.ordertypes.*;
4 | import com.google.common.base.Function;
5 | import com.google.common.base.Predicate;
6 | import com.google.common.collect.FluentIterable;
7 | import cucumber.annotation.en.Given;
8 | import cucumber.annotation.en.Then;
9 | import cucumber.annotation.en.When;
10 | import cucumber.table.DataTable;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.regex.Matcher;
15 | import java.util.regex.Pattern;
16 |
17 | import static junit.framework.Assert.assertEquals;
18 | import static org.hamcrest.CoreMatchers.is;
19 | import static org.junit.Assert.assertThat;
20 |
21 | public class MatchingUnitStepDefinitions {
22 |
23 | private final MatchingUnit matchingUnit;
24 | private final List generatedTrades;
25 |
26 | public MatchingUnitStepDefinitions(World world) {
27 | matchingUnit = world.getMatchingUnit();
28 | generatedTrades = world.getGeneratedTrades();
29 | }
30 |
31 | @Given("^that trading mode for security is \"([^\"]*)\" and phase is \"([^\"]*)\"$")
32 | public void that_trading_mode_for_security_is_and_phase_is(TradingMode tradingMode, TradingPhase phase) throws Throwable {
33 | matchingUnit.setTradingMode(tradingMode);
34 | matchingUnit.setTradingPhase(phase);
35 | }
36 |
37 | @Given("^that reference price is ([0-9]*\\.?[0-9]+)$")
38 | public void that_reference_price_is_(double price) throws Throwable {
39 | matchingUnit.setReferencePrice(price);
40 | }
41 |
42 | @Given("^the following orders are submitted in this order:$")
43 | public void the_following_orders_are_submitted_in_this_order(DataTable orderTable) throws Throwable {
44 |
45 | final List orderRows = orderTable.asList(OrderEntryRow.class);
46 | for (OrderEntryRow orderRow : orderRows) {
47 | matchingUnit.addOrder(new OrderEntry(orderRow.side, orderRow.broker, orderRow.quantity, orderRow.getOrderType()));
48 | }
49 | }
50 |
51 | @When("^class auction completes$")
52 | public void class_auction_completes() throws Throwable {
53 | matchingUnit.auction();
54 | }
55 |
56 | @Then("^the calculated IMP is:$")
57 | public void the_calculated_IMP_is(List imp) {
58 | assertThat(matchingUnit.getIndicativeMatchingPrice(), is(imp.get(0)));
59 | }
60 |
61 | @Then("^the following trades are generated:$")
62 | public void the_following_trades_are_generated(DataTable expectedTradesTable) throws Throwable {
63 | final List expectedTrades = expectedTradesTable.asList(TradeRow.class);
64 | final List actualTrades = FluentIterable.from(generatedTrades).transform(TradeRow.FROM_TRADE).toImmutableList();
65 | assertEquals(expectedTrades, actualTrades);
66 |
67 | generatedTrades.clear();
68 | }
69 |
70 | @Then("^no trades are generated$")
71 | public void no_trades_are_generated() throws Throwable {
72 | List actualTrades = FluentIterable.from(generatedTrades).transform(TradeRow.FROM_TRADE).toImmutableList();
73 | List empty = new ArrayList();
74 | assertEquals(empty, actualTrades);
75 | }
76 |
77 | @Then("^the book is empty$")
78 | public void the_book_is_empty() throws Throwable {
79 |
80 | final List actualBuy = FluentIterable.from(matchingUnit.getOrders(OrderSide.Buy)).transform(OrderBookRow.FROM_Order(matchingUnit)).toImmutableList();
81 | final List actualSell = FluentIterable.from(matchingUnit.getOrders(OrderSide.Sell)).transform(OrderBookRow.FROM_Order(matchingUnit)).toImmutableList();
82 |
83 | assertEquals(new ArrayList(), actualBuy);
84 | assertEquals(new ArrayList(), actualSell);
85 | }
86 |
87 | @Then("^the book looks like:$")
88 | public void the_book_looks_like(DataTable expectedBooks) throws Throwable {
89 |
90 | final List headerColumns = new ArrayList(expectedBooks.raw().get(0));
91 | final int columns = headerColumns.size();
92 | final int sideSize = columns / 2;
93 | for (int i = 0; i < columns; i++) {
94 | headerColumns.set(i, (i < sideSize ? "Buy " : "Sell ") + headerColumns.get(i));
95 | }
96 |
97 | final List> raw = new ArrayList>();
98 | raw.add(headerColumns);
99 | final List> body = expectedBooks.raw().subList(1, expectedBooks.raw().size());
100 | raw.addAll(body);
101 |
102 | final DataTable montageTable = expectedBooks.toTable(raw);
103 | final List rows = montageTable.asList(MontageRow.class);
104 | final List expectedBids = FluentIterable.from(rows).filter(MontageRow.NON_EMPTY_BID).transform(MontageRow.TO_TEST_BID).toImmutableList();
105 | final List expectedAsks = FluentIterable.from(rows).filter(MontageRow.NON_EMPTY_ASK).transform(MontageRow.TO_TEST_ASK).toImmutableList();
106 |
107 | final List actualBuy = FluentIterable.from(matchingUnit.getOrders(OrderSide.Buy)).transform(OrderBookRow.FROM_Order(matchingUnit)).toImmutableList();
108 | final List actualSell = FluentIterable.from(matchingUnit.getOrders(OrderSide.Sell)).transform(OrderBookRow.FROM_Order(matchingUnit)).toImmutableList();
109 |
110 | assertEquals(expectedBids, actualBuy);
111 | assertEquals(expectedAsks, actualSell);
112 | }
113 |
114 | private static class MontageRow {
115 | private String buyBroker;
116 | private Integer buyQuantity;
117 | private String buyPrice;
118 | private String sellBroker;
119 | private Integer sellQuantity;
120 | private String sellPrice;
121 |
122 | public static final Predicate super MontageRow> NON_EMPTY_BID = new Predicate() {
123 | @Override
124 | public boolean apply(final MontageRow input) {
125 | return input.buyBroker != null && !"".equals(input.buyBroker);
126 | }
127 | };
128 |
129 | public static final Function super MontageRow, OrderBookRow> TO_TEST_BID = new Function() {
130 | @Override
131 | public OrderBookRow apply(final MontageRow input) {
132 | final OrderBookRow orderRow = new OrderBookRow();
133 | orderRow.side = OrderSide.Buy;
134 | orderRow.broker = input.buyBroker;
135 | orderRow.price = input.buyPrice;
136 | orderRow.quantity = input.buyQuantity;
137 | return orderRow;
138 | }
139 | };
140 |
141 | public static final Function super MontageRow, OrderBookRow> TO_TEST_ASK = new Function() {
142 | @Override
143 | public OrderBookRow apply(final MontageRow input) {
144 | final OrderBookRow orderRow = new OrderBookRow();
145 | orderRow.side = OrderSide.Sell;
146 | orderRow.broker = input.sellBroker;
147 | orderRow.price = input.sellPrice;
148 | orderRow.quantity = input.sellQuantity;
149 | return orderRow;
150 | }
151 | };
152 |
153 | public static final Predicate super MontageRow> NON_EMPTY_ASK = new Predicate() {
154 | @Override
155 | public boolean apply(final MontageRow input) {
156 | return input.sellBroker != null && !"".equals(input.sellBroker);
157 | }
158 | };
159 | }
160 |
161 |
162 | private static class TradeRow {
163 | private String buyingBroker;
164 | private String sellingBroker;
165 | private int quantity;
166 | private double price;
167 |
168 | public static final Function super Trade, TradeRow> FROM_TRADE = new Function() {
169 | @Override
170 | public TradeRow apply(Trade input) {
171 | TradeRow tradeRow = new TradeRow();
172 | tradeRow.setBuyingBroker(input.getBuyBroker());
173 | tradeRow.setPrice(input.getPrice());
174 | tradeRow.setQuantity(input.getQuantity());
175 | tradeRow.setSellingBroker(input.getSellBroker());
176 | return tradeRow;
177 | }
178 | };
179 |
180 | @Override
181 | public boolean equals(Object o) {
182 | if (this == o) return true;
183 | if (o == null || getClass() != o.getClass()) return false;
184 |
185 | TradeRow tradeRow = (TradeRow) o;
186 |
187 | if (Double.compare(tradeRow.price, price) != 0) return false;
188 | if (quantity != tradeRow.quantity) return false;
189 | if (!buyingBroker.equals(tradeRow.buyingBroker)) return false;
190 | if (!sellingBroker.equals(tradeRow.sellingBroker)) return false;
191 |
192 | return true;
193 | }
194 |
195 | @Override
196 | public int hashCode() {
197 | int result;
198 | long temp;
199 | result = buyingBroker.hashCode();
200 | result = 31 * result + sellingBroker.hashCode();
201 | result = 31 * result + quantity;
202 | temp = price != +0.0d ? Double.doubleToLongBits(price) : 0L;
203 | result = 31 * result + (int) (temp ^ (temp >>> 32));
204 | return result;
205 | }
206 |
207 | @Override
208 | public String toString() {
209 | return "TradeRow{" +
210 | "buyingBroker='" + buyingBroker + '\'' +
211 | ", sellingBroker='" + sellingBroker + '\'' +
212 | ", quantity=" + quantity +
213 | ", price=" + price +
214 | '}';
215 | }
216 |
217 | public void setBuyingBroker(String buyingBroker) {
218 | this.buyingBroker = buyingBroker;
219 | }
220 |
221 | public void setSellingBroker(String sellingBroker) {
222 | this.sellingBroker = sellingBroker;
223 | }
224 |
225 | public void setQuantity(int quantity) {
226 | this.quantity = quantity;
227 | }
228 |
229 | public void setPrice(double price) {
230 | this.price = price;
231 | }
232 | }
233 |
234 | private static class OrderEntryRow {
235 | private static final Pattern PEG = Pattern.compile("Peg(?:\\[(.+)\\])?", Pattern.CASE_INSENSITIVE);
236 | private String broker;
237 | private OrderSide side;
238 | private int quantity;
239 | private String price;
240 |
241 | public OrderType getOrderType() {
242 |
243 | Matcher peg = PEG.matcher(price);
244 | if (peg.matches()) {
245 | String limit = peg.group(1);
246 | return limit != null ? new PegWithLimit(Double.parseDouble(limit)) : new Peg();
247 | }
248 | if ("MTL".compareToIgnoreCase(price) == 0) {
249 | return new MarketToLimit();
250 | }
251 | if ("MO".compareToIgnoreCase(price) == 0) {
252 | return new Market();
253 | }
254 |
255 | return new Limit(Double.parseDouble(price));
256 | }
257 | }
258 |
259 | private static class OrderBookRow {
260 | private String broker;
261 | private OrderSide side;
262 | private int quantity;
263 | private String price;
264 |
265 | public static Function super Order, OrderBookRow> FROM_Order(final MatchingUnit matchingUnit) {
266 | return new Function() {
267 | @Override
268 | public OrderBookRow apply(final Order order) {
269 | final OrderSide side = order.getSide();
270 | final OrderType orderType = order.getOrderType();
271 | final Double bestLimit = matchingUnit.getBestLimit(side);
272 | final OrderBookRow orderRow = new OrderBookRow();
273 |
274 | orderRow.broker = order.getBroker();
275 | orderRow.side = order.getSide();
276 | Double price = orderType.price(side, matchingUnit.getBestLimit(side));
277 | orderRow.price = orderType.displayPrice(price);
278 | orderRow.quantity = order.getQuantity();
279 | return orderRow;
280 | }
281 | };
282 | }
283 |
284 | @Override
285 | public boolean equals(Object o) {
286 | if (this == o) return true;
287 | if (o == null || getClass() != o.getClass()) return false;
288 |
289 | OrderBookRow orderRow = (OrderBookRow) o;
290 |
291 | if (quantity != orderRow.quantity) return false;
292 | if (!broker.equals(orderRow.broker)) return false;
293 | if (price != null ? !price.equals(orderRow.price) : orderRow.price != null) return false;
294 | if (side != orderRow.side) return false;
295 |
296 | return true;
297 | }
298 |
299 | @Override
300 | public int hashCode() {
301 | int result = broker.hashCode();
302 | result = 31 * result + side.hashCode();
303 | result = 31 * result + quantity;
304 | result = 31 * result + (price != null ? price.hashCode() : 0);
305 | return result;
306 | }
307 |
308 | @Override
309 | public String toString() {
310 | return "OrderBookRow{" +
311 | "broker='" + broker + '\'' +
312 | ", side=" + side +
313 | ", quantity=" + quantity +
314 | ", price=" + price +
315 | '}';
316 | }
317 | }
318 | }
--------------------------------------------------------------------------------
/src/test/java/com/euronextclone/RunAllCukesTest.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import cucumber.junit.Cucumber;
4 | import org.junit.runner.RunWith;
5 |
6 | @RunWith(Cucumber.class)
7 | public class RunAllCukesTest {
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/euronextclone/RunFocusCukesTest.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import cucumber.junit.Cucumber;
4 | import org.junit.runner.RunWith;
5 |
6 | @RunWith(Cucumber.class)
7 | @Cucumber.Options(tags = "@focus")
8 | public class RunFocusCukesTest {
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/java/com/euronextclone/World.java:
--------------------------------------------------------------------------------
1 | package com.euronextclone;
2 |
3 | import cucumber.annotation.Before;
4 | import hu.akarnokd.reactive4java.reactive.Observer;
5 |
6 | import javax.annotation.Nonnull;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class World {
11 |
12 | private final List generatedTrades = new ArrayList();
13 | private final MatchingUnit matchingUnit = new MatchingUnit("MSFT");
14 |
15 | public List getGeneratedTrades() {
16 | return generatedTrades;
17 | }
18 |
19 | public MatchingUnit getMatchingUnit() {
20 | return matchingUnit;
21 | }
22 |
23 | @Before
24 | public void setUp() {
25 | matchingUnit.register(new Observer() {
26 | @Override
27 | public void next(Trade trade) {
28 | generatedTrades.add(trade);
29 | }
30 |
31 | @Override
32 | public void error(@Nonnull Throwable throwable) {
33 | }
34 |
35 | @Override
36 | public void finish() {
37 | }
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/Euronext - market-to-limit-orders.feature:
--------------------------------------------------------------------------------
1 | Feature: Examples from the Euronext Market-to-Limit PDF
2 |
3 | Scenario: On a class of Securities traded by auction - Example 1 - Only Market to Limit orders in the order book
4 | Given that trading mode for security is "ByAuction" and phase is "CoreCall"
5 | And that reference price is 10
6 | And the following orders are submitted in this order:
7 | | Broker | Side | Quantity | Price |
8 | | A | Buy | 50 | MTL |
9 | | B | Sell | 40 | MTL |
10 | Then the calculated IMP is:
11 | | 10 |
12 | When class auction completes
13 | Then the following trades are generated:
14 | | Buying broker | Selling broker | Quantity | Price |
15 | | A | B | 40 | 10 |
16 | And the book looks like:
17 | | Broker | Quantity | Price | Price | Quantity | Broker |
18 | | A | 10 | 10 | | | |
19 |
20 |
21 | Scenario: On a class of Securities traded by auction - Example 2 - There are Market to limits, Pure Market orders and limited orders
22 | Given that trading mode for security is "ByAuction" and phase is "CoreCall"
23 | And that reference price is 10
24 | And the following orders are submitted in this order:
25 | | Broker | Side | Quantity | Price |
26 | | A | Buy | 10 | MTL |
27 | | B | Buy | 10 | MO |
28 | | C | Buy | 10 | MTL |
29 | | D | Sell | 10 | 10 |
30 | Then the calculated IMP is:
31 | | 10 |
32 | When class auction completes
33 | Then the following trades are generated:
34 | | Buying broker | Selling broker | Quantity | Price |
35 | | A | D | 10 | 10 |
36 | And the book looks like:
37 | | Broker | Quantity | Price | Price | Quantity | Broker |
38 | | B | 10 | MO | | | |
39 | | C | 10 | MTL | | | |
40 |
41 |
42 | Scenario: On a class of Securities traded by auction - Example 3 - There are Market to limits and Pure Market orders
43 | Given that trading mode for security is "ByAuction" and phase is "CoreCall"
44 | And that reference price is 10
45 | And the following orders are submitted in this order:
46 | | Broker | Side | Quantity | Price |
47 | | A | Buy | 10 | MTL |
48 | | B | Buy | 10 | MO |
49 | | C | Buy | 10 | MTL |
50 | | D | Sell | 5 | 10 |
51 | Then the calculated IMP is:
52 | | 10 |
53 | When class auction completes
54 | Then the following trades are generated:
55 | | Buying broker | Selling broker | Quantity | Price |
56 | | A | D | 5 | 10 |
57 | And the book looks like:
58 | | Broker | Quantity | Price | Price | Quantity | Broker |
59 | | B | 10 | MO | | | |
60 | | C | 10 | MTL | | | |
61 | | A | 5 | 10 | | | |
62 |
63 |
64 | Scenario: On a class of Securities traded on a continuous mode – Call phase - Example 1 - Only Market to Limit orders in the order book
65 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
66 | And that reference price is 10
67 | And the following orders are submitted in this order:
68 | | Broker | Side | Quantity | Price |
69 | | A | Buy | 50 | MTL |
70 | | B | Sell | 40 | MTL |
71 | Then the calculated IMP is:
72 | | 10 |
73 | When class auction completes
74 | Then the following trades are generated:
75 | | Buying broker | Selling broker | Quantity | Price |
76 | | A | B | 40 | 10 |
77 | And the book looks like:
78 | | Broker | Quantity | Price | Price | Quantity | Broker |
79 | | A | 10 | 10 | | | |
80 |
81 |
82 | Scenario: On a class of Securities traded on a continuous mode – Call phase - Example 2 - There are Market to limits and Pure Market orders
83 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
84 | And that reference price is 10
85 | And the following orders are submitted in this order:
86 | | Broker | Side | Quantity | Price |
87 | | A | Buy | 10 | MTL |
88 | | B | Buy | 10 | MO |
89 | | C | Buy | 10 | MTL |
90 | | D | Sell | 10 | 10 |
91 | Then the calculated IMP is:
92 | | 10 |
93 | And the book looks like:
94 | | Broker | Quantity | Price | Price | Quantity | Broker |
95 | | A | 10 | MTL | 10 | 10 | D |
96 | | B | 10 | MO | | | |
97 | | C | 10 | MTL | | | |
98 | When class auction completes
99 | Then the following trades are generated:
100 | | Buying broker | Selling broker | Quantity | Price |
101 | | A | D | 10 | 10 |
102 | And the book looks like:
103 | | Broker | Quantity | Price | Price | Quantity | Broker |
104 | | B | 10 | MO | | | |
105 | | C | 10 | 10 | | | |
106 |
107 |
108 | Scenario: On a class of Securities traded on a continuous mode – Call phase - Example 3 - There are Market to limits, Pure Market orders and Limited orders
109 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
110 | And that reference price is 10
111 | And the following orders are submitted in this order:
112 | | Broker | Side | Quantity | Price |
113 | | A | Buy | 10 | MTL |
114 | | B | Buy | 10 | MO |
115 | | C | Buy | 10 | MTL |
116 | | D | Sell | 5 | 10 |
117 | Then the calculated IMP is:
118 | | 10 |
119 | And the book looks like:
120 | | Broker | Quantity | Price | Price | Quantity | Broker |
121 | | A | 10 | MTL | 10 | 5 | D |
122 | | B | 10 | MO | | | |
123 | | C | 10 | MTL | | | |
124 | When class auction completes
125 | Then the following trades are generated:
126 | | Buying broker | Selling broker | Quantity | Price |
127 | | A | D | 5 | 10 |
128 | And the book looks like:
129 | | Broker | Quantity | Price | Price | Quantity | Broker |
130 | | B | 10 | MO | | | |
131 | | A | 5 | 10 | | | |
132 | | C | 10 | 10 | | | |
133 |
134 |
135 | Scenario: On a class of Securities traded on a continuous mode – Continuous phase - Example 1
136 | There are limit orders in the order book
137 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
138 | And that reference price is 15
139 | And the following orders are submitted in this order:
140 | | Broker | Side | Quantity | Price |
141 | | A | Buy | 10 | 15 |
142 | | B | Buy | 10 | 12 |
143 | | C | Buy | 10 | 10 |
144 | | D | Sell | 25 | MTL |
145 | Then the following trades are generated:
146 | | Buying broker | Selling broker | Quantity | Price |
147 | | A | D | 10 | 15 |
148 | And the book looks like:
149 | | Broker | Quantity | Price | Price | Quantity | Broker |
150 | | B | 10 | 12 | 15 | 15 | D |
151 | | C | 10 | 10 | | | |
152 |
153 |
154 | Scenario: On a class of Securities traded on a continuous mode – Continuous phase - Example 2
155 | There are limit orders and Pure Market orders in the order book
156 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
157 | And that reference price is 12
158 | And the following orders are submitted in this order:
159 | | Broker | Side | Quantity | Price |
160 | | A | Buy | 10 | MO |
161 | | B | Buy | 10 | 12 |
162 | | C | Buy | 10 | 9 |
163 | | D | Sell | 25 | MTL |
164 | And the book looks like:
165 | | Broker | Quantity | Price | Price | Quantity | Broker |
166 | | C | 10 | 9 | 12 | 5 | D |
167 |
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/Euronext - pure market order.feature:
--------------------------------------------------------------------------------
1 | Feature: Examples from the Euronext Pure Market Order PDF
2 |
3 | Scenario: Call Phase - Example 1 - the Market order is totally filled
4 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
5 | And that reference price is 10
6 | And the following orders are submitted in this order:
7 | | Broker | Side | Quantity | Price |
8 | | A | Buy | 50 | MTL |
9 | | B | Buy | 90 | 10.1 |
10 | | C | Buy | 10 | 9.9 |
11 | | D | Sell | 40 | MTL |
12 | | E | Sell | 100 | 10.08 |
13 | | F | Sell | 60 | 10.15 |
14 | Then the calculated IMP is:
15 | | 10.08 |
16 | And the book looks like:
17 | | Broker | Quantity | Price | Price | Quantity | Broker |
18 | | A | 50 | MTL | MTL | 40 | D |
19 | | B | 90 | 10.1 | 10.08 | 100 | E |
20 | | C | 10 | 9.9 | 10.15 | 60 | F |
21 | When the following orders are submitted in this order:
22 | | Broker | Side | Quantity | Price |
23 | | G | Buy | 20 | MO |
24 | Then the calculated IMP is:
25 | | 10.1 |
26 | And the book looks like:
27 | | Broker | Quantity | Price | Price | Quantity | Broker |
28 | | A | 50 | MTL | MTL | 40 | D |
29 | | G | 20 | MO | 10.08 | 100 | E |
30 | | B | 90 | 10.1 | 10.15 | 60 | F |
31 | | C | 10 | 9.9 | | | |
32 | When class auction completes
33 | Then the following trades are generated:
34 | | Buying broker | Selling broker | Quantity | Price |
35 | | A | D | 40 | 10.1 |
36 | | A | E | 10 | 10.1 |
37 | | G | E | 20 | 10.1 |
38 | | B | E | 70 | 10.1 |
39 | And the book looks like:
40 | | Broker | Quantity | Price | Price | Quantity | Broker |
41 | | B | 20 | 10.1 | 10.15 | 60 | F |
42 | | C | 10 | 9.9 | | | |
43 |
44 |
45 | Scenario: Call Phase - Example 2 - the market order is partially filled
46 | Broker G enters a Market Order for the purchase of 20 shares
47 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
48 | And that reference price is 10
49 | And the following orders are submitted in this order:
50 | | Broker | Side | Quantity | Price |
51 | | A | Buy | 40 | MTL |
52 | | D | Sell | 45 | MO |
53 | Then the book looks like:
54 | | Broker | Quantity | Price | Price | Quantity | Broker |
55 | | A | 40 | MTL | MO | 45 | D |
56 | Then the calculated IMP is:
57 | | 10 |
58 | And the following orders are submitted in this order:
59 | | Broker | Side | Quantity | Price |
60 | | G | Buy | 20 | MO |
61 | Then the calculated IMP is:
62 | | 10 |
63 | And the book looks like:
64 | | Broker | Quantity | Price | Price | Quantity | Broker |
65 | | A | 40 | MTL | MO | 45 | D |
66 | | G | 20 | MO | | | |
67 | When class auction completes
68 | Then the following trades are generated:
69 | | Buying broker | Selling broker | Quantity | Price |
70 | | A | D | 40 | 10 |
71 | | G | D | 5 | 10 |
72 | And the book looks like:
73 | | Broker | Quantity | Price | Price | Quantity | Broker |
74 | | G | 15 | MO | | | |
75 |
76 |
77 | Scenario: Call Phase - Example 3 - the Indicative Matching Price is higher than the best limit & equal to the reference price
78 | Broker G enters a sell Limit order at 9,98€ for the sale of 40 shares
79 | Since the number of shares on the bid side fully covers the number on offer, the Indicative
80 | Matching Price is 10€, this being the closest to the closing price on the previous day.
81 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
82 | And that reference price is 10
83 | And the following orders are submitted in this order:
84 | | Broker | Side | Quantity | Price |
85 | | A | Buy | 40 | MO |
86 | | G | Sell | 40 | 9.98 |
87 | Then the calculated IMP is:
88 | | 10 |
89 | And the book looks like:
90 | | Broker | Quantity | Price | Price | Quantity | Broker |
91 | | A | 40 | MO | 9.98 | 40 | G |
92 |
93 |
94 | Scenario: Call Phase - Example 4 - the Indicative Matching Price is equal to the best limit & lower to the reference price Reference price
95 | Broker G enters a sell Limit order at 9,98€ for the purchase of 41 shares
96 | Since the number of shares on the bid side partly covers the number on the ask side, the
97 | Indicative Matching Price adopted is 9.98€.
98 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
99 | And that reference price is 10
100 | And the following orders are submitted in this order:
101 | | Broker | Side | Quantity | Price |
102 | | A | Buy | 40 | MO |
103 | | G | Sell | 41 | 9.98 |
104 | Then the calculated IMP is:
105 | | 9.98 |
106 | And the book looks like:
107 | | Broker | Quantity | Price | Price | Quantity | Broker |
108 | | A | 40 | MO | 9.98 | 41 | G |
109 |
110 |
111 | Scenario: Call Phase - Example 5 - the Indicative Matching Price is equal to the best limit & higher to the reference price Reference price
112 | Broker G enters a sell Limit order at 10,02€ for the purchase of 40 shares
113 | The Indicative Matching Price adopted within the range [10,02;+∞] is 10.02€, since this is the closest to the reference
114 | price of 10€ and the only price allowing 80 shares to be traded.
115 | Given that trading mode for security is "Continuous" and phase is "CoreCall"
116 | And that reference price is 10
117 | And the following orders are submitted in this order:
118 | | Broker | Side | Quantity | Price |
119 | | A | Buy | 40 | MO |
120 | | G | Sell | 40 | 10.02 |
121 | Then the calculated IMP is:
122 | | 10.02 |
123 | And the book looks like:
124 | | Broker | Quantity | Price | Price | Quantity | Broker |
125 | | A | 40 | MO | 10.02 | 40 | G |
126 |
127 |
128 | Scenario: Trading session Phase - Example 1 - the Market order is totally executed upon entry
129 | Broker C enters a Market order for the purchase of 110 shares
130 | The order is executed with 100 shares at 10,2€ and 10 shares at 10.3€ (in compliance with the
131 | collars).
132 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
133 | And that reference price is 10
134 | And the following orders are submitted in this order:
135 | | Broker | Side | Quantity | Price |
136 | | A | Sell | 100 | 10.2 |
137 | | B | Sell | 60 | 10.3 |
138 | When the following orders are submitted in this order:
139 | | Broker | Side | Quantity | Price |
140 | | C | Buy | 110 | MO |
141 | Then the following trades are generated:
142 | | Buying broker | Selling broker | Quantity | Price |
143 | | C | A | 100 | 10.2 |
144 | | C | B | 10 | 10.3 |
145 | And the book looks like:
146 | | Broker | Quantity | Price | Price | Quantity | Broker |
147 | | | | | 10.3 | 50 | B |
148 |
149 |
150 | Scenario: Trading session Phase - Example 2 - the Market order is partially executed upon entry
151 | Broker C enters a Market order for the purchase of 200 shares
152 | The order is partly executed with 100 shares at 10.2€ and 60 at 10.3€ (in compliance with the
153 | collars)
154 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
155 | And that reference price is 10
156 | And the following orders are submitted in this order:
157 | | Broker | Side | Quantity | Price |
158 | | A | Sell | 100 | 10.2 |
159 | | B | Sell | 60 | 10.3 |
160 | When the following orders are submitted in this order:
161 | | Broker | Side | Quantity | Price |
162 | | C | Buy | 200 | MO |
163 | Then the following trades are generated:
164 | | Buying broker | Selling broker | Quantity | Price |
165 | | C | A | 100 | 10.2 |
166 | | C | B | 60 | 10.3 |
167 | And the book looks like:
168 | | Broker | Quantity | Price | Price | Quantity | Broker |
169 | | C | 40 | MO | | | |
170 |
171 |
172 | Scenario: Trading session Phase - Example 3 - there are only Market orders in the order book, trade price is the last traded price
173 | Broker C enters a Market order for the sale of 170 shares
174 | The order is partly executed, with 100 shares at 10€ each, representing the reference price witch
175 | is the last price.
176 | Priority of execution for market orders on the bid side is based on the rule of “First Come, first
177 | served”. The un-executed portion of the ask market order, representing 70 shares, remains on the
178 | order book.
179 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
180 | And that reference price is 10
181 | And the following orders are submitted in this order:
182 | | Broker | Side | Quantity | Price |
183 | | A | Buy | 90 | MO |
184 | | B | Buy | 10 | MO |
185 | Then the book looks like:
186 | | Broker | Quantity | Price | Price | Quantity | Broker |
187 | | A | 90 | MO | | | |
188 | | B | 10 | MO | | | |
189 | When the following orders are submitted in this order:
190 | | Broker | Side | Quantity | Price |
191 | | C | Sell | 170 | MO |
192 | Then the following trades are generated:
193 | | Buying broker | Selling broker | Quantity | Price |
194 | | A | C | 90 | 10 |
195 | | B | C | 10 | 10 |
196 |
197 |
198 | Scenario: Trading session Phase - Example 4 - the best limit is higher that the last traded price
199 | Broker D enters a limit order at 10€ for the sale of 120 shares
200 | The order is executed, with 100 shares at 10.1€ (90 representing the Market Order and 10 the
201 | limit order at 10.1€) and 20 at 10.08€.
202 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
203 | And that reference price is 10
204 | And the following orders are submitted in this order:
205 | | Broker | Side | Quantity | Price |
206 | | A | Buy | 90 | MO |
207 | | B | Buy | 10 | 10.1 |
208 | | C | Buy | 20 | 10.08 |
209 | Then the book looks like:
210 | | Broker | Quantity | Price | Price | Quantity | Broker |
211 | | A | 90 | MO | | | |
212 | | B | 10 | 10.1 | | | |
213 | | C | 20 | 10.08 | | | |
214 | When the following orders are submitted in this order:
215 | | Broker | Side | Quantity | Price |
216 | | D | Sell | 120 | 10 |
217 | Then the following trades are generated:
218 | | Buying broker | Selling broker | Quantity | Price |
219 | | A | D | 90 | 10.1 |
220 | | B | D | 10 | 10.1 |
221 | | C | D | 20 | 10.08 |
222 | And the book is empty
223 |
224 |
225 | Scenario: Trading session Phase - Example 5 - the best limit is lower that the last traded price
226 | Broker D enters a limit order at 10€ for the sale of 120 shares
227 | The order is executed, with 90 shares at 10.3€, 10 shares at 10.1€ and 20 at 10.08€.
228 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
229 | And that reference price is 10.3
230 | And the following orders are submitted in this order:
231 | | Broker | Side | Quantity | Price |
232 | | A | Buy | 90 | MO |
233 | | B | Buy | 10 | 10.1 |
234 | | C | Buy | 20 | 10.08 |
235 | Then the book looks like:
236 | | Broker | Quantity | Price | Price | Quantity | Broker |
237 | | A | 90 | MO | | | |
238 | | B | 10 | 10.1 | | | |
239 | | C | 20 | 10.08 | | | |
240 | When the following orders are submitted in this order:
241 | | Broker | Side | Quantity | Price |
242 | | D | Sell | 120 | 10 |
243 | Then the following trades are generated:
244 | | Buying broker | Selling broker | Quantity | Price |
245 | | A | D | 90 | 10.1 |
246 | | B | D | 10 | 10.1 |
247 | | C | D | 20 | 10.08 |
248 | And the book is empty
249 |
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/Euronext - the peg orders.feature:
--------------------------------------------------------------------------------
1 | Feature: Examples from the Euronext the Peg Orders PDF
2 | See http://www.euronext.com/fic/000/041/609/416094.pdf
3 |
4 | Background:
5 | Given that trading mode for security is "Continuous" and phase is "CoreContinuous"
6 |
7 | Scenario: Example 1
8 | Given that reference price is 10
9 | And the following orders are submitted in this order:
10 | | Broker | Side | Quantity | Price |
11 | | A | Buy | 200 | 10.5 |
12 | | B | Buy | 150 | Peg |
13 | | B | Buy | 70 | Peg |
14 | | B | Buy | 125 | 10.5 |
15 | | C | Sell | 130 | 10.9 |
16 | | C | Sell | 350 | 10.9 |
17 | | D | Sell | 275 | 11 |
18 | And the book looks like:
19 | | Broker | Quantity | Price | Price | Quantity | Broker |
20 | | A | 200 | 10.5 | 10.9 | 130 | C |
21 | | B | 150 | Peg(10.5) | 10.9 | 350 | C |
22 | | B | 70 | Peg(10.5) | 11 | 275 | D |
23 | | B | 125 | 10.5 | | | |
24 | Given the following orders are submitted in this order:
25 | | Broker | Side | Quantity | Price |
26 | | E | Buy | 200 | 10.8 |
27 | Then no trades are generated
28 | And the book looks like:
29 | | Broker | Quantity | Price | Price | Quantity | Broker |
30 | | E | 200 | 10.8 | 10.9 | 130 | C |
31 | | B | 150 | Peg(10.8) | 10.9 | 350 | C |
32 | | B | 70 | Peg(10.8) | 11 | 275 | D |
33 | | A | 200 | 10.5 | | | |
34 | | B | 125 | 10.5 | | | |
35 | Given the following orders are submitted in this order:
36 | | Broker | Side | Quantity | Price |
37 | | G | Buy | 100 | 10.9 |
38 | Then the following trades are generated:
39 | | Buying broker | Selling broker | Quantity | Price |
40 | | G | C | 100 | 10.9 |
41 | And the book looks like:
42 | | Broker | Quantity | Price | Price | Quantity | Broker |
43 | | E | 200 | 10.8 | 10.9 | 30 | C |
44 | | B | 150 | Peg(10.8) | 10.9 | 350 | C |
45 | | B | 70 | Peg(10.8) | 11 | 275 | D |
46 | | A | 200 | 10.5 | | | |
47 | | B | 125 | 10.5 | | | |
48 | Given the following orders are submitted in this order:
49 | | Broker | Side | Quantity | Price |
50 | | G | Sell | 250 | 10.8 |
51 | Then the following trades are generated:
52 | | Buying broker | Selling broker | Quantity | Price |
53 | | E | G | 200 | 10.8 |
54 | | B | G | 50 | 10.8 |
55 | And the book looks like:
56 | | Broker | Quantity | Price | Price | Quantity | Broker |
57 | | A | 200 | 10.5 | 10.9 | 30 | C |
58 | | B | 125 | 10.5 | 10.9 | 350 | C |
59 | | B | 100 | Peg(10.5) | 11 | 275 | D |
60 | | B | 70 | Peg(10.5) | | | |
61 |
62 | Scenario: Example 2
63 | Given that reference price is 10
64 | And the following orders are submitted in this order:
65 | | Broker | Side | Quantity | Price |
66 | | A | Buy | 200 | 11.5 |
67 | | B | Buy | 150 | Peg[11.6] |
68 | | B | Buy | 70 | Peg |
69 | | B | Buy | 125 | 10.5 |
70 | | C | Sell | 130 | 11.8 |
71 | | C | Sell | 350 | 11.9 |
72 | | D | Sell | 275 | 12 |
73 | And the book looks like:
74 | | Broker | Quantity | Price | Price | Quantity | Broker |
75 | | A | 200 | 11.5 | 11.8 | 130 | C |
76 | | B | 150 | Peg(11.5)[11.6] | 11.9 | 350 | C |
77 | | B | 70 | Peg(11.5) | 12 | 275 | D |
78 | | B | 125 | 10.5 | | | |
79 | And the following orders are submitted in this order:
80 | | Broker | Side | Quantity | Price |
81 | | E | Buy | 200 | 11.7 |
82 | And the book looks like:
83 | | Broker | Quantity | Price | Price | Quantity | Broker |
84 | | E | 200 | 11.7 | 11.8 | 130 | C |
85 | | B | 70 | Peg(11.7) | 11.9 | 350 | C |
86 | | B | 150 | Peg(11.6)[11.6] | 12 | 275 | D |
87 | | A | 200 | 11.5 | | | |
88 | | B | 125 | 10.5 | | | |
89 | When the following orders are submitted in this order:
90 | | Broker | Side | Quantity | Price |
91 | | A | Sell | 270 | 11.7 |
92 | Then the following trades are generated:
93 | | Buying broker | Selling broker | Quantity | Price |
94 | | E | A | 200 | 11.7 |
95 | | B | A | 70 | 11.7 |
96 | And the book looks like:
97 | | Broker | Quantity | Price | Price | Quantity | Broker |
98 | | A | 200 | 11.5 | 11.8 | 130 | C |
99 | | B | 150 | Peg(11.5)[11.6] | 11.9 | 350 | C |
100 | | B | 125 | 10.5 | 12 | 275 | D |
101 |
102 | Scenario: Example 3
103 | Given that reference price is 10
104 | And the following orders are submitted in this order:
105 | | Broker | Side | Quantity | Price |
106 | | A | Buy | 200 | 11.5 |
107 | | B | Buy | 150 | Peg[11.6] |
108 | Then the book looks like:
109 | | Broker | Quantity | Price | Price | Quantity | Broker |
110 | | A | 200 | 11.5 | | | |
111 | | B | 150 | Peg(11.5)[11.6] | | | |
112 | And the following orders are submitted in this order:
113 | | Broker | Side | Quantity | Price |
114 | | C | Sell | 200 | 11.5 |
115 | Then the following trades are generated:
116 | | Buying broker | Selling broker | Quantity | Price |
117 | | A | C | 200 | 11.5 |
118 | And the book is empty
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/_intellij-support.feature:
--------------------------------------------------------------------------------
1 | Feature: Cucumber-JVM support in IntelliJ
2 |
3 | @focus
4 | Scenario: Running only scenarios tagged with @focus
5 | This scenario ensures that there is always at least one cuke marked with @focus
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/auction-market-order.feature:
--------------------------------------------------------------------------------
1 | Feature: Auction Phase Matching Rules
2 |
3 | Background:
4 | Given that trading mode for security is "ByAuction" and phase is "CoreCall"
5 | And that reference price is 10
6 |
7 | Scenario: Simplest single trade full match on offsetting limit orders
8 | Given the following orders are submitted in this order:
9 | | Broker | Side | Quantity | Price |
10 | | A | Buy | 50 | 10 |
11 | | B | Sell | 50 | 10 |
12 | When class auction completes
13 | Then the following trades are generated:
14 | | Buying broker | Selling broker | Quantity | Price |
15 | | A | B | 50 | 10 |
16 |
17 | Scenario: Simplest single trade partial match on offsetting limit orders
18 | Given the following orders are submitted in this order:
19 | | Broker | Side | Quantity | Price |
20 | | A | Buy | 30 | 10 |
21 | | B | Sell | 50 | 10 |
22 | When class auction completes
23 | Then the following trades are generated:
24 | | Buying broker | Selling broker | Quantity | Price |
25 | | A | B | 30 | 10 |
26 |
27 | Scenario: Simple multi trade full match on offsetting orders (multiple fills due to to sell side)
28 | Given the following orders are submitted in this order:
29 | | Broker | Side | Quantity | Price |
30 | | A | Buy | 30 | 10 |
31 | | B | Sell | 20 | 10 |
32 | | C | Sell | 10 | 10 |
33 | When class auction completes
34 | Then the following trades are generated:
35 | | Buying Broker | Selling Broker | Quantity | Price |
36 | | A | B | 20 | 10 |
37 | | A | C | 10 | 10 |
38 |
39 | Scenario: Simple multi trade full match on offsetting orders (multiple fills due to to buy side)
40 | Given the following orders are submitted in this order:
41 | | Broker | Side | Quantity | Price |
42 | | A | Buy | 30 | 10 |
43 | | B | Buy | 50 | 10 |
44 | | C | Sell | 80 | 10 |
45 | When class auction completes
46 | Then the following trades are generated:
47 | | Buying Broker | Selling Broker | Quantity | Price |
48 | | A | C | 30 | 10 |
49 | | B | C | 50 | 10 |
50 |
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/basic-order-book-construction.feature:
--------------------------------------------------------------------------------
1 | Feature: Basic order book construction
2 |
3 | Background:
4 | Given that trading mode for security is "ByAuction" and phase is "CoreCall"
5 |
6 | Scenario: Limit buy orders are sorted from high to low
7 | Given the following orders are submitted in this order:
8 | | Broker | Side | Quantity | Price |
9 | | A | Buy | 100 | 10.2 |
10 | | B | Buy | 100 | 10.3 |
11 | | C | Buy | 100 | 10.1 |
12 | | D | Buy | 100 | 10.5 |
13 | Then the book looks like:
14 | | Broker | Quantity | Price | Price | Quantity | Broker |
15 | | D | 100 | 10.5 | | | |
16 | | B | 100 | 10.3 | | | |
17 | | A | 100 | 10.2 | | | |
18 | | C | 100 | 10.1 | | | |
19 |
20 | Scenario: Limit sell orders are sorted from low to high
21 | Given the following orders are submitted in this order:
22 | | Broker | Side | Quantity | Price |
23 | | A | Sell | 100 | 10.2 |
24 | | B | Sell | 100 | 10.3 |
25 | | C | Sell | 100 | 10.1 |
26 | | D | Sell | 100 | 10.5 |
27 | Then the book looks like:
28 | | Broker | Quantity | Price | Price | Quantity | Broker |
29 | | | | | 10.1 | 100 | C |
30 | | | | | 10.2 | 100 | A |
31 | | | | | 10.3 | 100 | B |
32 | | | | | 10.5 | 100 | D |
33 |
34 |
35 | Scenario: Equal limit orders are sorted by their arrival
36 | Given the following orders are submitted in this order:
37 | | Broker | Side | Quantity | Price |
38 | | A | Buy | 100 | 10.2 |
39 | | B | Buy | 100 | 10.2 |
40 | | C | Sell | 100 | 10.3 |
41 | | D | Sell | 100 | 10.3 |
42 | Then the book looks like:
43 | | Broker | Quantity | Price | Price | Quantity | Broker |
44 | | A | 100 | 10.2 | 10.3 | 100 | C |
45 | | B | 100 | 10.2 | 10.3 | 100 | D |
46 |
47 |
48 | Scenario: Market orders are sorted by their arrival
49 | Given the following orders are submitted in this order:
50 | | Broker | Side | Quantity | Price |
51 | | A | Buy | 100 | MO |
52 | | B | Buy | 100 | MTL |
53 | | C | Buy | 100 | MO |
54 | | D | Sell | 100 | MO |
55 | | E | Sell | 100 | MTL |
56 | | F | Sell | 100 | MO |
57 | Then the book looks like:
58 | | Broker | Quantity | Price | Price | Quantity | Broker |
59 | | A | 100 | MO | MO | 100 | D |
60 | | B | 100 | MTL | MTL | 100 | E |
61 | | C | 100 | MO | MO | 100 | F |
62 |
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/indicative-market-price.feature:
--------------------------------------------------------------------------------
1 | Feature: Calculating Indicative Market Price Used in Auction Phase
2 |
3 | Background:
4 | Given that trading mode for security is "Continuous" and phase is "CoreAuction"
5 |
6 | Scenario: - The Indicative Matching Price should be the one clearing most shares in the presence of multiple sell limit prices
7 | Given that reference price is 10
8 | And the following orders are submitted in this order:
9 | | Broker | Side | Quantity | Price |
10 | | A | Buy | 80 | MO |
11 | | G | Sell | 40 | 10.02 |
12 | | H | Sell | 40 | 10.04 |
13 | Then the calculated IMP is:
14 | | 10.04 |
15 |
16 | Scenario: - The Indicative Matching Price should be the one clearing most shares in the presence of multiple buy limit prices
17 | Given that trading mode for security is "ByAuction" and phase is "CoreCall"
18 | And that reference price is 10
19 | And the following orders are submitted in this order:
20 | | Broker | Side | Quantity | Price |
21 | | A | Buy | 40 | 9.98 |
22 | | B | Buy | 40 | 9.96 |
23 | | G | Sell | 80 | MO |
24 | Then the calculated IMP is:
25 | | 9.96 |
26 |
--------------------------------------------------------------------------------
/src/test/resources/com/euronextclone/pure-market-orders.feature:
--------------------------------------------------------------------------------
1 | Feature: Pure market orders
2 |
3 | Scenario: Resting market order is partially filled by incoming limit order
4 | Given the following orders are submitted in this order:
5 | | Broker | Side | Quantity | Price |
6 | | A | Buy | 100 | MO |
7 | | B | Sell | 60 | 10.3 |
8 | Then the following trades are generated:
9 | | Buying broker | Selling broker | Quantity | Price |
10 | | A | B | 60 | 10.3 |
11 | And the book looks like:
12 | | Broker | Quantity | Price | Price | Quantity | Broker |
13 | | A | 40 | MO | | | |
14 |
--------------------------------------------------------------------------------