async-rithmic 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. async_rithmic-1.0.0.dist-info/LICENSE +21 -0
  2. async_rithmic-1.0.0.dist-info/METADATA +313 -0
  3. async_rithmic-1.0.0.dist-info/RECORD +85 -0
  4. async_rithmic-1.0.0.dist-info/WHEEL +5 -0
  5. async_rithmic-1.0.0.dist-info/top_level.txt +1 -0
  6. rithmic/__init__.py +5 -0
  7. rithmic/certificates/rithmic_ssl_cert_auth_params +34 -0
  8. rithmic/client.py +104 -0
  9. rithmic/enums.py +32 -0
  10. rithmic/event.py +21 -0
  11. rithmic/logger.py +20 -0
  12. rithmic/plants/__init__.py +4 -0
  13. rithmic/plants/base.py +341 -0
  14. rithmic/plants/history.py +205 -0
  15. rithmic/plants/order.py +261 -0
  16. rithmic/plants/pnl.py +77 -0
  17. rithmic/plants/ticker.py +87 -0
  18. rithmic/protocol_buffers/__init__.py +13 -0
  19. rithmic/protocol_buffers/account_pnl_position_update_pb2.py +26 -0
  20. rithmic/protocol_buffers/base_pb2.py +26 -0
  21. rithmic/protocol_buffers/best_bid_offer_pb2.py +28 -0
  22. rithmic/protocol_buffers/exchange_order_notification_pb2.py +38 -0
  23. rithmic/protocol_buffers/instrument_pnl_position_update_pb2.py +26 -0
  24. rithmic/protocol_buffers/last_trade_pb2.py +30 -0
  25. rithmic/protocol_buffers/request_account_list_pb2.py +28 -0
  26. rithmic/protocol_buffers/request_account_rms_info_pb2.py +28 -0
  27. rithmic/protocol_buffers/request_bracket_order_pb2.py +42 -0
  28. rithmic/protocol_buffers/request_cancel_order_pb2.py +28 -0
  29. rithmic/protocol_buffers/request_front_month_contract_pb2.py +26 -0
  30. rithmic/protocol_buffers/request_heartbeat_pb2.py +26 -0
  31. rithmic/protocol_buffers/request_login_info_pb2.py +26 -0
  32. rithmic/protocol_buffers/request_login_pb2.py +28 -0
  33. rithmic/protocol_buffers/request_logout_pb2.py +26 -0
  34. rithmic/protocol_buffers/request_market_data_update_pb2.py +30 -0
  35. rithmic/protocol_buffers/request_modify_order_pb2.py +34 -0
  36. rithmic/protocol_buffers/request_new_order_pb2.py +38 -0
  37. rithmic/protocol_buffers/request_pnl_position_snapshot_pb2.py +26 -0
  38. rithmic/protocol_buffers/request_pnl_position_updates_pb2.py +28 -0
  39. rithmic/protocol_buffers/request_product_rms_info_pb2.py +26 -0
  40. rithmic/protocol_buffers/request_reference_data_pb2.py +26 -0
  41. rithmic/protocol_buffers/request_rithmic_system_info_pb2.py +26 -0
  42. rithmic/protocol_buffers/request_search_symbols_pb2.py +30 -0
  43. rithmic/protocol_buffers/request_show_order_history_detail_pb2.py +26 -0
  44. rithmic/protocol_buffers/request_show_orders_pb2.py +26 -0
  45. rithmic/protocol_buffers/request_subscribe_for_order_updates_pb2.py +26 -0
  46. rithmic/protocol_buffers/request_subscribe_to_bracket_updates_pb2.py +26 -0
  47. rithmic/protocol_buffers/request_tick_bar_replay_pb2.py +34 -0
  48. rithmic/protocol_buffers/request_tick_bar_update_pb2.py +32 -0
  49. rithmic/protocol_buffers/request_time_bar_replay_pb2.py +32 -0
  50. rithmic/protocol_buffers/request_time_bar_update_pb2.py +30 -0
  51. rithmic/protocol_buffers/request_trade_routes_pb2.py +26 -0
  52. rithmic/protocol_buffers/request_update_stop_bracket_level_pb2.py +26 -0
  53. rithmic/protocol_buffers/request_update_target_bracket_level_pb2.py +26 -0
  54. rithmic/protocol_buffers/response_account_list_pb2.py +26 -0
  55. rithmic/protocol_buffers/response_account_rms_info_pb2.py +30 -0
  56. rithmic/protocol_buffers/response_bracket_order_pb2.py +26 -0
  57. rithmic/protocol_buffers/response_cancel_order_pb2.py +26 -0
  58. rithmic/protocol_buffers/response_front_month_contract_pb2.py +26 -0
  59. rithmic/protocol_buffers/response_heartbeat_pb2.py +26 -0
  60. rithmic/protocol_buffers/response_login_info_pb2.py +28 -0
  61. rithmic/protocol_buffers/response_login_pb2.py +26 -0
  62. rithmic/protocol_buffers/response_logout_pb2.py +26 -0
  63. rithmic/protocol_buffers/response_market_data_update_pb2.py +26 -0
  64. rithmic/protocol_buffers/response_modify_order_pb2.py +26 -0
  65. rithmic/protocol_buffers/response_new_order_pb2.py +26 -0
  66. rithmic/protocol_buffers/response_pnl_position_snapshot_pb2.py +26 -0
  67. rithmic/protocol_buffers/response_pnl_position_updates_pb2.py +26 -0
  68. rithmic/protocol_buffers/response_product_rms_info_pb2.py +28 -0
  69. rithmic/protocol_buffers/response_reference_data_pb2.py +28 -0
  70. rithmic/protocol_buffers/response_rithmic_system_info_pb2.py +26 -0
  71. rithmic/protocol_buffers/response_search_symbols_pb2.py +26 -0
  72. rithmic/protocol_buffers/response_show_order_history_detail_pb2.py +26 -0
  73. rithmic/protocol_buffers/response_show_orders_pb2.py +26 -0
  74. rithmic/protocol_buffers/response_subscribe_for_order_updates_pb2.py +26 -0
  75. rithmic/protocol_buffers/response_tick_bar_replay_pb2.py +30 -0
  76. rithmic/protocol_buffers/response_tick_bar_update_pb2.py +26 -0
  77. rithmic/protocol_buffers/response_time_bar_replay_pb2.py +28 -0
  78. rithmic/protocol_buffers/response_time_bar_update_pb2.py +26 -0
  79. rithmic/protocol_buffers/response_trade_routes_pb2.py +26 -0
  80. rithmic/protocol_buffers/response_update_stop_bracket_level_pb2.py +26 -0
  81. rithmic/protocol_buffers/response_update_target_bracket_level_pb2.py +26 -0
  82. rithmic/protocol_buffers/rithmic_order_notification_pb2.py +38 -0
  83. rithmic/protocol_buffers/tick_bar_pb2.py +30 -0
  84. rithmic/protocol_buffers/time_bar_pb2.py +28 -0
  85. rithmic/protocol_buffers/trade_route_pb2.py +26 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mickael Burguet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,313 @@
