Logo
Fast & minimalist limit-order-book implementation in Python, with almost no dependencies.

GitHub  |  PyPI

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()

API Reference