├── setup.py
├── optiver
├── __init__.py
├── test
│ ├── __init__.py
│ └── main.py
├── trader.py
├── messageService.py
├── MarketState.py
└── autotrader.py
├── sms_0.png
├── sms_1.jpg
├── impression.png
├── front_end
├── js
│ ├── Price.js
│ ├── Trade.js
│ ├── theme.js
│ ├── main.js
│ └── dummy_data.js
├── style.css
└── index.html
├── .gitignore
└── README.md
/setup.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/optiver/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/optiver/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sms_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nout-kleef/HTB-VI/HEAD/sms_0.png
--------------------------------------------------------------------------------
/sms_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nout-kleef/HTB-VI/HEAD/sms_1.jpg
--------------------------------------------------------------------------------
/impression.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nout-kleef/HTB-VI/HEAD/impression.png
--------------------------------------------------------------------------------
/front_end/js/Price.js:
--------------------------------------------------------------------------------
1 | class Price {
2 | constructor(time, price) {
3 | this.time = time;
4 | this.price = price;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/optiver/test/main.py:
--------------------------------------------------------------------------------
1 | def inc(x):
2 | return x + 1
3 |
4 |
5 | def test_answer1():
6 | assert inc(3) == 4
7 |
8 |
9 | def test_answer2():
10 | assert inc(3) == 4
11 |
--------------------------------------------------------------------------------
/front_end/js/Trade.js:
--------------------------------------------------------------------------------
1 | class Trade {
2 | constructor(time, isBuy, price, volume) {
3 | this.x = time;
4 | this.y = price;
5 | this.color = isBuy ? "green" : "red";
6 | this.radius = 10; // implement volume later
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/optiver/trader.py:
--------------------------------------------------------------------------------
1 | from autotrader import start_autotrader
2 | import MarketState
3 |
4 |
5 | class Trader():
6 | def __init__(self):
7 | self.balance = 20000
8 | def startTrading(self):
9 | start_autotrader()
10 | def updateBalance(amount):
11 | print("Updating balance. Current Balance: {0}".format(self.balance))
12 | self.balance = self.balance + amount
13 | print("Balance after update: ".format(self.balance))
14 |
15 |
16 | trader = Trader()
17 | trader.startTrading()
18 |
--------------------------------------------------------------------------------
/optiver/messageService.py:
--------------------------------------------------------------------------------
1 | from secrets import TWILLIO_AUTH_TOKEN, TWILLIO_ACCOUNT_SID
2 | from twilio.rest import Client
3 |
4 | class MessageService():
5 | def __init__(self, number_to_inform):
6 | self.client = Client(TWILLIO_ACCOUNT_SID, TWILLIO_AUTH_TOKEN)
7 | self.number_to_inform = number_to_inform
8 | self.sendInitMessage()
9 |
10 | def sendInitMessage(self):
11 | message = self.client.messages.create(from_='+12058462931',to=self.number_to_inform,body="Hello! Automated trading system is started!")
12 | print(message.sid)
13 |
14 | def sendMessageUpdate(self, percentage_profit):
15 | print('Sending Message')
16 | message = self.client.messages.create(body="Hello! I just made: {0} percentage profit!".format(percentage_profit),from_='+12058462931',to=self.number_to_inform)
17 | print(message.sid)
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/front_end/js/theme.js:
--------------------------------------------------------------------------------
1 | Highcharts.theme = {
2 | colors: ['#058DC7', '#50B432', '#ED561B', '#DDDF00', '#24CBE5', '#64E572',
3 | '#FF9655', '#FFF263', '#6AF9C4'],
4 | chart: {
5 | backgroundColor: {
6 | linearGradient: [0, 0, 500, 500],
7 | stops: [
8 | [0, 'rgb(11, 11, 11)'],
9 | [1, 'rgb(11, 11, 11)']
10 | ]
11 | },
12 | },
13 | title: {
14 | style: {
15 | color: '#fff',
16 | font: 'bold 16px "Trebuchet MS", Verdana, sans-serif'
17 | }
18 | },
19 | subtitle: {
20 | style: {
21 | color: '#999',
22 | font: 'bold 12px "Trebuchet MS", Verdana, sans-serif'
23 | }
24 | },
25 | legend: {
26 | itemStyle: {
27 | font: '9pt Trebuchet MS, Verdana, sans-serif',
28 | color: 'black'
29 | },
30 | itemHoverStyle: {
31 | color: 'gray'
32 | }
33 | }
34 | };
35 | // Apply the theme
36 | Highcharts.setOptions(Highcharts.theme);
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 | cover/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | secrets.py
--------------------------------------------------------------------------------
/front_end/style.css:
--------------------------------------------------------------------------------
1 | html, body, * {
2 | background-color: #111111;
3 | color: white;
4 | }
5 |
6 | div.row {
7 | margin: 0;
8 | }
9 |
10 | h1 {
11 | text-align: center;
12 | }
13 |
14 | #notifications{
15 | box-shadow: 2px 2px 3px 1px white;
16 | margin-right: 10px;
17 | }
18 |
19 | .highcharts-root{
20 | border: 1px solid green;
21 | border-radius: 10px;
22 | }
23 |
24 | #container1, #container2{
25 | margin-bottom: 20px;
26 | z-index: 999;
27 | margin-left: 10px;
28 | margin-right: 5px;
29 | box-shadow: 2px 2px 3px 1px white;
30 | }
31 |
32 |
33 | body {
34 | min-height: 450px;
35 | height: 100vh;
36 | margin: 0;
37 | background: radial-gradient(circle, darken(dodgerblue, 10%), #1f4f96, #1b2949, #000);
38 | }
39 |
40 | .stage {
41 | height: 300px;
42 | width: 500px;
43 | margin: auto;
44 | position: absolute;
45 | top: 0; right: 0; bottom: 0; left: 0;
46 | perspective: 9999px;
47 | transform-style: preserve-3d;
48 | }
49 |
50 | .layer {
51 | width: 100%;
52 | height: 100%;
53 | position: absolute;
54 | transform-style: preserve-3d;
55 | animation: ಠ_ಠ 5s infinite alternate ease-in-out -7.5s;
56 | animation-fill-mode: forwards;
57 | transform: rotateY(40deg) rotateX(33deg) translateZ(0);
58 | }
59 |
60 | .layer:after {
61 | font: 150px/0.65 'Pacifico', 'Kaushan Script', Futura, 'Roboto', 'Trebuchet MS', Helvetica, sans-serif;
62 | content: 'Pure\A css!';
63 | white-space: pre;
64 | text-align: center;
65 | height: 100%;
66 | width: 100%;
67 | position: absolute;
68 | top: 50px;
69 | color: darken(#fff, 4%);
70 | letter-spacing: -2px;
71 | text-shadow: 4px 0 10px hsla(0, 0%, 0%, .13);
72 | }
73 |
74 | $i: 1;
75 | $NUM_LAYERS: 20;
76 | @for $i from 1 through $NUM_LAYERS {
77 | .layer:nth-child(#{$i}):after{
78 | transform: translateZ(($i - 1) * -1.5px);
79 | }
80 | }
81 |
82 | .layer:nth-child(n+#{round($NUM_LAYERS/2)}):after {
83 | -webkit-text-stroke: 3px hsla(0, 0%, 0%, .25);
84 | }
85 |
86 | .layer:nth-child(n+#{round($NUM_LAYERS/2 + 1)}):after {
87 | -webkit-text-stroke: 15px dodgerblue;
88 | text-shadow: 6px 0 6px darken(dodgerblue,35%),
89 | 5px 5px 5px darken(dodgerblue,40%),
90 | 0 6px 6px darken(dodgerblue,35%);
91 | }
92 |
93 | .layer:nth-child(n+#{round($NUM_LAYERS/2 + 2)}):after {
94 | -webkit-text-stroke: 15px darken(dodgerblue, 10%);
95 | }
96 |
97 | .layer:last-child:after {
98 | -webkit-text-stroke: 17px hsla(0, 0%, 0%, .1);
99 | }
100 |
101 | .layer:first-child:after{
102 | color: #fff;
103 | text-shadow: none;
104 | }
105 |
106 | @keyframes ಠ_ಠ {
107 | 100% { transform: rotateY(-40deg) rotateX(-43deg); }
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hack The Burgh - VI
2 | During a sleep depriving but rewarding weekend at **Hack the Burgh**, we set out to build an **automated trading bot**.
3 | This challenge was set up by Optiver, and the goal was to find a trading strategy that would allow the bot to "out-trade" the competition.
4 |
5 | Both me and my team had little to no knowledge of the FinTech world, and it was interesting to gain some insight into how latency (or rather, lack thereof) allows one trader to gain an edge over the others!
6 |
7 | I'm very proud of what my team accomplished: apart from learning how to program an automated trading strategy, we ended up winning the Optiver challenge! 🥳 📈
8 |
9 | # Project - Trading Bot for the Optiver Challenge
10 |
11 | The Challenge is to create a trading algorithm to make money by buy and selling stocks on two indeces : S&P500 (SP) and Eurostoxx (ESX).
12 |
13 | Our project is composed of three parts:
14 |
15 | 1) The trading algorithm itself
16 | 2) An SMS alert functionality to communicate profits and losses
17 | 3) A Web page for data visualization
18 |
19 | ## **1 - Algorithm**
20 | We use the Ichimoku Cloud Indicator, a fairly safe and simple strategy especially for Microtrading.
21 | The indicator is based on four components:
22 | 1) Conversion Line -> midpoint of the last 9 average prices
23 | 2) Base Line -> midpoint of the last 26 average prices
24 | 3) Leading Span A -> midpoint of Conversion Line and Base Line
25 | 4) Leading Span B -> midpoint of the last 52 average prices
26 |
27 | **Trading Strategy**:
28 | The Leading Spans consitute the boundaries of the "cloud". When Leading Span A is moving above Leading Span B it indicates an uptrend is gaining momentum. Conversly, Leading Span B above means a downward trend is taking place. A thin cloud shows indecision and a potentially weakening trend, whereas a wide one means it is a good time to start trading.
29 |
30 | If the trend is Up and the Conversion Line falls below the Base Line, then it is time to buy stocks. Similarly, when the crossover of Conversion Line and Base Line happens in the opposite direction, it is time to sell.
31 | To contain risk, we are allowing the bot to buy only two stocks on each market (four in total) for every "trading period".
32 | 
33 |
34 | ## **2 - SMS alert**
35 | The SMS alert system will send a notification to the user when a trade is closed, communicating losses and profit.
36 |
37 | Some screenshots of the SMS functionality:
38 |
39 |  |  |
40 |
41 |
42 | ## **3 - front-end**
43 | 1) `cd front_end/`
44 | 2) `python -m SimpleHTTPServer`
45 | 3) go to `0.0.0.0:8000`
46 |
47 | The following shows an impression of what our front-end looks like.
48 | The graphs show the mid-market prices for the SP and ESX instruments, respectively.
49 | We plan to broadcast the trades our bot makes over SocketIO, which we can also show in the graphs as a scatterplot.
50 | NB: currently, the front-end only shows static data. When we get the SocketIO integration set up, we hope that we can update the chart in real time.
51 | 
52 |
53 | ## Authors
54 | [Mihail Yonchev](https://github.com/slaifan)
55 | [Nout Kleef](https://github.com/nout-kleef)
56 | [Vincenzo Incutti](https://github.com/enzo-inc)
57 |
--------------------------------------------------------------------------------
/optiver/MarketState.py:
--------------------------------------------------------------------------------
1 |
2 | from messageService import MessageService
3 | from secrets import PHONE_NUMBER
4 |
5 | class MarketState():
6 | def __init__(self, stock, feedcode):
7 | self.messageService = MessageService(PHONE_NUMBER)
8 | self.stock = stock
9 | self.feedcode = feedcode
10 | self.buy_prices = list()
11 | self.sell_prices = list()
12 | self.entries = 0
13 | self.buy_price = None
14 | self.last_bought_price = None
15 | self.positions = 0
16 |
17 | def addEntry(self, buy_price, sell_price):
18 | if self.entries > 52:
19 | self.buy_prices.pop(0)
20 | self.sell_prices.pop(0)
21 | self.buy_prices.append(buy_price)
22 | self.sell_prices.append(sell_price)
23 | self.entries = self.entries + 1
24 | print(self.buy_prices)
25 | print('entries', self.entries)
26 |
27 | def isEligibleForTradeBuy(self, buy_price):
28 | if self.entries < 52:
29 | return False
30 | conversionLine = self.__calcConversionLine(self.buy_prices)
31 | baseLine = self.__calcBaseLine(self.buy_prices)
32 | cloudPoint = self.__getCloud(conversionLine, baseLine, self.buy_prices)
33 | print(self.stock)
34 | print('Conversion Line over Base Line: ', conversionLine > baseLine)
35 | print('Positions:', self.positions)
36 | print('Cloud Point: ', cloudPoint)
37 | print('Buy Price ', buy_price)
38 | print('----------------------')
39 | if self.__isAboveCloud(cloudPoint, buy_price) and self.positions == 0 and conversionLine > baseLine:
40 | self.positions = self.positions + 1
41 | self.buy_price = buy_price
42 | return True
43 | return False
44 |
45 | def isEligibleForTradeSell(self, sell_price):
46 | if self.entries < 52:
47 | return False
48 | conversionLine = self.__calcConversionLine(self.sell_prices)
49 | baseLine = self.__calcBaseLine(self.sell_prices)
50 | cloudPoint = self.__getCloud(conversionLine, baseLine, self.sell_prices)
51 | if conversionLine < baseLine and self.buy_price and self.buy_price < sell_price:
52 | self.positions = 0
53 | try:
54 | percentage_difference = ((float(sell_price)-self.buy_price)/self.buy_price)*100
55 | self.messageService.sendMessageUpdate(percentage_difference)
56 | except:
57 | print('[ERROR]: Messaging service error!')
58 | pass
59 | return True
60 | return False
61 |
62 | def __calcConversionLine(self, prices):
63 | target_list = prices[-1:-10:-1]
64 | highest = max(target_list)
65 | lowest = min(target_list)
66 | return (highest+lowest)/2
67 |
68 | def __calcBaseLine(self, prices):
69 | target_list = prices[-1:-27:-1]
70 | highest = max(target_list)
71 | lowest = min(target_list)
72 | return (highest+lowest)/2
73 |
74 | def __calcLeadingSpanA(self, conversionLinePrice, baseLinePrice):
75 | return (conversionLinePrice + baseLinePrice) / 2
76 |
77 | def __calcLeadingSpanB(self, prices):
78 | target_list = prices[-1:-52:-1]
79 | highest = max(target_list)
80 | lowest = min(target_list)
81 | return (highest+lowest)/2
82 |
83 | def __getCloud(self, conversionLinePrice, baseLinePrice, prices):
84 | leadingSpanA = self.__calcLeadingSpanA(conversionLinePrice, baseLinePrice)
85 | leadingSpanB = self.__calcLeadingSpanB(prices)
86 | return max(leadingSpanA, leadingSpanB)
87 |
88 | def __isAboveCloud(self, cloudPrice, currentAskPrice):
89 | return currentAskPrice > cloudPrice
--------------------------------------------------------------------------------
/front_end/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Ichimoku Cloud Indicator trading bot
10 |
11 |
12 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Ichimoku Cloud Indicator trading bot
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
SP MidMarket Prices
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
ESX MidMarket Prices
53 |
54 |
55 | sold 2 shares of ESX-FUTURE at 33403
56 | bought 5 shares
57 | of
58 | ESX-FUTURE at 3398
59 | sold 2 shares of
60 | ESX-FUTURE at
61 | 3418
62 | bought 5 shares
63 | of ESX-FUTURE at
64 | 2918
65 | bought 2 shares
66 | of SP-FUTURE at
67 | 2916
68 | bought 1 share
69 | of ESX-FUTURE at
70 | 2900
71 | bought 2 shares
72 | of SP-FUTURE at
73 | 2916
74 | bought 1 share
75 | of ESX-FUTURE at
76 | 3410
77 | bought 2 shares
78 | of SP-FUTURE at
79 | 2926
80 | bought 2 shares
81 | of SP-FUTURE at
82 | 2816
83 | bought 2 shares
84 | of SP-FUTURE at
85 | 2916
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/front_end/js/main.js:
--------------------------------------------------------------------------------
1 | const DUMMY = true;
2 | let SP_prices = [];
3 | let ESX_prices = [];
4 | let SP_trades = [];
5 | let ESX_trades = [];
6 | let ws;
7 |
8 | window.onload = main();
9 |
10 | function main() {
11 | console.info("page loaded");
12 | if (DUMMY) {
13 | SP_prices = dummy_SP_prices;
14 | ESX_prices = dummy_ESX_prices;
15 | SP_trades = dummy_SP_trades;
16 | ESX_trades = dummy_ESX_trades;
17 | updateGraph();
18 | } else {
19 | // TODO: live data
20 | startListening();
21 | }
22 | }
23 |
24 | function startListening() {
25 | // ws = new WebSocket('ws://');
26 | // ws.onopen = function () {
27 | // console.log('Connected');
28 | // };
29 | // ws.onmessage = updateReceived;
30 | }
31 |
32 | // TYPE=PRICE|FEEDCODE=FOOBAR|BID_PRICE=10.0|BID_VOLUME=100|ASK_PRICE=11.0|ASK_VOLUME=20
33 | // TYPE=TRADE|FEEDCODE=FOOBAR|BUY=TRUE|PRICE=22.0|VOLUME=100
34 | function updateReceived(msg) {
35 | // check which list this update should go to
36 | comps = msg.split("|");
37 | if (comps[0] == "TYPE=PRICE") {
38 | let bid_price = Number(comps[2].split("=")[1]);
39 | let bid_volume = Number(comps[3].split("=")[1]);
40 | let ask_price = Number(comps[4].split("=")[1]);
41 | let ask_volume = Number(comps[5].split("=")[1]);
42 | let mid_market = (bid_price + ask_price) / 2;
43 | if (comps[1] == "FEEDCODE=SP-FUTURE") {
44 | SP_prices.push(mid_market);
45 | } else {
46 | ESX_prices.push(mid_market);
47 | }
48 | } else {
49 | let isBuy = compos[2] == "BUY=TRUE";
50 | let price = Number(comps[3].split("=")[1]);
51 | let volume = Number(comps[4].split("=")[1]);
52 | notify(isBuy ? "bought" : "sold", volume, comps[1].split("=")[1], price);
53 | if (comps[1] == "FEEDCODE=SP-FUTURE") {
54 | SP_trades.push(new Trade(0, isBuy, price, volume));
55 | } else {
56 | ESX_trades.push(new Trade(0, isBuy, price, volume));
57 | }
58 | }
59 | // redraw graph with new data
60 | updateGraph();
61 | }
62 |
63 | function notify(action, vol, instr, price) {
64 | let icon = "";
65 | if (action == "bought") {
66 | icon = " ";
67 | } else if (action == "sold") {
68 | icon = " ";
69 | }
70 | const msg = "" + icon + "" + action + " " + vol + " shares of " + instr + " at " + price + "
";
71 | const currHtml = document.getElementById("notifications").innerHTML;
72 | document.getElementById("notifications").innerHTML = msg + currHtml;
73 | }
74 |
75 | function updateGraph() {
76 | // TODO: convert lists to JSON
77 | let chart1 = Highcharts.chart('container1', {
78 | chart: {
79 | zoomType: 'x'
80 | },
81 | title: {
82 | text: 'SP over time',
83 | color: "white"
84 | },
85 | xAxis: {
86 | type: 'datetime'
87 | },
88 | yAxis: {
89 | },
90 | legend: {
91 | enabled: false
92 | },
93 | plotOptions: {
94 | area: {
95 | fillColor: {
96 | linearGradient: {
97 | x1: 0,
98 | y1: 0,
99 | x2: 0,
100 | y2: 1
101 | },
102 | stops: [
103 | [0, Highcharts.getOptions().colors[0]],
104 | [1, Highcharts.color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
105 | ]
106 | },
107 | marker: {
108 | radius: 2
109 | },
110 | lineWidth: 1,
111 | states: {
112 | hover: {
113 | lineWidth: 1
114 | }
115 | },
116 | threshold: null
117 | }
118 | },
119 |
120 | series: [{
121 | type: 'line',
122 | name: 'SP mid-market price',
123 | data: SP_prices
124 | }
125 | ]
126 | });
127 |
128 | let chart2 = Highcharts.chart('container2', {
129 | chart: {
130 | zoomType: 'x'
131 | },
132 | title: {
133 | text: 'ESX over time'
134 | },
135 | xAxis: {
136 | type: 'datetime'
137 | },
138 | yAxis: {
139 | },
140 | legend: {
141 | enabled: false
142 | },
143 | plotOptions: {
144 | area: {
145 | fillColor: {
146 | linearGradient: {
147 | x1: 0,
148 | y1: 0,
149 | x2: 0,
150 | y2: 1
151 | },
152 | stops: [
153 | [0, Highcharts.getOptions().colors[0]],
154 | [1, Highcharts.color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
155 | ]
156 | },
157 | marker: {
158 | radius: 2
159 | },
160 | lineWidth: 1,
161 | states: {
162 | hover: {
163 | lineWidth: 1
164 | }
165 | },
166 | threshold: null
167 | }
168 | },
169 |
170 | series: [{
171 | type: 'line',
172 | name: 'ESX mid-market price',
173 | data: ESX_prices
174 | }
175 | ]
176 | });
177 | }
178 |
--------------------------------------------------------------------------------
/front_end/js/dummy_data.js:
--------------------------------------------------------------------------------
1 | let dummy_SP_prices = [
2 | 2912.25,
3 | 2912.5,
4 | 2912.25,
5 | 2912.25,
6 | 2912.0,
7 | 2911.75,
8 | 2911.75,
9 | 2911.5,
10 | 2911.5,
11 | 2911.5,
12 | 2912.25,
13 | 2912.25,
14 | 2912.0,
15 | 2911.75,
16 | 2911.5,
17 | 2911.5,
18 | 2911.5,
19 | 2911.75,
20 | 2911.75,
21 | 2911.75,
22 | 2910.75,
23 | 2910.5,
24 | 2910.5,
25 | 2910.5,
26 | 2910.5,
27 | 2910.5,
28 | 2910.25,
29 | 2910.25,
30 | 2910.5,
31 | 2910.5,
32 | 2910.5,
33 | 2910.75,
34 | 2910.75,
35 | 2910.5,
36 | 2911.25,
37 | 2911.25,
38 | 2911.5,
39 | 2911.25,
40 | 2911.5,
41 | 2911.5,
42 | 2911.25,
43 | 2911.25,
44 | 2911.25,
45 | 2911.25,
46 | 2911.25,
47 | 2911.25,
48 | 2911.25,
49 | 2911.5,
50 | 2910.75,
51 | 2911.25,
52 | 2911.25,
53 | 2911.25,
54 | 2911.5,
55 | 2911.0,
56 | 2912.25,
57 | 2912.25,
58 | 2912.25,
59 | 2912.25,
60 | 2912.25,
61 | 2911.75,
62 | 2912.0,
63 | 2912.25,
64 | 2912.5,
65 | 2912.25,
66 | 2912.25,
67 | 2912.25,
68 | 2912.25,
69 | 2912.25,
70 | 2912.0,
71 | 2912.0,
72 | 2911.75,
73 | 2911.75,
74 | 2912.25,
75 | 2912.25,
76 | 2912.5,
77 | 2912.25,
78 | 2912.25,
79 | 2912.25,
80 | 2912.25,
81 | 2912.25,
82 | 2910.75,
83 | 2911.25,
84 | 2911.5,
85 | 2912.0,
86 | 2912.0,
87 | 2912.0,
88 | 2912.0,
89 | 2912.0,
90 | 2912.0,
91 | 2911.75,
92 | 2913.5,
93 | 2914.75,
94 | 2915.5,
95 | 2915.5,
96 | 2915.25,
97 | 2915.25,
98 | 2915.25,
99 | 2915.25,
100 | 2915.25,
101 | 2915.25,
102 | 2915.25,
103 | 2915.5,
104 | 2915.25,
105 | 2915.75,
106 | 2915.5,
107 | 2915.0,
108 | 2915.0,
109 | 2915.0,
110 | 2915.0,
111 | 2914.25,
112 | 2914.25,
113 | 2914.25,
114 | 2914.75,
115 | 2914.75,
116 | 2915.25,
117 | 2914.25,
118 | 2915.5,
119 | 2915.5,
120 | 2915.75,
121 | 2915.75,
122 | 2915.75,
123 | 2916.0,
124 | 2916.0,
125 | 2916.25,
126 | 2915.75,
127 | 2915.75,
128 | 2916.0,
129 | 2916.0,
130 | 2916.0,
131 | 2915.5,
132 | 2915.5,
133 | 2915.25,
134 | 2915.25,
135 | 2915.25,
136 | 2915.25,
137 | 2915.25,
138 | 2915.25,
139 | 2915.0,
140 | 2915.0,
141 | 2915.25,
142 | 2915.25,
143 | 2915.75,
144 | 2915.75,
145 | 2915.75,
146 | 2915.75,
147 | 2915.75,
148 | 2915.75,
149 | 2916.25,
150 | 2916.25,
151 | 2915.75,
152 | 2916.0,
153 | 2916.0,
154 | 2916.25,
155 | 2916.25,
156 | 2916.25,
157 | 2915.75,
158 | 2915.5,
159 | 2913.0,
160 | 2912.75,
161 | 2912.75,
162 | 2913.25,
163 | 2913.25
164 | ];
165 | let dummy_ESX_prices = [
166 |
167 | 3353.0,
168 | 3353.0,
169 | 3353.0,
170 | 3353.0,
171 | 3353.25,
172 | 3353.75,
173 | 3354.0,
174 | 3354.0,
175 | 3354.0,
176 | 3354.0,
177 | 3354.0,
178 | 3354.0,
179 | 3354.0,
180 | 3353.75,
181 | 3353.75,
182 | 3353.25,
183 | 3353.25,
184 | 3353.0,
185 | 3353.75,
186 | 3353.25,
187 | 3353.25,
188 | 3353.0,
189 | 3352.75,
190 | 3352.5,
191 | 3352.0,
192 | 3352.25,
193 | 3352.25,
194 | 3352.0,
195 | 3351.75,
196 | 3351.5,
197 | 3351.5,
198 | 3352.25,
199 | 3353.25,
200 | 3353.0,
201 | 3352.25,
202 | 3352.5,
203 | 3352.5,
204 | 3352.5,
205 | 3352.5,
206 | 3352.5,
207 | 3352.25,
208 | 3352.25,
209 | 3352.25,
210 | 3352.0,
211 | 3351.75,
212 | 3351.5,
213 | 3351.5,
214 | 3351.5,
215 | 3351.5,
216 | 3351.5,
217 | 3351.5,
218 | 3351.25,
219 | 3351.5,
220 | 3351.5,
221 | 3351.5,
222 | 3351.75,
223 | 3351.75,
224 | 3350.75,
225 | 3350.5,
226 | 3350.75,
227 | 3351.0,
228 | 3350.75,
229 | 3351.0,
230 | 3351.0,
231 | 3351.0,
232 | 3351.25,
233 | 3351.5,
234 | 3352.0,
235 | 3352.0,
236 | 3352.0,
237 | 3352.0,
238 | 3352.0,
239 | 3352.0,
240 | 3350.75,
241 | 3351.0,
242 | 3350.75,
243 | 3350.75,
244 | 3351.0,
245 | 3351.0,
246 | 3351.0,
247 | 3351.25,
248 | 3351.25,
249 | 3351.25,
250 | 3351.25,
251 | 3351.25,
252 | 3351.25,
253 | 3351.25,
254 | 3350.5,
255 | 3350.5,
256 | 3350.5,
257 | 3350.5,
258 | 3350.75,
259 | 3350.0,
260 | 3350.0,
261 | 3349.75,
262 | 3350.25,
263 | 3350.25,
264 | 3350.25,
265 | 3350.0,
266 | 3350.0,
267 | 3350.0,
268 | 3350.25,
269 | 3350.25,
270 | 3350.5,
271 | 3350.5,
272 | 3350.5,
273 | 3350.25,
274 | 3350.25,
275 | 3350.0,
276 | 3348.5,
277 | 3348.5,
278 | 3348.5,
279 | 3348.5,
280 | 3348.75,
281 | 3348.75,
282 | 3349.0,
283 | 3349.0,
284 | 3349.0,
285 | 3349.0,
286 | 3346.25,
287 | 3346.5,
288 | 3346.5,
289 | 3346.25,
290 | 3346.25,
291 | 3346.0,
292 | 3345.75,
293 | 3346.0,
294 | 3346.25,
295 | 3346.5,
296 | 3346.5,
297 | 3346.5,
298 | 3346.25,
299 | 3346.25,
300 | 3346.0,
301 | 3346.0,
302 | 3346.0,
303 | 3345.75,
304 | 3345.75,
305 | 3346.25,
306 | 3346.5,
307 | 3346.5,
308 | 3346.75,
309 | 3346.75,
310 | 3346.75,
311 | 3346.75,
312 | 3347.0,
313 | 3347.0,
314 | 3348.25,
315 | 3348.75,
316 | 3350.0,
317 | 3350.0,
318 | 3349.75,
319 | 3349.5,
320 | 3349.5,
321 | 3349.5,
322 | 3349.5,
323 | 3348.75,
324 | 3348.5,
325 | 3348.5,
326 | 3348.5,
327 | 3348.5
328 | ];
329 | let dummy_SP_trades = [];
330 | let dummy_ESX_trades = [];
331 |
--------------------------------------------------------------------------------
/optiver/autotrader.py:
--------------------------------------------------------------------------------
1 | import socket
2 | import select
3 | from MarketState import MarketState
4 | from secrets import USERNAME, PASSWORD
5 |
6 | REMOTE_IP = "35.179.45.135"
7 | UDP_ANY_IP = ""
8 |
9 | # -------------------------------------
10 | # EML code (EML is execution market link)
11 | # -------------------------------------
12 |
13 | EML_UDP_PORT_LOCAL = 8078
14 | EML_UDP_PORT_REMOTE = 8001
15 |
16 | eml_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
17 | eml_sock.bind((UDP_ANY_IP, EML_UDP_PORT_LOCAL))
18 |
19 |
20 | # -------------------------------------
21 | # IML code (IML is information market link)
22 | # -------------------------------------
23 |
24 | IML_UDP_PORT_LOCAL = 7078
25 | IML_UDP_PORT_REMOTE = 7001
26 | IML_INIT_MESSAGE = "TYPE=SUBSCRIPTION_REQUEST"
27 |
28 | iml_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
29 | iml_sock.bind((UDP_ANY_IP, IML_UDP_PORT_LOCAL))
30 |
31 |
32 | # -------------------------------------
33 | # Auto trader
34 | # -------------------------------------
35 |
36 | def start_autotrader():
37 | subscribe()
38 | event_listener()
39 |
40 |
41 | def subscribe():
42 | iml_sock.sendto(IML_INIT_MESSAGE.encode(),
43 | (REMOTE_IP, IML_UDP_PORT_REMOTE))
44 |
45 | def event_listener():
46 | """
47 | Wait for messages from the exchange and
48 | call handle_message on each of them.
49 | """
50 | marketStateESX = MarketState('ESX', 'ESX-FUTURE')
51 | marketStateSP = MarketState('SP', 'SP-FUTURE')
52 | while True:
53 | ready_socks, _, _ = select.select([iml_sock, eml_sock], [], [])
54 | for socket in ready_socks:
55 | data, addr = socket.recvfrom(1024)
56 | message = data.decode('utf-8')
57 | handle_message(message, marketStateESX, marketStateSP)
58 |
59 | def place_order_if_eligible_sell(market, bid_price, volume):
60 | if market.isEligibleForTradeSell(bid_price):
61 | send_order(market.feedcode,"SELL",bid_price,volume)
62 | print("Selling {0} at: {1}, Volume: {2}".format(market.stock, bid_price, volume))
63 |
64 | def place_order_if_eligble_buy(market, ask_price, volume):
65 | if market.isEligibleForTradeBuy(ask_price):
66 | send_order(market.feedcode,"BUY",ask_price,volume)
67 | print("Buying {0} at: {1}, Volume: {2}".format(market.stock, ask_price, volume))
68 |
69 | """
70 | Adds entry to market.
71 | Calls place_order_if_eligible_sell
72 | Calls place_order_if_eligble_buy
73 | """
74 | def handle_server_message_for_marketState(market, bid_price, ask_price):
75 | add_entry_to_market(market, bid_price, ask_price)
76 | place_order_if_eligible_sell(market, bid_price, 0)
77 | place_order_if_eligble_buy(market, ask_price, 0)
78 |
79 | def add_entry_to_market(market, bid_price, ask_price):
80 | market.addEntry(bid_price, ask_price)
81 |
82 | """
83 | Handles an entry message from the server [Either type: PRICE or TRADE]
84 | Receives message and a reference to marketStateESX and marketStateSP
85 | On message received from server its adding the entry to the appropriate market and checks if eiligble to open position.
86 | If yes, it opens a position using open_position().
87 | """
88 | def handle_message(message, marketStateESX, marketStateSP):
89 | comps = message.split("|")
90 |
91 | if len(comps) == 0:
92 | print(f"Invalid message received: {message}")
93 | return
94 |
95 | type = comps[0]
96 |
97 | if type == "TYPE=PRICE":
98 |
99 | feedcode = comps[1].split("=")[1]
100 | bid_price = float(comps[2].split("=")[1])
101 | bid_volume = int(comps[3].split("=")[1])
102 | ask_price = float(comps[4].split("=")[1])
103 | ask_volume = int(comps[5].split("=")[1])
104 |
105 | if feedcode == "SP-FUTURE":
106 | handle_server_message_for_marketState(marketStateSP, bid_price, ask_price)
107 |
108 | if feedcode == "ESX-FUTURE":
109 | handle_server_message_for_marketState(marketStateESX, bid_price, ask_price)
110 |
111 | if type == "TYPE=TRADE":
112 |
113 | feedcode = comps[1].split("=")[1]
114 | side = comps[2].split("=")[1]
115 | traded_price = float(comps[3].split("=")[1])
116 | traded_volume = int(comps[4].split("=")[1])
117 |
118 | if type == "TYPE=ORDER_ACK":
119 |
120 | if comps[1].split("=")[0] == "ERROR":
121 | error_message = comps[1].split("=")[1]
122 | print(f"Order was rejected because of error {error_message}.")
123 | return
124 |
125 | feedcode = comps[1].split("=")[1]
126 | traded_price = float(comps[2].split("=")[1])
127 |
128 | # This is only 0 if price is not there, and volume became 0 instead.
129 | # Possible cause: someone else got the trade instead of you.
130 | if traded_price == 0:
131 | print(f"Unable to get trade on: {feedcode}")
132 | return
133 |
134 | traded_volume = int(comps[3].split("=")[1])
135 |
136 |
137 |
138 | def send_order(target_feedcode, action, target_price, volume):
139 | """
140 | Send an order to the exchange.
141 | :param target_feedcode: The feedcode, either "SP-FUTURE" or "ESX-FUTURE"
142 | :param action: "BUY" or "SELL"
143 | :param target_price: Price you want to trade at
144 | :param volume: Volume you want to trade at. Please start with 10 and go from there. Don't go crazy!
145 | :return:
146 | Example:
147 | If you want to buy 100 SP-FUTURES at a price of 3000:
148 | - send_order("SP-FUTURE", "BUY", 3000, 100)
149 | """
150 | order_message = f"TYPE=ORDER|USERNAME={USERNAME}|PASSWORD={PASSWORD}|FEEDCODE={target_feedcode}|ACTION={action}|PRICE={target_price}|VOLUME={volume}"
151 | print(f"[SENDING ORDER] {order_message}")
152 | eml_sock.sendto(order_message.encode(), (REMOTE_IP, EML_UDP_PORT_REMOTE))
153 |
154 |
155 | # -------------------------------------
156 | # Main
157 | # -------------------------------------
158 |
159 | if __name__ == "__main__":
160 | start_autotrader()
161 |
--------------------------------------------------------------------------------