Skip to content

Commit 022a30b

Browse files
authored
Merge pull request #5 from joequant/master
Add algo simulator
2 parents 2a69d49 + 64d48bc commit 022a30b

File tree

9 files changed

+255
-16
lines changed

9 files changed

+255
-16
lines changed

bin/algosim.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#!/usr/bin/python
2+
3+
import sys
4+
from orderbook import OrderBook
5+
from six.moves import cStringIO
6+
from builtins import input
7+
from decimal import Decimal
8+
from six.moves import cStringIO as StringIO
9+
from six.moves import zip_longest
10+
import importlib
11+
import json
12+
import copy
13+
import difflib
14+
import pprint
15+
16+
d = difflib.Differ()
17+
format='html'
18+
19+
if __name__ == '__main__':
20+
def printme(*args):
21+
if format == 'html':
22+
print(*args)
23+
print('<br>')
24+
else:
25+
print(*args)
26+
27+
def print_orderbook(newbook, oldbook):
28+
if format == 'html':
29+
tempfile = StringIO()
30+
tempfile.write('<table>')
31+
tempfile.write('<tr>')
32+
tempfile.write("<td><b>Bids</b></td>")
33+
tempfile.write("<td><b>Asks</b></td>")
34+
tempfile.write("</tr>")
35+
bids_new = []
36+
asks_new = []
37+
38+
bids_old = []
39+
asks_old = []
40+
if newbook.bids != None and len(newbook.bids) > 0:
41+
for key, value in newbook.bids.price_tree.items(reverse=True):
42+
for order in value:
43+
bids_new += [str(order)]
44+
if newbook.asks != None and len(newbook.asks) > 0:
45+
for key, value in list(newbook.asks.price_tree.items()):
46+
for order in value:
47+
asks_new += [str(order)]
48+
if oldbook.bids != None and len(oldbook.bids) > 0:
49+
for key, value in oldbook.bids.price_tree.items(reverse=True):
50+
for order in value:
51+
bids_old += [str(order)]
52+
if oldbook.asks != None and len(oldbook.asks) > 0:
53+
for key, value in list(oldbook.asks.price_tree.items()):
54+
for order in value:
55+
asks_old += [str(order)]
56+
57+
bids_diff = list(d.compare(bids_old, bids_new))
58+
asks_diff = list(d.compare(asks_old, asks_new))
59+
bids = []
60+
for i in bids_diff:
61+
if i[0:2] == '? ':
62+
continue
63+
elif i[0:2] == '+ ':
64+
bids += ["<b>" + i[2:] + "</b>"]
65+
elif i[0:2] == '- ':
66+
bids += ["<strike>" + i[2:] + "</strike>"]
67+
else:
68+
bids += [i]
69+
70+
asks = []
71+
for i in asks_diff:
72+
if i[0:2] == '? ':
73+
continue
74+
elif i[0:2] == '+ ':
75+
asks += ["<b>" + i[2:] + "</b>"]
76+
elif i[0:2] == '- ':
77+
asks += ["<strike>" + i[2:] + "</strike>"]
78+
else:
79+
asks += [i]
80+
81+
for i in zip_longest(bids, asks):
82+
tempfile.write('<tr><td>' + \
83+
(i[0] if i[0] is not None else '') + \
84+
'</td><td>' + \
85+
(i[1] if i[1] is not None else '') + \
86+
'</td></tr>\n')
87+
tempfile.write('</table><p>')
88+
tempfile.write("\n<b>Trades</b><br>\n")
89+
if newbook.tape != None and len(newbook.tape) > 0:
90+
num = 0
91+
for entry in newbook.tape:
92+
if num < 10: # get last 5 entries
93+
tempfile.write(str(entry['quantity']) + " @ " + str(entry['price']) + " (" + str(entry['timestamp']) + ") " + str(entry['party1'][0]) + "/" + str(entry['party2'][0]) + "<br>\n")
94+
num += 1
95+
else:
96+
break
97+
tempfile.write("\n")
98+
print(tempfile.getvalue())
99+
else:
100+
print(newbook)
101+
102+
def process_line(order_book, line, output=True):
103+
tokens = line.strip().split(",")
104+
d = {"type" : "limit",
105+
"side" : "bid" if tokens[0] == 'B' else 'ask',
106+
"quantity": int(tokens[1]),
107+
"price" : Decimal(tokens[2]),
108+
"trade_id" : tokens[3]}
109+
if output:
110+
printme('external order=', pprint.pformat(d))
111+
return order_book.process_order(d, False, False)
112+
113+
114+
order_book = OrderBook()
115+
if len(sys.argv) != 2 and len(sys.argv) != 3 and len(sys.argv) != 4:
116+
printme("usage: %s input.csv [algo]" % sys.argv[0])
117+
sys.exit(0)
118+
if len(sys.argv) == 3:
119+
myalgomodule = importlib.import_module(sys.argv[2])
120+
myalgo = myalgomodule.Algorithm(order_book)
121+
elif len(sys.argv) == 4:
122+
myalgomodule = importlib.import_module(sys.argv[2])
123+
json_data=open(sys.argv[3]).read()
124+
data = json.loads(json_data)
125+
myalgo = myalgomodule.Algorithm(order_book, **data)
126+
else:
127+
myalgo = None
128+
try:
129+
reader = open(sys.argv[1], 'r')
130+
start_algo = False
131+
for line in reader:
132+
trades = None
133+
order = None
134+
if start_algo:
135+
printme("--------- START -------")
136+
old_orderbook = copy.deepcopy(order_book)
137+
if line[0] == '#':
138+
next
139+
elif line[0] == 'B' or line[0] == 'A':
140+
(trade, order) = process_line(order_book, line, start_algo)
141+
myalgo.process_trade(trade, 'trade')
142+
elif line[0:12] == 'C,start-algo':
143+
start_algo = True
144+
printme("--------- START -------")
145+
146+
if not start_algo:
147+
continue
148+
# Manual Debugging
149+
printme ("\n")
150+
print_orderbook(order_book, old_orderbook)
151+
stats=myalgo.stats()
152+
printme ("total volume=", stats[0])
153+
printme ("my volume=", stats[1])
154+
printme ("participation=", stats[3])
155+
156+
if myalgo != None:
157+
(algo_orders, mode) = myalgo.process_order(line,
158+
trade, order)
159+
printme('')
160+
printme("RUNNING ALGO WITH MODE=", mode)
161+
old_orderbook = copy.deepcopy(order_book)
162+
for line in algo_orders:
163+
printme(pprint.pformat(line))
164+
if line['type'] == 'cancel':
165+
order_book.cancel_order(line['side'],
166+
line['order_id'])
167+
elif line['type'] == 'modify':
168+
order_book.modify_order(line['order_id'], {
169+
'side': line['side'],
170+
'price': line['price'],
171+
'quantity': line['quantity']})
172+
else:
173+
(trade, order) = order_book.process_order(line,
174+
False,
175+
False)
176+
myalgo.process_trade(trade, mode)
177+
if len(algo_orders) > 0:
178+
printme("\n")
179+
printme("After algo")
180+
print_orderbook(order_book, old_orderbook)
181+
stats=myalgo.stats()
182+
printme ("total volume=", stats[0])
183+
printme ("my volume=", stats[1])
184+
printme ("participation=", stats[3])
185+
else:
186+
printme("No action by algo")
187+
printme("--------- END -------")
188+
reader.close()
189+
except IOError:
190+
printme ('Cannot open input file "%s"' % sys.argv[1])
191+
sys.exit(1)

