
Fast & minimalist limit-order-book implementation in Python, with almost no dependencies.
This is the very first version of the project, the idea was to have a working, correct and clean single-threaded version before making it fast. The next step is to rewrite the core parts in a faster and concurrent fashion.
For now, I have decided to keep it written only in Python (no interfacing with C/C++), but that may change in the future.
Quickstart
To install the package you can install it easily using pip:
pip install fastlob
Otherwise, one can build the project from source:
git clone git@github.com:mrochk/fastlob.git
cd fastlob
pip install -r requirements.txt
pip install .
Examples
Placing a limit GTD order and getting its status.¶
1import time, logging
2
3from fastlob import (
4 Orderbook,
5 OrderParams,
6 OrderSide,
7 OrderType,
8)
9
10logging.basicConfig(level=logging.INFO) # maximum logging
11
12with Orderbook(name='example') as lob: # init and start lob in cm
13
14 # every order must be created this way
15 params = OrderParams(
16 side=OrderSide.BID, # is it a buy or sell order
17 price=123.32, quantity=3.42, # by default runs at 2 digits decimal precision
18 otype=OrderType.GTD, # good-till-date order
19 expiry=time.time() + 120 # order will expire in two minutes
20 # since order is GTD, expiry must be set to some future timestamp
21 )
22
23 # -> at this point an exception will be raised if invalid attributes are provided
24
25 result = lob(params) # let the book process the order
26 assert result.success() # result object can be used to see various infos about the order execution
27
28 # order uuid is used to query our order after it's been placed
29 status, quantity_left = lob.get_status(result.orderid())
30 print(f'Current order status: {status.name}, quantity left: {quantity_left}.\n')
31
32 lob.render() # pretty-print the book state
33
34# if lob not started using context manager, must call lob.stop() before terminating
Run a market simulation using various distributions.¶
1import random, time, os
2from scipy import stats
3
4from fastlob import (
5 Orderbook,
6 OrderParams,
7 OrderSide,
8)
9
10def generate_orders(T: int, midprice: float) -> list:
11 '''
12 A function for generating random batches of orders around a certain midprice.
13 '''
14 result = []
15
16 for _ in range(T):
17
18 # how many limit orders to place
19 n_ask_limits = stats.poisson.rvs(500)
20 n_bid_limits = stats.poisson.rvs(500)
21
22 ask_limits_price = stats.expon.rvs(loc=midprice, scale=1, size=n_ask_limits)
23 bid_limits_price = -stats.expon.rvs(loc=midprice, scale=1, size=n_bid_limits) + 2*midprice
24
25 ask_limits_quantities = stats.uniform.rvs(loc=1, scale=100, size=n_ask_limits)
26 bid_limits_quantities = stats.uniform.rvs(loc=1, scale=100, size=n_bid_limits)
27
28 ask_limits_params = [OrderParams(OrderSide.ASK, p, q) for (p, q) in zip(ask_limits_price, ask_limits_quantities)]
29 bid_limits_params = [OrderParams(OrderSide.BID, p, q) for (p, q) in zip(bid_limits_price, bid_limits_quantities)]
30
31 # how many market orders to place
32 n_markets = stats.poisson.rvs(100)
33
34 markets_price = stats.norm.rvs(loc=midprice, scale=2, size=n_markets)
35 markets_quantities = stats.uniform.rvs(loc=1, scale=100, size=n_markets)
36 markets_bid_or_ask = [random.choice((OrderSide.BID, OrderSide.ASK)) for _ in range(n_markets)]
37
38 markets_params = [OrderParams(s, p, q) for (s, p, q) in zip(markets_bid_or_ask, markets_price, markets_quantities)]
39
40 orders = ask_limits_params + bid_limits_params + markets_params
41 random.shuffle(orders)
42 result.append(orders)
43
44 return result
45
46def simulate(orders: list, speed: float) -> None:
47 with Orderbook('simulation') as ob:
48
49 for o in orders:
50 ob.process_many(o)
51 print(); ob.render()
52 time.sleep(speed)
53 os.system('clear')
54
55# main:
56orders = generate_orders(10, 100)
57simulate(orders, 0.5)
Running the lob using historical price levels data.¶
1from fastlob import (
2 Orderbook,
3 OrderParams,
4 OrderSide,
5)
6
7# initial state of the book
8snapshot = {
9 'bids': [ # bid side
10 (98.78, 11.56), # (price, quantity)
11 (95.65, 67.78), (94.23, 56.76),
12 (93.23, 101.59), (90.03, 200.68),
13 ],
14 'asks': [ # ask side
15 (99.11, 12.87), # (price, quantity)
16 (100.89, 45.87), (101.87, 88.56),
17 (103.78, 98.77), (105.02, 152.43),
18 ]
19}
20
21# list of successive (price, quantity) updates to apply
22updates = [
23 # update 1
24 {
25 'bids': [
26 (99.07, 10.01), (95.65, 79.78),
27 (93.23, 89.59), (90.03, 250.68),
28 ],
29 'asks': [(99.11, 5.81)]
30 },
31
32 # update 2
33 {
34 'bids': [(99.07, 0.00), (98.78, 3.56), (79.90, 100.56)],
35 'asks': [(103.78, 90.77), (105.02, 123.43)]
36 },
37
38 # update 3
39 {
40 'bids': [
41 (98.78, 11.56), (95.65, 67.78), (94.23, 56.76),
42 (93.23, 0.00), (90.03, 0.00),
43 # updates that set qty to 0 also delete the limit
44 # if no other order sits there
45 ],
46 'asks': [
47 (99.11, 0.00), (100.89, 0.00),
48 (101.87, 0.00), (103.78, 1.23),
49 (105.02, 152.43),
50 ]}]
51
52 # initialize order-book from snapshot
53 ob = Orderbook.from_snapshot(snapshot, start=True)
54 ob.load_updates(updates) # load updates to be able to call step()
55
56 ob.render()
57
58 ob.step() # apply first update
59 ob.render()
60
61 # even tho we use historical data,
62 # can still place a manual order between updates
63 ob(OrderParams(OrderSide.BID, 99.07, 1.98))
64
65 ob.step() # apply second update
66 ob.render()
67
68 ob.step() # apply third update
69 ob.render()
70
71 ob.stop()