1
+ Metadata-Version: 2.1
2
+ Name: async-rithmic
3
+ Version: 1.0.0
4
+ Summary: Python API Integration with Rithmic Protocol Buffer API
5
+ Home-page: https://github.com/rundef/pyrithmic
6
+ Author: Mickael Burguet
7
+ Project-URL: Documentation, https://github.com/rundef/pyrithmic
8
+ Project-URL: Bug Reports, https://github.com/rundef/pyrithmic/issues
9
+ Project-URL: Source Code, https://github.com/rundef/pyrithmic
10
+ Keywords: python rithmic
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Build Tools
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Operating System :: OS Independent
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: websockets >=9.0
27
+ Requires-Dist: protobuf ==4.25.4
28
+ Provides-Extra: dev
29
+ Requires-Dist: check-manifest ; extra == 'dev'
30
+ Provides-Extra: test
31
+ Requires-Dist: coverage ; extra == 'test'
32
+ Requires-Dist: pytest ; extra == 'test'
33
+
34
+ # Python Rithmic API
35
+
36
+ A robust, async-based Python API designed to interface seamlessly with the Rithmic Protocol Buffer API. This package is built to provide an efficient and reliable connection to Rithmic's trading infrastructure, catering to advanced trading strategies and real-time data handling.
37
+
38
+ This was originally a fork of [pyrithmic](https://github.com/jacksonwoody/pyrithmic), but the code has been completely rewritten.
39
+
40
+ ## Key Enhancements
41
+
42
+ This repo introduces several key improvements and new features over the original repository, ensuring compatibility with modern Python environments and providing additional functionality:
43
+
44
+ - **Python 3.11+ Compatibility**: Refactored code to ensure smooth operation with the latest Python versions.
45
+ - **System Name Validation**: Implements pre-login validation of system names, as recommended by Rithmic support, with enhanced error handling during the login process.
46
+ - **Account Selection**: Allows users to specify which account to use when calling trading functions, rather than being restricted to the primary account.
47
+ - **STOP Orders**: Exposing STOP orders to users
48
+ - **Best Bid Offer (BBO) Streaming**: Integrates real-time Best Bid Offer tick streaming.
49
+ - **Historical Time Bars + Time Bars Streaming**:
50
+
51
+ The most significant upgrade is the transition to an async architecture, providing superior performance and responsiveness when dealing with real-time trading and market data.
52
+
53
+ ## Installation
54
+
55
+ ```
56
+ pip install git+https://github.com/rundef/pyrithmic.git#egg=pyrithmic
57
+ ```
58
+
59
+ ## Market data
60
+
61
+ ### Streaming Live Tick Data
62
+
63
+ Here's an example to get the front month contract for ES and stream market data:
64
+
65
+ ```python
66
+ import asyncio
67
+ from rithmic import RithmicClient, Gateway, DataType, LastTradePresenceBits
68
+
69
+ async def callback(data: dict):
70
+ if data["presence_bits"] & LastTradePresenceBits.LAST_TRADE:
71
+ print("received", data)
72
+
73
+ async def main():
74
+ client = RithmicClient(user="", password="", system_name="Rithmic Test", app_name="my_test_app", app_version="1.0", gateway=Gateway.TEST)
75
+ await client.connect()
76
+
77
+ # Request front month contract
78
+ symbol, exchange = "ES", "CME"
79
+ security_code = await client.get_front_month_contract(symbol, exchange)
80
+
81
+ # Stream market data
82
+ print(f"Streaming market data for {security_code}")
83
+ data_type = DataType.LAST_TRADE
84
+ client.on_tick += callback
85
+ await client.subscribe_to_market_data(security_code, exchange, data_type)
86
+
87
+ # Wait 10 seconds, unsubscribe and disconnect
88
+ await asyncio.sleep(10)
89
+ await client.unsubscribe_from_market_data(security_code, exchange, data_type)
90
+ await client.disconnect()
91
+
92
+ asyncio.run(main())
93
+ ```
94
+
95
+ ### Streaming Live Time Bars
96
+
97
+ ```python
98
+ import asyncio
99
+ from rithmic import RithmicClient, Gateway, TimeBarType
100
+
101
+ async def callback(data: dict):
102
+ print("received", data)
103
+
104
+ async def main():
105
+ client = RithmicClient(user="", password="", system_name="Rithmic Test", app_name="my_test_app", app_version="1.0", gateway=Gateway.TEST)
106
+ await client.connect()
107
+
108
+ # Request front month contract
109
+ symbol, exchange = "ES", "CME"
110
+ security_code = await client.get_front_month_contract(symbol, exchange)
111
+
112
+ # Stream market data
113
+ print(f"Streaming market data for {security_code}")
114
+
115
+ client.on_time_bar += callback
116
+ await client.subscribe_to_time_bar_data(security_code, exchange, TimeBarType.SECOND_BAR, 6)
117
+
118
+ # Wait 10 seconds, unsubscribe and disconnect
119
+ await asyncio.sleep(20)
120
+ await client.unsubscribe_from_time_bar_data(security_code, exchange, TimeBarType.SECOND_BAR, 6)
121
+ await client.disconnect()
122
+
123
+ asyncio.run(main())
124
+ ```
125
+
126
+ ## Order API
127
+
128
+ #### Placing a Market Order:
129
+
130
+ As a market order will be filled immediately, this script will submit the order and receive a fill straight away:
131
+
132
+ ```python
133
+ import asyncio
134
+ from datetime import datetime
135
+ from rithmic import RithmicClient, Gateway, OrderType, ExchangeOrderNotificationType, TransactionType
136
+
137
+ async def callback(notification):
138
+ if notification.notify_type == ExchangeOrderNotificationType.FILL:
139
+ print("order filled", notification)
140
+
141
+ async def main():
142
+ client = RithmicClient(user="", password="", system_name="Rithmic Test", app_name="my_test_app", app_version="1.0", gateway=Gateway.TEST)
143
+ await client.connect()
144
+
145
+ # Request front month contract
146
+ symbol, exchange = "ES", "CME"
147
+ security_code = await client.get_front_month_contract(symbol, exchange)
148
+
149
+ # Submit order
150
+ client.on_exchange_order_notification += callback
151
+
152
+ order_id = '{0}_order'.format(datetime.now().strftime('%Y%m%d_%H%M%S'))
153
+ await client.submit_order(
154
+ order_id,
155
+ security_code,
156
+ exchange,
157
+ qty=1,
158
+ order_type=OrderType.MARKET,
159
+ transaction_type=TransactionType.SELL,
160
+ #account_id="ABCD" # Mandatory if you have multiple accounts
161
+ )
162
+
163
+ await asyncio.sleep(1)
164
+
165
+ await client.disconnect()
166
+
167
+ asyncio.run(main())
168
+ ```
169
+
170
+ #### Placing a Limit Order and cancelling it
171
+
172
+ ```python
173
+ import asyncio
174
+ from datetime import datetime
175
+ from rithmic import RithmicClient, Gateway, OrderType, TransactionType
176
+
177
+ async def exchange_order_notification_callback(notification):
178
+ print("exchange order notification", notification)
179
+
180
+ async def main():
181
+ client = RithmicClient(user="", password="", system_name="Rithmic Test", app_name="my_test_app", app_version="1.0", gateway=Gateway.TEST)
182
+ await client.connect()
183
+
184
+ # Request front month contract
185
+ symbol, exchange = "ES", "CME"
186
+ security_code = await client.get_front_month_contract(symbol, exchange)
187
+
188
+ # Submit order
189
+ client.on_exchange_order_notification += exchange_order_notification_callback
190
+
191
+ order_id = '{0}_order'.format(datetime.now().strftime('%Y%m%d_%H%M%S'))
192
+ await client.submit_order(
193
+ order_id,
194
+ security_code,
195
+ exchange,
196
+ qty=1,
197
+ order_type=OrderType.LIMIT,
198
+ transaction_type=TransactionType.BUY,
199
+ price=5300.
200
+ )
201
+
202
+ await asyncio.sleep(1)
203
+ await client.cancel_order(order_id)
204
+
205
+ await asyncio.sleep(1)
206
+ await client.disconnect()
207
+
208
+ asyncio.run(main())
209
+ ```
210
+
211
+ ## History Data API
212
+
213
+ ### Fetch Historical Tick Data
214
+
215
+ The following example will fetch historical data, in a streaming fashion:
216
+
217
+ ```python
218
+ import asyncio
219
+ from datetime import datetime
220
+ from rithmic import RithmicClient, Gateway
221
+
222
+ async def main():
223
+ client = RithmicClient(user="", password="", system_name="Rithmic Test", app_name="my_test_app", app_version="1.0", gateway=Gateway.TEST)
224
+ await client.connect()
225
+
226
+ # Fetch historical tick data
227
+ ticks = await client.get_historical_tick_data(
228
+ "ESZ4",
229
+ "CME",
230
+ datetime(2024, 10, 15, 15, 30),
231
+ datetime(2024, 10, 15, 15, 31),
232
+ )
233
+
234
+ print(f"Received {len(ticks)} ticks")
235
+ print(f"Last tick timestamp: {ticks[-1]['datetime']}")
236
+
237
+ await client.disconnect()
238
+
239
+ asyncio.run(main())
240
+ ```
241
+
242
+ ### Fetch Historical Time Bars
243
+
244
+ ```python
245
+ import asyncio
246
+ from datetime import datetime
247
+ from rithmic import RithmicClient, Gateway, TimeBarType
248
+
249
+ async def main():
250
+ client = RithmicClient(user="", password="", system_name="Rithmic Test", app_name="my_test_app", app_version="1.0", gateway=Gateway.TEST)
251
+ await client.connect()
252
+
253
+ # Fetch historical time bar data
254
+ bars = await client.get_historical_time_bar(
255
+ "ESZ4",
256
+ "CME",
257
+ datetime(2024, 10, 15, 15, 30),
258
+ datetime(2024, 10, 15, 15, 31),
259
+ TimeBarType.SECOND_BAR,
260
+ 6
261
+ )
262
+
263
+ print(f"Received {len(bars)} bars")
264
+ print(f"Last bar timestamp: {bars[-1]['datetime']}")
265
+
266
+ await client.disconnect()
267
+
268
+ asyncio.run(main())
269
+ ```
270
+
271
+ ## Other methods
272
+
273
+ This code snippet will list your account summary, session orders and positions:
274
+
275
+ ```python
276
+ import asyncio
277
+ from rithmic import RithmicClient, Gateway
278
+
279
+ async def main():
280
+ client = RithmicClient(user="", password="", system_name="Rithmic Test", app_name="my_test_app", app_version="1.0", gateway=Gateway.TEST)
281
+ await client.connect()
282
+
283
+ account_id = "MY_ACCOUNT"
284
+
285
+ summary = await client.list_account_summary(account_id=account_id)
286
+ print("Account summary:", summary[0])
287
+
288
+ orders = await client.list_orders(account_id=account_id)
289
+ print("Orders:", orders)
290
+
291
+ positions = await client.list_positions(account_id=account_id)
292
+ print("Positions:", positions)
293
+
294
+ await client.disconnect()
295
+
296
+ asyncio.run(main())
297
+ ```
298
+
299
+ ## Testing
300
+
301
+ To execute the tests, use the following command: `make tests`
302
+
303
+ ## Unimplemented
304
+
305
+ The following features are currently not available in this package.
306
+ Contributions are welcome!
307
+ If you're interested in adding any of these features, please feel free to submit a Pull Request.
308
+
309
+ - Search Symbols Endpoint
310
+ - Bracket Orders
311
+ - One-Cancels-Other (OCO) Orders
312
+ - Market depth
313
+ - Tick bar historical & live data (Volume, Range or Tick bars)
@@ -0,0 +1,85 @@
1
+ rithmic/__init__.py,sha256=1Q5z44DrveueECZPApY2vfsv_8y9eeIqro9stbPcfPw,126
2
+ rithmic/client.py,sha256=tNTFlyJYAC0dEKQ7zpc6jnrW50tFdHBO5nE3HhbhVCs,3067
3
+ rithmic/enums.py,sha256=B2bPVBIavF1-J3bTSHmAe29ZqbJJgjk5nfMld-Hylbk,1060
4
+ rithmic/event.py,sha256=LWQcx7eFtH6Jb_TMB6tzcb9Dqx3YkMLGJhKDlPQ_0K4,546
5
+ rithmic/logger.py,sha256=qrtwz49bSCWtBimlGjj-H0T_LqsfCAwuX0Dn906-z5s,583
6
+ rithmic/certificates/rithmic_ssl_cert_auth_params,sha256=ij28uSqxxid2R_4quFNrXJgqu_2x8d9XKOAbkGq6lTo,2094
7
+ rithmic/plants/__init__.py,sha256=76NA41KpRQKkuqmIAAdJTWPgysmd_MOG8dDBFrKYJwY,122
8
+ rithmic/plants/base.py,sha256=bXb6cYcC-L3um0E2_70GlxymECkg4OA4KTjyxD1BQWo,12763
9
+ rithmic/plants/history.py,sha256=b2nXB9x11hyLSHob0zCB1F39C4odg_fIMVfFMP1Ljqs,7188
10
+ rithmic/plants/order.py,sha256=euZsMuzynHN-O-ulrI6zBWI1WyNz2HEoHHJ8UHVb5l8,8637
11
+ rithmic/plants/pnl.py,sha256=aI6_M5FLrtkMjchDZ7-MSy1uBCO22MIu_pcAnAWMaRo,2427
12
+ rithmic/plants/ticker.py,sha256=9fR4ceYUQJn33NewLOLNvQ5xP_T-RujcO5TNBIvnM44,2952
13
+ rithmic/protocol_buffers/__init__.py,sha256=G52UIHVcjDFFfPmK0iSeERgoHBWYRrxNsUsx5Y7uBa0,482
14
+ rithmic/protocol_buffers/account_pnl_position_update_pb2.py,sha256=juAsK-EF9ZP4GQi9txpgYgROY0oWqlOvFuzAHYRzyQA,3336
15
+ rithmic/protocol_buffers/base_pb2.py,sha256=GNh_JVG5_RDkj_NgZWChD2li82Szj58q5i0zhxA4Wrs,1018
16
+ rithmic/protocol_buffers/best_bid_offer_pb2.py,sha256=PUyUnRnGZwNJa2GCtIOTOSq2oG1-yI9pXoTjV8__A2c,2231
17
+ rithmic/protocol_buffers/exchange_order_notification_pb2.py,sha256=JCznownwHqdlPjD_1KQpMknuWpDvQoB1fZ4iwCicFIo,7026
18
+ rithmic/protocol_buffers/instrument_pnl_position_update_pb2.py,sha256=t36RsCjllltHLWQLNPYyIJgczD0oLG2VHlrVqkQnXZM,2837
19
+ rithmic/protocol_buffers/last_trade_pb2.py,sha256=ychwA6l2lwOH8Egbu6DJ_NY1s2zQ7tzIN3ez0ZfWFwU,2736
20
+ rithmic/protocol_buffers/request_account_list_pb2.py,sha256=D4bpH5jfxz5ouNeE7xZNCcJjczVufH2MOpHWhrmBGjU,1618
21
+ rithmic/protocol_buffers/request_account_rms_info_pb2.py,sha256=CQu8LbzHc9RqF51k7ZA9qkKZxTGsx59PldaQ2ly5WZQ,1648
22
+ rithmic/protocol_buffers/request_bracket_order_pb2.py,sha256=-Zh0ObENZlYQmdsfI8H4prwuTIQJ8dIjW1AmZHEGboU,6438
23
+ rithmic/protocol_buffers/request_cancel_order_pb2.py,sha256=2P2i6gGaO5JDJk4mfQfhhqFiTGs64jfrN37KNnVKIvs,1738
24
+ rithmic/protocol_buffers/request_front_month_contract_pb2.py,sha256=Nq4qEbFB5hsUMDozZoGo06eEtEAU810lDYcEe5x_vs8,1341
25
+ rithmic/protocol_buffers/request_heartbeat_pb2.py,sha256=tESZKoaZwBz7xAuhAsSuKCQm5sqnZ-a3Bk6OtuccctI,1219
26
+ rithmic/protocol_buffers/request_login_info_pb2.py,sha256=56LAAM-KhCLDFrwBRwVRUwEHSdb4N-EmMd46Wka7Zlk,1132
27
+ rithmic/protocol_buffers/request_login_pb2.py,sha256=B7CCH4wu8-DOAuuM4kvCqHTQkqJq9R9xYpA-9Rgyf-k,2050
28
+ rithmic/protocol_buffers/request_logout_pb2.py,sha256=dtBX_IEmhYoAcyt8iC-Ek1mQgHiH-8ARPiN1l1SY9ZM,1109
29
+ rithmic/protocol_buffers/request_market_data_update_pb2.py,sha256=VNNRnxLToiKaPOxsRWVV42J_SYZDujTAkyQVp54JwMM,2498
30
+ rithmic/protocol_buffers/request_modify_order_pb2.py,sha256=Bf_OQO_yyE1Ka1DcaH4U8TwuNo3cKmQlC4e5yp_s_Mc,3542
31
+ rithmic/protocol_buffers/request_new_order_pb2.py,sha256=QUImvGKy65zB_IY_C2ztGinDFA3uec-8PyLKlmwNjX0,4575
32
+ rithmic/protocol_buffers/request_pnl_position_snapshot_pb2.py,sha256=9gxxwyDaNQojwqgHzTK-JnYceqqj9RcREfCM9gdHdw8,1333
33
+ rithmic/protocol_buffers/request_pnl_position_updates_pb2.py,sha256=TdTUnlaeTuaHoXym2_CF9CRHtGqA1yX_0XSVAeqYeoA,1678
34
+ rithmic/protocol_buffers/request_product_rms_info_pb2.py,sha256=BROTYaf5EPk4C5WL3UvdT5s-bbhC3iXnI2Xr7GVUy64,1299
35
+ rithmic/protocol_buffers/request_reference_data_pb2.py,sha256=HvkWCN-7-KWQZ1rNb6r1AkX5r8PCSBHXf2QeDz1gMfA,1250
36
+ rithmic/protocol_buffers/request_rithmic_system_info_pb2.py,sha256=PSWWD7zxR2TtuRPdhfm25BLC9eI5E-5clDW9P3mQ94s,1181
37
+ rithmic/protocol_buffers/request_search_symbols_pb2.py,sha256=RVTPgShJYtJyBARZxrissFeuTLJe7lNirhhO9_bzoOE,2278
38
+ rithmic/protocol_buffers/request_show_order_history_detail_pb2.py,sha256=RtrNp51jKF66yEXjLn6NTR67fyvgcQDHKg4URos6G6w,1448
39
+ rithmic/protocol_buffers/request_show_orders_pb2.py,sha256=FrESadGL2XPfkTXLIM_iQYMmkhqz-hdJcCGiFKeqpu8,1272
40
+ rithmic/protocol_buffers/request_subscribe_for_order_updates_pb2.py,sha256=Wc8DQ_rMgDltNdX9KFVTAsV5DU2cr5Ip52rN3rUi6FA,1366
41
+ rithmic/protocol_buffers/request_subscribe_to_bracket_updates_pb2.py,sha256=5QsfdCvH0Sig6U1Sb0xfyIZkOhEbK4MMgC2cIfVffME,1369
42
+ rithmic/protocol_buffers/request_tick_bar_replay_pb2.py,sha256=F8n0iHuz8X9aRd6Qq1PAVEdLRRz7RqGjMUwotQKKWmc,3063
43
+ rithmic/protocol_buffers/request_tick_bar_update_pb2.py,sha256=zcZ1YM4UYludRFfAOb33H0OoumQg4dkrWuFg8BksCes,2518
44
+ rithmic/protocol_buffers/request_time_bar_replay_pb2.py,sha256=H7zJr1dvK_srEXFNMgiADplDpjiGKKHu59GnIXYeQ5Y,2609
45
+ rithmic/protocol_buffers/request_time_bar_update_pb2.py,sha256=Fn4cTCEZ9X7zC8v1Db0saPdS0JqIbgfB3Rk2NnivFTk,2064
46
+ rithmic/protocol_buffers/request_trade_routes_pb2.py,sha256=6U4FuO6-qWQTOXVYTDWEp71U8MtGgv79MZfd_9EqHF0,1207
47
+ rithmic/protocol_buffers/request_update_stop_bracket_level_pb2.py,sha256=SXwW5Bz0fahwZqxFBum5i6iBaXsuuh9aAhUjH2lVH-U,1492
48
+ rithmic/protocol_buffers/request_update_target_bracket_level_pb2.py,sha256=js1DvoPQhWzZE8sKZK7vexkchru_2hIsmRqzY-heP64,1507
49
+ rithmic/protocol_buffers/response_account_list_pb2.py,sha256=EJ7wLzTL09wTMtWmdZHTkxKCiNSfiXW-5TbSWaK6-90,1640
50
+ rithmic/protocol_buffers/response_account_rms_info_pb2.py,sha256=HBBO4vx1DytrgLnD9LeiGIYxcPvwQtEFW8_hOwZly2s,3327
51
+ rithmic/protocol_buffers/response_bracket_order_pb2.py,sha256=N8FFfxRPMKZPbgDYjBeB6xbyUCXnXQicWNOqI4tIzrM,1443
52
+ rithmic/protocol_buffers/response_cancel_order_pb2.py,sha256=ZYEoQIkHk87ewqWvA5PANgetwpJISdMI_kqGTLvaFQk,1393
53
+ rithmic/protocol_buffers/response_front_month_contract_pb2.py,sha256=Jyskw2XNsouEYxYf3JflRvXb6_6L-7f2xRym_BN0yZ4,1548
54
+ rithmic/protocol_buffers/response_heartbeat_pb2.py,sha256=-M6nRCzIAVeeAkgTNmDuo7E8VhU7oHJpDw7mgQtS9e4,1270
55
+ rithmic/protocol_buffers/response_login_info_pb2.py,sha256=iUWpmO_IgwCyctb1gJv7ptWtAEPcvu8w6RwQIv7Y2Wo,1739
56
+ rithmic/protocol_buffers/response_login_pb2.py,sha256=qJOJhJVD_CWipZK5G90mcGP7Tu93VOLtIfP8DgNHgZM,1507
57
+ rithmic/protocol_buffers/response_logout_pb2.py,sha256=Dk8gzoGZpxXgYxwnsNICTqneABoBB-9SSfzsypMjGa8,1163
58
+ rithmic/protocol_buffers/response_market_data_update_pb2.py,sha256=WF7CeeZoKAtGjefxyLR4m2R2hqucqYkiPszWH6mhHm8,1226
59
+ rithmic/protocol_buffers/response_modify_order_pb2.py,sha256=gaK-u3ATcKmnDxE4wfiy8BP3wKaa2j3QA4tTyaPQRmg,1393
60
+ rithmic/protocol_buffers/response_new_order_pb2.py,sha256=svzxD6QyPgnNsaHFOF8Sh9jWx-XIqmdgjo-2fVbnGe0,1419
61
+ rithmic/protocol_buffers/response_pnl_position_snapshot_pb2.py,sha256=nIaLkao5d-Ja1AgV9X4mZnQEd8ywCjTfmgQD8nvs7XE,1244
62
+ rithmic/protocol_buffers/response_pnl_position_updates_pb2.py,sha256=f_QlIDDP_t_q1dY7UI5jvplaFXym7eTkE-mM7gemEbU,1238
63
+ rithmic/protocol_buffers/response_product_rms_info_pb2.py,sha256=R7W6e2ZqafNIidc_GOJGeVPYSoOFItSI6KkmNydy6Jc,2339
64
+ rithmic/protocol_buffers/response_reference_data_pb2.py,sha256=Ats6Y1dzJtA7VrinwZKCg3Q4wHczle96wmWnuJVPqZc,3499
65
+ rithmic/protocol_buffers/response_rithmic_system_info_pb2.py,sha256=-YSOz4J9lN3tCLAUY-tD226FWgfF7He8-dnIttyQ-yE,1349
66
+ rithmic/protocol_buffers/response_search_symbols_pb2.py,sha256=M_85Y6uL_1aACXGgPMtnifKWH81ebHsHUtyJKjR5blU,1572
67
+ rithmic/protocol_buffers/response_show_order_history_detail_pb2.py,sha256=tElsRQO13PfGBMVc2ywDjsU_o4Vdqf_cNPYqiIqTcx8,1265
68
+ rithmic/protocol_buffers/response_show_orders_pb2.py,sha256=XsfXKUY6fS67fqCsjMuhR-djCJz1Mg0TeSlDuCFIv4o,1190
69
+ rithmic/protocol_buffers/response_subscribe_for_order_updates_pb2.py,sha256=a53bHIIk007_gjzYC7MnawOMqWhOiMaviy0qiTBtvD8,1274
70
+ rithmic/protocol_buffers/response_tick_bar_replay_pb2.py,sha256=axmw96yieIs_B3voC25ZE_FnO5B_kgLZpCEwHuw0en8,2764
71
+ rithmic/protocol_buffers/response_tick_bar_update_pb2.py,sha256=7xygfTLkYatqRJOe84EgAwBxgSAtpmPzxZVXtuixBHA,1211
72
+ rithmic/protocol_buffers/response_time_bar_replay_pb2.py,sha256=t2G2ilnEy1bQOTFN9CB63tyVy6NhpZfEBGQLdXBpSoQ,2474
73
+ rithmic/protocol_buffers/response_time_bar_update_pb2.py,sha256=wKQdmmRVUOR4Ojr5Rn9Xnr4ddhaAd7ZXvvVetwZlrGU,1211
74
+ rithmic/protocol_buffers/response_trade_routes_pb2.py,sha256=-PbzeSCAu8YjNzyRZ6YRti5b1R9QBGZEm035ymxhhE4,1539
75
+ rithmic/protocol_buffers/response_update_stop_bracket_level_pb2.py,sha256=LnKMMX5k4n_P3ZpXWvMb4j9Tr-qzmcOnEBp0TvCgvkM,1265
76
+ rithmic/protocol_buffers/response_update_target_bracket_level_pb2.py,sha256=ey9eR5__PiUp5Os5BOrrqTkA47hI75XRQUTl8QzHI9E,1274
77
+ rithmic/protocol_buffers/rithmic_order_notification_pb2.py,sha256=APpBnlvuQg1FcnIxLvS4yGcddIpSU5xZxL8cp3sbMm8,6454
78
+ rithmic/protocol_buffers/tick_bar_pb2.py,sha256=JlJqEFiyLciyXyvPdte3kl7fXBZhdnvMQiTnx1AIpyI,2393
79
+ rithmic/protocol_buffers/time_bar_pb2.py,sha256=0VU5dO_AmuLOaIKtP5I3ChRZ6DTlNDkxj60kLVqthiM,2145
80
+ rithmic/protocol_buffers/trade_route_pb2.py,sha256=pVYYk7U0j_UdNoQx6_3-qN8P2_fQcUpzWSgEhD15hnA,1333
81
+ async_rithmic-1.0.0.dist-info/LICENSE,sha256=5Xwftm2QG1YMTP6QnjXb9nFQnPz1d8buQhKsarguNeo,1072
82
+ async_rithmic-1.0.0.dist-info/METADATA,sha256=86nh_XELLtF_KofeNp7LskHa3r-VR9SRnZtIy-LWpqc,10190
83
+ async_rithmic-1.0.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
84
+ async_rithmic-1.0.0.dist-info/top_level.txt,sha256=dGQrFnYxpnxyYTagE7GFCH1OAL5bzhj_ufqdTJN-_H0,8
85
+ async_rithmic-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.41.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ rithmic
rithmic/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from rithmic.client import RithmicClient
2
+ from rithmic.enums import *
3
+ from rithmic.logger import logger
4
+
5
+ __version__ = '1.0.0'
@@ -0,0 +1,34 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
3
+ iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
4
+ cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
5
+ BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
6
+ MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
7
+ BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
8
+ aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
9
+ dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
10
+ AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
11
+ 3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
12
+ tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
13
+ Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
14
+ VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
15
+ 79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
16
+ c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
17
+ Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
18
+ c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
19
+ UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
20
+ Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
21
+ BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
22
+ A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
23
+ Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
24
+ VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
25
+ ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
26
+ 8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
27
+ iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
28
+ Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
29
+ XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
30
+ qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
31
+ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
32
+ L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
33
+ jjxDah2nGN59PRbxYvnKkKj9
34
+ -----END CERTIFICATE-----
rithmic/client.py ADDED
@@ -0,0 +1,104 @@
1
+ import ssl
2
+ import asyncio
3
+ from pathlib import Path
4
+
5
+ from rithmic.plants.ticker import TickerPlant
6
+ from rithmic.plants.history import HistoryPlant
7
+ from rithmic.plants.order import OrderPlant
8
+ from rithmic.plants.pnl import PnlPlant
9
+ from rithmic.event import Event
10
+ from rithmic.enums import Gateway
11
+ from rithmic.logger import logger
12
+
13
+ def _setup_ssl_context():
14
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
15
+ path = Path(__file__).parent / 'certificates'
16
+ localhost_pem = path / 'rithmic_ssl_cert_auth_params'
17
+ ssl_context.load_verify_locations(localhost_pem)
18
+ return ssl_context
19
+
20
+ class RithmicClient:
21
+ on_connected = Event()
22
+ on_tick = Event()
23
+ on_time_bar = Event()
24
+ on_historical_tick = Event()
25
+ on_historical_time_bar = Event()
26
+ on_rithmic_order_notification = Event()
27
+ on_exchange_order_notification = Event()
28
+
29
+ def __init__(
30
+ self,
31
+ user: str,
32
+ password: str,
33
+ system_name: str,
34
+ app_name: str,
35
+ app_version: str,
36
+ gateway: Gateway = Gateway.TEST,
37
+ **kwargs
38
+ ):
39
+
40
+ self.credentials = dict(
41
+ user=user,
42
+ password=password,
43
+ system_name=system_name,
44
+ app_name=app_name,
45
+ app_version=app_version,
46
+ gateway=f"wss://{gateway.value}",
47
+ )
48
+ self.ssl_context = _setup_ssl_context()
49
+ self.listeners = []
50
+
51
+ self.plants = {
52
+ "ticker": TickerPlant(self, **kwargs),
53
+ "order": OrderPlant(self, **kwargs),
54
+ "pnl": PnlPlant(self, **kwargs),
55
+ "history": HistoryPlant(self, **kwargs),
56
+ }
57
+
58
+ for plant in self.plants.values():
59
+ self._map_methods(plant)
60
+
61
+ def _map_methods(self, plant):
62
+ """
63
+ Binds plant's public methods to the current class instance
64
+ """
65
+ for method_name in dir(plant):
66
+ if not method_name.startswith('_'):
67
+ method = getattr(plant, method_name)
68
+ if callable(method):
69
+ setattr(self, method_name, method)
70
+
71
+ async def connect(self):
72
+ try:
73
+ for plant_type, plant in self.plants.items():
74
+ await plant._connect()
75
+ await plant._login()
76
+
77
+ logger.info(f"Connected to {plant_type} plant")
78
+
79
+ self.listeners.append(asyncio.create_task(plant._listen()))
80
+
81
+ await asyncio.sleep(0.1)
82
+
83
+ await self.on_connected.notify()
84
+
85
+ except:
86
+ logger.exception("Failed to connect")
87
+
88
+ async def disconnect(self):
89
+ for listener in self.listeners:
90
+ listener.cancel()
91
+ await asyncio.gather(*self.listeners, return_exceptions=True)
92
+ self.listeners = []
93
+
94
+ for plant_type, plant in self.plants.items():
95
+ await plant._logout()
96
+ await plant._disconnect()
97
+
98
+ logger.debug(f"Disconnected from {plant_type} plant")
99
+
100
+ def get_listeners(self):
101
+ return [
102
+ plant.listen()
103
+ for plant in self.plants.values()
104
+ ]
rithmic/enums.py ADDED
@@ -0,0 +1,32 @@
1
+ import enum
2
+
3
+ import rithmic.protocol_buffers as pb
4
+
5
+ class DataType(enum.Enum):
6
+ LAST_TRADE = 1
7
+ BBO = 2
8
+
9
+ OrderType = pb.request_new_order_pb2.RequestNewOrder.PriceType
10
+ OrderDuration = pb.request_new_order_pb2.RequestNewOrder.Duration
11
+ TransactionType = pb.request_new_order_pb2.RequestNewOrder.TransactionType
12
+
13
+ LastTradePresenceBits = pb.last_trade_pb2.LastTrade.PresenceBits
14
+ ExchangeOrderNotificationType = pb.exchange_order_notification_pb2.ExchangeOrderNotification.NotifyType
15
+
16
+ TimeBarType = pb.request_time_bar_replay_pb2.RequestTimeBarReplay.BarType
17
+
18
+ class Gateway(enum.Enum):
19
+ TEST = "rituz00100.rithmic.com:443"
20
+
21
+ CHICAGO = "rprotocol.rithmic.com:443"
22
+ SYDNEY = "au.rithmic.com:443"
23
+ SAO_PAULO = "br.rithmic.com:443"
24
+ COLO75 = "colo75.rithmic.com:443"
25
+ FRANKFURT = "de.rithmic.com:443"
26
+ HONGKONG = "hk.rithmic.com:443"
27
+ IRELAND = "ie.rithmic.com:443"
28
+ MUMBAI = "in.rithmic.com:443"
29
+ SEOUL = "kr.rithmic.com:443"
30
+ CAPETOWN = "za.rithmic.com:443"
31
+ TOKYO = "jp.rithmic.com:443"
32
+ SINGAPORE = "sg.rithmic.com:443"
rithmic/event.py ADDED
@@ -0,0 +1,21 @@
1
+ from rithmic.logger import logger
2
+
3
+ class Event:
4
+ def __init__(self):
5
+ self._subscribers = []
6
+
7
+ def __iadd__(self, callback):
8
+ self._subscribers.append(callback)
9
+ return self
10
+
11
+ def __isub__(self, callback):
12
+ self._subscribers.remove(callback)
13
+ return self
14
+
15
+ async def notify(self, *args, **kwargs):
16
+ for callback in self._subscribers:
17
+ try:
18
+ await callback(*args, **kwargs)
19
+ except:
20
+ logger.error(f"Error in callback")
21
+ raise
rithmic/logger.py ADDED
@@ -0,0 +1,20 @@
1
+ import logging
2
+ import sys
3
+
4
+ class Logger:
5
+ def __init__(self, level: int = logging.INFO):
6
+ self.logger = logging.getLogger("rithmic")
7
+ self.logger.setLevel(level)
8
+
9
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
10
+
11
+ console_handler = logging.StreamHandler(sys.stdout)
12
+ console_handler.setFormatter(formatter)
13
+
14
+ if not self.logger.hasHandlers():
15
+ self.logger.addHandler(console_handler)
16
+
17
+ def get_logger(self):
18
+ return self.logger
19
+
20
+ logger = Logger(level=logging.DEBUG).get_logger()
@@ -0,0 +1,4 @@
1
+ from .ticker import TickerPlant
2
+ from .order import OrderPlant
3
+ from .history import HistoryPlant
4
+ from .pnl import PnlPlant