Skip to content

Commit 0dcc762

Browse files
authored
Order book methods (#8)
* Start order book endpoint implementation (wip) * Handle market depth subscriptions (wip) * Whoops * Implement market depth endpoints + update docs * whoops * Update README * Add doc about how to handle the different values of update_type * Update changelog * Bump version: 1.5.3 → 1.5.4
1 parent be31be2 commit 0dcc762

19 files changed

+361
-17
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.5.3
2+
current_version = 1.5.4
33
commit = True
44
tag = True
55

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [1.5.4] - 2025-07-28
4+
### Added
5+
- Order book/market depth methods
6+
37
## [1.5.3] - 2025-07-09
48
### Added
59
- Logger name suffix kwarg

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ Designed with reliability and extensibility in mind, `async_rithmic` is a strong
1818
## ✨ Key Features
1919

2020
-**Python 3.10+ Compatibility**: Fully tested and supported.
21+
-**Async-first design**: Better scalability & responsiveness.
2122
- 🛠️ **Robust architecture**: Built-in reconnection & fault-tolerance.
2223
- [**Automatic reconnection**](https://async-rithmic.readthedocs.io/en/latest/connection.html#custom-reconnection-settings): Resilient to network interruptions with customizable backoff and retry logic.
2324
- [**Automatic retries**](https://async-rithmic.readthedocs.io/en/latest/connection.html#custom-retry-settings): Configure how many times a slow request will be retried and for how long, making your client more resilient to network delays and backend slowness.
2425
- 👥 **Multi-account support**
2526
- 📊 **Historical + Live Time Bars**: Ideal for time-based strategies.
2627
- 🎯 **Live Tick Data & Best Bid/Ask Streaming**: Fine-grained market data for real-time decision-making.
27-
- **Async-first design**: Better scalability & responsiveness.
28+
- 🪟 **Full Order Book (L2) Streaming**: Stream real-time depth of market (all bids/asks, multiple price levels) for advanced order flow analysis.
2829

2930
## 📦 Installation
3031

async_rithmic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
from .exceptions import *
55
from .objects import RetrySettings, ReconnectionSettings
66

7-
__version__ = '1.5.3'
7+
__version__ = '1.5.4'

async_rithmic/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def __init__(
3939
# Real-time market updates events
4040
self.on_tick = Event()
4141
self.on_time_bar = Event()
42+
self.on_order_book = Event()
43+
self.on_market_depth = Event()
4244

4345
# Order updates events
4446
self.on_rithmic_order_notification = Event()

async_rithmic/enums.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22

33
from . import protocol_buffers as pb
44

5-
class DataType(int, enum.Enum):
5+
6+
class DataType(enum.IntEnum):
67
LAST_TRADE = 1
78
BBO = 2
9+
ORDER_BOOK = 4
10+
811

912
OrderType = pb.request_new_order_pb2.RequestNewOrder.PriceType
1013
OrderDuration = pb.request_new_order_pb2.RequestNewOrder.Duration
1114
TransactionType = pb.request_new_order_pb2.RequestNewOrder.TransactionType
1215

1316
LastTradePresenceBits = pb.last_trade_pb2.LastTrade.PresenceBits
1417
BestBidOfferPresenceBits = pb.best_bid_offer_pb2.BestBidOffer.PresenceBits
18+
OrderBookPresenceBits = pb.order_book_pb2.OrderBook.PresenceBits
1519

1620
ExchangeOrderNotificationType = pb.exchange_order_notification_pb2.ExchangeOrderNotification.NotifyType
1721

async_rithmic/plants/base.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,16 @@
4545
113: pb.request_front_month_contract_pb2.RequestFrontMonthContract,
4646
114: pb.response_front_month_contract_pb2.ResponseFrontMonthContract,
4747

48-
#115: pb.request_depth_by_order_snapshot_pb2.RequestDepthByOrderSnapshot,
49-
#116: pb.response_depth_by_order_snapshot_pb2.ResponseDepthByOrderSnapshot,
50-
#117: pb.request_depth_by_order_updates_pb2.RequestDepthByOrderUpdates,
51-
#118: pb.response_depth_by_order_updates_pb2.ResponseDepthByOrderUpdates,
48+
115: pb.request_depth_by_order_snapshot_pb2.RequestDepthByOrderSnapshot,
49+
116: pb.response_depth_by_order_snapshot_pb2.ResponseDepthByOrderSnapshot,
50+
117: pb.request_depth_by_order_updates_pb2.RequestDepthByOrderUpdates,
51+
118: pb.response_depth_by_order_updates_pb2.ResponseDepthByOrderUpdates,
5252

5353
150: pb.last_trade_pb2.LastTrade,
5454
151: pb.best_bid_offer_pb2.BestBidOffer,
55-
#156: pb.order_book_pb2.OrderBook,
56-
#160: pb.depth_by_order.DepthByOrder,
57-
#161: pb.depth_by_order_end_event.DepthByOrderEndEvent,
55+
156: pb.order_book_pb2.OrderBook,
56+
160: pb.depth_by_order_pb2.DepthByOrder,
57+
161: pb.depth_by_order_end_event_pb2.DepthByOrderEndEvent,
5858

5959
# Order Plant Infrastructure
6060
300: pb.request_login_info_pb2.RequestLoginInfo,
@@ -500,10 +500,11 @@ async def _process_response(self, response):
500500
Handles async responses
501501
"""
502502

503-
if response.template_id in [13, 19, 401]:
503+
if response.template_id in [13, 19, 161, 401]:
504504
# Ignore
505505
# - logout responses
506506
# - heartbeat responses
507+
# - market depth end event
507508
# - pnl subscription responses
508509
return True
509510

async_rithmic/plants/ticker.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ async def _login(self):
1313
for symbol, exchange, update_bits in self._subscriptions["market_data"]:
1414
await self.subscribe_to_market_data(symbol, exchange, update_bits)
1515

16+
for symbol, exchange, depth_price in self._subscriptions["market_depth"]:
17+
await self.subscribe_to_market_depth(symbol, exchange, depth_price)
18+
1619
async def list_exchanges(self):
1720
return await self._send_and_collect(
1821
template_id=342,
@@ -98,6 +101,67 @@ async def search_symbols(self, search_text, **kwargs):
98101
**kwargs
99102
)
100103

104+
async def request_market_depth(
105+
self,
106+
symbol: str,
107+
exchange: str,
108+
depth_price: float
109+
):
110+
"""
111+
Request order book data for a given price
112+
"""
113+
responses = await self._send_and_collect(
114+
template_id=115,
115+
expected_response=dict(template_id=116),
116+
symbol=symbol,
117+
exchange=exchange,
118+
depth_price=depth_price,
119+
account_id=None,
120+
)
121+
return responses[0] if responses else None
122+
123+
async def subscribe_to_market_depth(
124+
self,
125+
symbol: str,
126+
exchange: str,
127+
depth_price: float
128+
):
129+
"""
130+
Subscribes to market depth updates (L2 data) for a given price
131+
"""
132+
133+
sub = (symbol, exchange, depth_price)
134+
self._subscriptions["market_depth"].add(sub)
135+
136+
await self._send_request(
137+
template_id=117,
138+
symbol=symbol,
139+
exchange=exchange,
140+
depth_price=depth_price,
141+
request=pb.request_depth_by_order_updates_pb2.RequestDepthByOrderUpdates.Request.SUBSCRIBE,
142+
)
143+
144+
async def unsubscribe_from_market_depth(
145+
self,
146+
symbol: str,
147+
exchange: str,
148+
depth_price: float
149+
):
150+
"""
151+
Unsubscribes from market depth updates (L2 data) for a given price
152+
"""
153+
154+
sub = (symbol, exchange, depth_price)
155+
self._subscriptions["market_depth"].discard(sub)
156+
157+
await self._send_request(
158+
template_id=117,
159+
symbol=symbol,
160+
exchange=exchange,
161+
depth_price=depth_price,
162+
request=pb.request_depth_by_order_updates_pb2.RequestDepthByOrderUpdates.Request.UNSUBSCRIBE,
163+
)
164+
101165
async def _process_response(self, response):
102166
if await super()._process_response(response):
103167
return True
@@ -106,6 +170,10 @@ async def _process_response(self, response):
106170
# Market data update response
107171
pass
108172

173+
elif response.template_id == 118:
174+
# Market depth data update response
175+
pass
176+
109177
elif response.template_id == 150:
110178
# Market data stream: Last Trade
111179
data = self._response_to_dict(response)
@@ -122,5 +190,13 @@ async def _process_response(self, response):
122190

123191
await self.client.on_tick.call_async(data)
124192

193+
elif response.template_id == 156:
194+
# Market data stream: Order Book
195+
await self.client.on_order_book.call_async(response)
196+
197+
elif response.template_id == 160:
198+
# Market depth data stream
199+
await self.client.on_market_depth.call_async(response)
200+
125201
else:
126202
self.logger.warning(f"Unhandled inbound message with template_id={response.template_id}")

async_rithmic/protocol_buffers/depth_by_order_end_event_pb2.py

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

async_rithmic/protocol_buffers/depth_by_order_pb2.py

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)