bin/input.csv

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
B,15000,1,AB
2+
B,15000,1,AB
3+
B,5000,0.99,CD
4+
B,3000,0.98,EF
5+
B,12000,0.97,ABC
6+
A,31000,1.02,JF
7+
A,5000,1.04,ML
8+
A,2000,1.05,SC
9+
A,2000,1.09,HS
10+
C,start-algo
11+

bin/myalgo.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from decimal import Decimal
2+
3+
class Algorithm(object):
4+
def __init__(self, order_book):
5+
self.active = False
6+
self.order_book = order_book
7+
self.volume = 0
8+
self.my_volume = 0
9+
10+
def process_order(self, line, trade, order):
11+
tokens = line.strip().split(",")
12+
if tokens[0] == 'C' and tokens[1] == 'start-algo':
13+
print("starting-algo")
14+
self.active = True
15+
return (self.start(), 'start')
16+
if not self.active:
17+
return ([], 'inactive')
18+
return ([], 'inactive')
19+
20+
def start(self):
21+
return [{"type": "limit",
22+
"side": "bid",
23+
"quantity": 10000,
24+
"price": Decimal(1.04 ),
25+
"trade_id" : "ME"}]
26+
27+
def process_trade(self, trade, mode):
28+
for i in trade:
29+
self.volume += i['quantity']
30+
if i['party1'][0] == "ME" or \
31+
i['party2'][0] == "ME":
32+
self.my_volume += i['quantity']
33+
def stats(self):
34+
if (self.volume > 0):
35+
return (self.volume, self.my_volume, None,
36+
self.my_volume / self.volume)
37+
else:
38+
return (self.volume, self.my_volume, None, None)
39+

