quantpylib.hft
quantpylib.hft is our core module designed for hft-trading purposes, including live trading, tick data replay and simulation, archival and analytics.
quantpylib.hft.feed is our data feeder for public information and market data such as bba, order book and trade feeds.
quantpylib.hft.l0 is the L0/QBN (Quantpylib Binary eNcoding) capture layer for persisting native market-data feed events.
quantpylib.hft.oms is our order management system for trade execution and management of in-memory mirrors of positions, orders and fills through websocket/stream based reconciliation.
quantpylib.hft.replay is our tick data simulation for high-fidelity backtesting over the same Feed, OMS and Gateway clients as in live mode.
quantpylib.hft.orderbook is our native, integral order book implementation, optimised for speed (nanoseconds per level) with a cpp backend.
quantpylib.hft.qet provides the binary Quantpylib Event Trace
(QET) reader, writer, and decoders.
quantpylib.hft.lob is a legacy limit-order book implementation.
Examples
Live Feed
examples/example_feed.py:
The following example script shows how you can easily subscribe to ticker book updates and trades on binance and hyperliquid. All wrappers
compatible with the gateway may be used. We demonstrate using an OrderbookMaterializerSink object to obtain a live, in-memory durable orderbook state.
These are optimised implementations in cpp for low-latency trading exposed to Python via nanobind.
import os
import asyncio
from pprint import pprint
from dotenv import load_dotenv
load_dotenv()
from quantpylib.hft import OrderbookMaterializerSink
from quantpylib.hft.feed import Feed
from quantpylib.gateway.master import Gateway
keys = {
"binance": {'key':'','secret':''},
"hyperliquid": {'key':'','secret':''},
}
async def printer(data):
print(data)
async def printer_1(orderbook):
print('1\n', orderbook.best_bid(), orderbook.best_ask())
async def main():
exchange,ticker = 'hyperliquid', 'BTC'
# exchange,ticker = 'binance', 'BTCUSDT'
gateway = Gateway(config_keys=keys)
await gateway.init_clients()
feed = Feed(gateway=gateway)
book_sink = OrderbookMaterializerSink(on_update=printer_1)
l2_feed = await feed.add_l2_book_feed(
exc=exchange,
ticker=ticker,
sinks=[book_sink],
)
lob = book_sink.orderbook
print(l2_feed)
print(lob)
print(lob.mid(), lob.best_bid(), lob.best_ask())
trades_feed = await feed.add_trades_feed(exc=exchange,ticker=ticker,on_msg=printer)
print(feed.get_feed(trades_feed))
print(feed.get_feed_ids())
await asyncio.sleep(1e9) #keep streaming
if __name__ == "__main__":
asyncio.run(main())
Data Archival
The following example script shows how you can easily store and decode archival artefacts of tick data on gateway exchanges. This stores compact, raw binary Quantpylib Binary eNcoding qbn files. See Feed for full feed suite and l0 for our QBN data encoding layout.
Data models for book updates, trades and bba are available in quantpylib.standards.models.
A more detailed discussion is available on the Tick Data Archival tutorial.
import asyncio
from collections import Counter
from pathlib import Path
from quantpylib.gateway.master import Gateway
from quantpylib.hft.feed import Feed
from quantpylib.hft.l0 import (
DailyCaptureFileSystemPolicy,
L0ArchiveSink,
reader_for_file,
)
ARCHIVE_ROOT = Path(".archival/")
RUN_SECONDS = 30
MARKETS = {
"binance": ["BTCUSDT", "ETHUSDT", "SOLUSDT"],
"bybit": ["BTCUSDT", "ETHUSDT", "SOLUSDT"],
"hyperliquid": ["BTC", "ETH", "SOL"],
}
def gateway_config():
return {
"binance": {},
"bybit": {},
"hyperliquid": {"key": "0x0000000000000000000000000000000000000000"},
}
def counter_handler(counts, venue, feed_type, ticker):
async def on_msg(_update):
counts[(venue, feed_type, ticker)] += 1
return on_msg
async def add_capture_feeds(feed, counts):
for venue, tickers in MARKETS.items():
for ticker in tickers:
await feed.add_l2_book_feed(
exc=venue,
ticker=ticker,
on_msg=counter_handler(counts, venue, "l2book", ticker),
)
await feed.add_trades_feed(
exc=venue,
ticker=ticker,
on_msg=counter_handler(counts, venue, "trades", ticker),
)
await feed.add_bba_feed(
exc=venue,
ticker=ticker,
on_msg=counter_handler(counts, venue, "bba", ticker),
)
async def capture_archives():
counts = Counter()
default_policy = DailyCaptureFileSystemPolicy(root=ARCHIVE_ROOT)
sink = L0ArchiveSink(policy=default_policy)
gateway = Gateway(config_keys=gateway_config())
feed = None
try:
await gateway.init_clients()
feed = Feed(gateway=gateway, sinks=[sink])
await add_capture_feeds(feed, counts)
print(f"Capturing {len(MARKETS)} venues for {RUN_SECONDS} seconds...")
await asyncio.sleep(RUN_SECONDS)
finally:
if feed is not None:
await feed.cleanup()
else:
sink.close()
await gateway.cleanup_clients()
paths = sorted(Path(path) for path in sink.writers)
if not paths:
raise RuntimeError("archive sink did not write any QBN files")
return paths, counts
def read_back_archives(paths, counts):
print("\nCallback counts:")
for key, count in sorted(counts.items()):
venue, feed_type, ticker = key
print(f" {venue:12s} {feed_type:8s} {ticker:10s} {count}")
print("\nQBN files: \n", '\n'.join(str(path) for path in paths))
for path in paths:
reader = reader_for_file(path)
header = reader.header()
records = list(reader.records(with_context=True))
# for record in records:
# print(record)
async def main():
paths, counts = await capture_archives()
read_back_archives(paths, counts)
if __name__ == "__main__":
asyncio.run(main())
We get the following
QBN files:
.archival/binance/perp/bba/2026-07-08.qbn
.archival/binance/perp/l2book/2026-07-08.qbn
.archival/binance/perp/trades/2026-07-08.qbn
.archival/bybit/perp/bba/2026-07-08.qbn
.archival/bybit/perp/l2book/2026-07-08.qbn
.archival/bybit/perp/trades/2026-07-08.qbn
.archival/hyperliquid/perp/bba/2026-07-08.qbn
.archival/hyperliquid/perp/l2book/2026-07-08.qbn
.archival/hyperliquid/perp/trades/2026-07-08.qbn
Reader and writer APIs are provided, for instance:
{'block_offset': 25316482, 'record_index': 69486, 'venue': 'binance', 'market_class': 'perp', 'feed_type': 'bba', 'symbol': 'ETHUSDT', 'event': BBAUpdate(ticker='ETHUSDT', seq_type=0, seq0=0, bid_price=1733670000000, ask_price=1733680000000)}
OMS
It is easy to create a manager class - it is similar to thequantpylib.hft.feed.Feed objects. Simply - create a gateway object with the correct keys and pass them in.
We will demonstrate with examples:
import os
import asyncio
from pprint import pprint
from dotenv import load_dotenv
load_dotenv()
from quantpylib.hft.oms import OMS
from quantpylib.gateway.master import Gateway
import quantpylib.standards.markets as markets
config_keys = {
'binance': {
'key': '1234',
'secret': '1234',
},
'hyperliquid': {
'key': '1234',
'secret': '1234',
}
}
gateway = Gateway(config_keys)
async def main():
await gateway.init_clients()
oms = OMS(gateway)
await oms.init()
#code goes here...
###
await oms.cleanup()
if __name__ == "__main__":
asyncio.run(main())
For all of our socket-based message handlers, we will use a generic printer to showcase results:
async def printer(data):
if isinstance(data,dict) or isinstance(data,list):
pprint(data)
else:
try: pprint(data.as_dict())
except: pprint(data.as_list())
{'base_asset': 'BTC',
'min_notional': Decimal('10.0'),
'price_precision': 1,
'quantity_precision': 5,
'quote_asset': 'USDT'}
pprint(oms.lot_precision(exc='hyperliquid', ticker='BTC')) #5
pprint(oms.price_precision(exc='hyperliquid', ticker='DOGE')) #6
pprint(oms.rounded_lots(exc='hyperliquid', ticker='BTC',amount=0.0023032)) #0.0023
pprint(oms.rounded_price(exc='hyperliquid', ticker='BTC',price=62000.1234)) #62000.1
pprint(oms.min_notional(exc='hyperliquid', ticker='BTC')) #Decimal('10.0')
pprint(oms.rand_cloid(exc='binance')) #'b486130e1b35986abc803bb79d2e675d'
pprint(oms.common_lot_precision(ex1='hyperliquid',ex2='binance',ticker1='BTC',ticker2='BTCUSDT')) #3
pprint(oms.common_price_precision(ex1='hyperliquid',ex2='binance',ticker1='BTC',ticker2='BTCUSDT')) #1
pprint(oms.common_min_notional(ex1='hyperliquid',ex2='binance',ticker1='BTC',ticker2='BTCUSDT')) #Decimal('100')
We would like to get some positions data. Note that when oms.init() is called, all orders and positions are automatically mirrored using underlying exchange socket subscriptions. We can make connection-less request by retrieving local state:
pprint(await oms.positions_get(exc='hyperliquid')) #HTTP requests made
pprint(await oms.positions_get_all())
{'SOL': {'amount': Decimal('1.0'),
'entry': Decimal('134.81'),
'ticker': 'SOL',
'unrealized_pnl': -0.3,
'value': Decimal('134.51')}}
{'binance': {'QUANTUSDT': {'amount': Decimal('826'),
'entry': Decimal('0.1250522412206'),
'ticker': 'QUANTUSDT',
'unrealized_pnl': 1.03231002,
'value': Decimal('104.32546026')}},
'hyperliquid': {'SOL': {'amount': Decimal('1.0'),
'entry': Decimal('134.81'),
'ticker': 'SOL',
'unrealized_pnl': -0.3,
'value': Decimal('134.51')}}}
If we want to get the live positions object that tracks all positions, or register a handler on position change, we may do so. In particular, we can register handler on_update which passes the entire positions page (and/or) on_delta which passes the change in positions. Furthermore, the return value is the Positions object which is 'alive', so to speak, and keeps up to date with filled orders.
live_positions = await oms.positions_mirror(exc='hyperliquid',on_update=printer,on_delta=printer)
print(live_positions)#<quantpylib.standards.portfolio.Positions object at 0x12a98c8f0>
We may do the same with orders:
pprint(await oms.orders_get(exc='hyperliquid')) #HTTP
pprint(await oms.orders_get_all())
live_orders = await oms.orders_mirror(
exc='hyperliquid',
on_update=printer, #order snapshots passed to handler
on_delta=printer #only changed orders passed to handler
) #this in live, in-memory
Now that we have registered some handlers for orders, and what not - let us see what the messages look like. We can make a limit order through the OMS - the parameters are the same as in Gateway usage:
cloid = oms.rand_cloid(exc='hyperliquid')
await oms.limit_order(exc='hyperliquid',ticker='SOL',amount=1,price=129.56,cloid=cloid)
await oms.limit_order(exc='binance',ticker='SOLUSDT',price_rule=markets.PRICE_RULE_QUEUE_1,amount=1)
hyperliquid, so let's see what gets printed:
The on_delta handler receives two messages:
{'amount': Decimal('1'),
'cloid': '0x272e45bf06706c3259f41079a1d48d2a',
'exc': 'hyperliquid',
'filled_sz': Decimal('0'),
'last_fill_sz': Decimal('0'),
'oid': None,
'ord_status': 'CREATE_PENDING',
'ord_type': None,
'price': Decimal('129.56'),
'price_rule': None,
'reduce_only': None,
'sl': None,
'ticker': 'SOL',
'tif': None,
'timestamp': 1726154778980,
'tp': None}
{'amount': Decimal('1.0'),
'cloid': '0x272e45bf06706c3259f41079a1d48d2a',
'exc': 'hyperliquid',
'filled_sz': Decimal('0.0'),
'last_fill_sz': Decimal('0.0'),
'oid': '1234',
'ord_status': 'NEW',
'ord_type': None,
'price': Decimal('129.56'),
'price_rule': None,
'reduce_only': None,
'sl': None,
'ticker': 'SOL',
'tif': None,
'timestamp': 1726154781307,
'tp': None}
CREATE_PENDING status. This is followed by a NEW order which means the order was acknowledged successful by the exchange.
Following an order cancel:
this is acknowledged on_delta:
{'amount': Decimal('1.0'),
'cloid': '0x272e45bf06706c3259f41079a1d48d2a',
'exc': 'hyperliquid',
'filled_sz': Decimal('0.0'),
'last_fill_sz': Decimal('0.0'),
'oid': '1234',
'ord_status': 'CANCELLED',
'ord_type': None,
'price': Decimal('129.56'),
'price_rule': None,
'reduce_only': None,
'sl': None,
'ticker': 'SOL',
'tif': None,
'timestamp': 1726154781307,
'tp': None}
on_update prints the new list (not shown), this time without the cancelled order - since it is not on the orders page anymore (it is no longer open).
Market Making and Sims
Please refer to the market-making tutorial on how to write cross-exchange, cross environment (live, simulation) code for production and backtesting.
HFT Event Journaling
Please refer to the event journal tutorial on how to create binary qet trace files for downstream quantitative analytics and modelling.