orderbook/order.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, quote, order_list):
1313
self.quantity = Decimal(quote['quantity']) # decimal representing amount of thing - can be partial amounts
1414
self.price = Decimal(quote['price']) # decimal representing price (currency)
1515
self.order_id = int(quote['order_id'])
16-
self.trade_id = int(quote['trade_id'])
16+
self.trade_id = quote['trade_id']
1717
# doubly linked list to make it easier to re-order Orders for a particular price point
1818
self.next_order = None
1919
self.prev_order = None
@@ -35,4 +35,5 @@ def update_quantity(self, new_quantity, new_timestamp):
3535
self.quantity = new_quantity
3636

3737
def __str__(self):
38-
return "Order: Price - %s, Quantity - %s, Timestamp - %s" % (self.price, self.quantity, self.timestamp)
38+
return "{}@{}/{} - {}".format(self.quantity, self.price,
39+
self.trade_id, self.timestamp)

orderbook/orderbook.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import math
33
from collections import deque # a faster insert/pop queue
44
from six.moves import cStringIO as StringIO
5+
from decimal import Decimal
56

67
from .ordertree import OrderTree
78

@@ -16,11 +17,6 @@ def __init__(self, tick_size = 0.0001):
1617
self.time = 0
1718
self.next_order_id = 0
1819

19-
def clip_price(self, price):
20-
'''Clips the price according to the tick size. May not make sense if not
21-
a currency'''
22-
return round(price, int(math.log10(1 / self.tick_size)))
23-
2420
def update_time(self):
2521
self.time += 1
2622

@@ -39,7 +35,7 @@ def process_order(self, quote, from_data, verbose):
3935
if order_type == 'market':
4036
trades = self.process_market_order(quote, verbose)
4137
elif order_type == 'limit':
42-
quote['price'] = self.clip_price(quote['price'])
38+
quote['price'] = Decimal(quote['price'])
4339
trades, order_in_book = self.process_limit_order(quote, from_data, verbose)
4440
else:
4541
sys.exit("order_type for process_order() is neither 'market' or 'limit'")
@@ -78,8 +74,7 @@ def process_order_list(self, side, order_list, quantity_still_to_trade, quote, v
7874
self.asks.remove_order_by_id(head_order.order_id)
7975
quantity_to_trade -= traded_quantity
8076
if verbose:
81-
print(("TRADE: Time - %d, Price - %f, Quantity - %d, TradeID - %d, Matching TradeID - %d" %
82-
(self.time, traded_price, traded_quantity, counter_party, quote['trade_id'])))
77+
print(("TRADE: Time - {}, Price - {}, Quantity - {}, TradeID - {}, Matching TradeID - {}".format(self.time, traded_price, traded_quantity, counter_party, quote['trade_id'])))
8378

8479
transaction_record = {
8580
'timestamp': self.time,
@@ -183,7 +178,7 @@ def modify_order(self, order_id, order_update, time=None):
183178
sys.exit('modify_order() given neither "bid" nor "ask"')
184179

185180
def get_volume_at_price(self, side, price):
186-
price = self.clip_price(price)
181+
price = Decimal(price)
187182
if side == 'bid':
188183
volume = 0
189184
if self.bids.price_exists(price):
@@ -234,7 +229,7 @@ def __str__(self):
234229
num = 0
235230
for entry in self.tape:
236231
if num < 10: # get last 5 entries
237-
tempfile.write(str(entry['quantity']) + " @ " + str(entry['price']) + " (" + str(entry['timestamp']) + ")\n")
232+
tempfile.write(str(entry['quantity']) + " @ " + str(entry['price']) + " (" + str(entry['timestamp']) + ") " + str(entry['party1'][0]) + "/" + str(entry['party2'][0]) + "\n")
238233
num += 1
239234
else:
240235
break

orderbook/orderlist.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def next(self):
3535
self.last = self.last.next_order
3636
return return_value
3737

38+
__next__ = next # python3
39+
3840
def get_head_order(self):
3941
return self.head_order
4042

@@ -92,9 +94,9 @@ def move_to_tail(self, order):
9294
def __str__(self):
9395
from six.moves import cStringIO as StringIO
9496
temp_file = StringIO()
95-
#for order in self:
96-
# temp_file.write("%s\n" % str(order))
97-
temp_file.write("%s\n" % str(self.head_order))
97+
for order in self:
98+
temp_file.write("%s\n" % str(order))
99+
#temp_file.write("%s\n" % str(self.head_order))
98100
return temp_file.getvalue()
99101

100102

orderbook/ordertree.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ def order_exists(self, order):
4444
return order in self.order_map
4545

4646
def insert_order(self, quote):
47-
print(quote)
4847
if self.order_exists(quote['order_id']):
4948
self.remove_order_by_id(quote['order_id'])
5049
self.num_orders += 1

orderbook/test/example.py

100644100755
File mode changed.

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
install_requires=[
1313
"bintrees >= 2.0.1"
1414
],
15+
scripts=['bin/algosim.py']
1516
)

0 commit comments

Comments
 (0)