walrasquant-lib 0.4.20__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 (105) hide show
  1. walrasquant/__init__.py +7 -0
  2. walrasquant/aggregation.py +449 -0
  3. walrasquant/backends/__init__.py +5 -0
  4. walrasquant/backends/db.py +109 -0
  5. walrasquant/backends/db_memory.py +61 -0
  6. walrasquant/backends/db_postgresql.py +321 -0
  7. walrasquant/backends/db_sqlite.py +310 -0
  8. walrasquant/base/__init__.py +24 -0
  9. walrasquant/base/api_client.py +46 -0
  10. walrasquant/base/connector.py +863 -0
  11. walrasquant/base/ems.py +794 -0
  12. walrasquant/base/exchange.py +213 -0
  13. walrasquant/base/oms.py +428 -0
  14. walrasquant/base/retry.py +220 -0
  15. walrasquant/base/sms.py +545 -0
  16. walrasquant/base/ws_client.py +408 -0
  17. walrasquant/config.py +284 -0
  18. walrasquant/constants.py +413 -0
  19. walrasquant/core/__init__.py +0 -0
  20. walrasquant/core/cache.py +688 -0
  21. walrasquant/core/clock.py +59 -0
  22. walrasquant/core/connection.py +41 -0
  23. walrasquant/core/entity.py +504 -0
  24. walrasquant/core/nautilius_core.py +103 -0
  25. walrasquant/core/registry.py +41 -0
  26. walrasquant/engine.py +745 -0
  27. walrasquant/error.py +34 -0
  28. walrasquant/exchange/__init__.py +13 -0
  29. walrasquant/exchange/base_factory.py +172 -0
  30. walrasquant/exchange/binance/__init__.py +30 -0
  31. walrasquant/exchange/binance/connector.py +1093 -0
  32. walrasquant/exchange/binance/constants.py +934 -0
  33. walrasquant/exchange/binance/ems.py +140 -0
  34. walrasquant/exchange/binance/error.py +48 -0
  35. walrasquant/exchange/binance/exchange.py +144 -0
  36. walrasquant/exchange/binance/factory.py +115 -0
  37. walrasquant/exchange/binance/oms.py +1807 -0
  38. walrasquant/exchange/binance/rest_api.py +1653 -0
  39. walrasquant/exchange/binance/schema.py +1063 -0
  40. walrasquant/exchange/binance/websockets.py +389 -0
  41. walrasquant/exchange/bitget/__init__.py +28 -0
  42. walrasquant/exchange/bitget/connector.py +578 -0
  43. walrasquant/exchange/bitget/constants.py +392 -0
  44. walrasquant/exchange/bitget/ems.py +202 -0
  45. walrasquant/exchange/bitget/error.py +36 -0
  46. walrasquant/exchange/bitget/exchange.py +128 -0
  47. walrasquant/exchange/bitget/factory.py +135 -0
  48. walrasquant/exchange/bitget/oms.py +1619 -0
  49. walrasquant/exchange/bitget/rest_api.py +610 -0
  50. walrasquant/exchange/bitget/schema.py +885 -0
  51. walrasquant/exchange/bitget/websockets.py +753 -0
  52. walrasquant/exchange/bybit/__init__.py +32 -0
  53. walrasquant/exchange/bybit/connector.py +819 -0
  54. walrasquant/exchange/bybit/constants.py +479 -0
  55. walrasquant/exchange/bybit/ems.py +93 -0
  56. walrasquant/exchange/bybit/error.py +36 -0
  57. walrasquant/exchange/bybit/exchange.py +108 -0
  58. walrasquant/exchange/bybit/factory.py +128 -0
  59. walrasquant/exchange/bybit/oms.py +1195 -0
  60. walrasquant/exchange/bybit/rest_api.py +570 -0
  61. walrasquant/exchange/bybit/schema.py +867 -0
  62. walrasquant/exchange/bybit/websockets.py +307 -0
  63. walrasquant/exchange/hyperliquid/__init__.py +28 -0
  64. walrasquant/exchange/hyperliquid/connector.py +370 -0
  65. walrasquant/exchange/hyperliquid/constants.py +371 -0
  66. walrasquant/exchange/hyperliquid/ems.py +156 -0
  67. walrasquant/exchange/hyperliquid/error.py +48 -0
  68. walrasquant/exchange/hyperliquid/exchange.py +120 -0
  69. walrasquant/exchange/hyperliquid/factory.py +135 -0
  70. walrasquant/exchange/hyperliquid/oms.py +1081 -0
  71. walrasquant/exchange/hyperliquid/rest_api.py +348 -0
  72. walrasquant/exchange/hyperliquid/schema.py +583 -0
  73. walrasquant/exchange/hyperliquid/websockets.py +592 -0
  74. walrasquant/exchange/okx/__init__.py +25 -0
  75. walrasquant/exchange/okx/connector.py +931 -0
  76. walrasquant/exchange/okx/constants.py +518 -0
  77. walrasquant/exchange/okx/ems.py +144 -0
  78. walrasquant/exchange/okx/error.py +66 -0
  79. walrasquant/exchange/okx/exchange.py +102 -0
  80. walrasquant/exchange/okx/factory.py +138 -0
  81. walrasquant/exchange/okx/oms.py +1199 -0
  82. walrasquant/exchange/okx/rest_api.py +799 -0
  83. walrasquant/exchange/okx/schema.py +1449 -0
  84. walrasquant/exchange/okx/websockets.py +420 -0
  85. walrasquant/exchange/registry.py +201 -0
  86. walrasquant/execution/__init__.py +24 -0
  87. walrasquant/execution/algorithm.py +968 -0
  88. walrasquant/execution/algorithms/__init__.py +3 -0
  89. walrasquant/execution/algorithms/twap.py +392 -0
  90. walrasquant/execution/config.py +34 -0
  91. walrasquant/execution/constants.py +27 -0
  92. walrasquant/execution/schema.py +62 -0
  93. walrasquant/indicator.py +382 -0
  94. walrasquant/push.py +77 -0
  95. walrasquant/schema.py +755 -0
  96. walrasquant/strategy.py +1805 -0
  97. walrasquant/tools/__init__.py +0 -0
  98. walrasquant/tools/pm2_wrapper.py +1016 -0
  99. walrasquant/web/__init__.py +26 -0
  100. walrasquant/web/app.py +157 -0
  101. walrasquant/web/server.py +92 -0
  102. walrasquant_lib-0.4.20.dist-info/METADATA +162 -0
  103. walrasquant_lib-0.4.20.dist-info/RECORD +105 -0
  104. walrasquant_lib-0.4.20.dist-info/WHEEL +4 -0
  105. walrasquant_lib-0.4.20.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,934 @@
1
+ from enum import Enum
2
+ from datetime import timedelta
3
+ from walrasquant.constants import (
4
+ KlineInterval,
5
+ AccountType,
6
+ OrderStatus,
7
+ OrderType,
8
+ PositionSide,
9
+ OrderSide,
10
+ TimeInForce,
11
+ TriggerType,
12
+ RateLimiter,
13
+ )
14
+ from walrasquant.error import KlineSupportedError
15
+ from walrasquant.exchange.binance.error import BinanceRateLimitError
16
+ from throttled.asyncio import Throttled, rate_limiter, RateLimiterType
17
+ from throttled import RateLimitResult
18
+ # from throttled import Throttled as ThrottledSync
19
+
20
+
21
+ class BinancePriceMatch(Enum):
22
+ """
23
+ NONE (No price match)
24
+ OPPONENT (counterparty best price)
25
+ OPPONENT_5 (the 5th best price from the counterparty)
26
+ OPPONENT_10 (the 10th best price from the counterparty)
27
+ OPPONENT_20 (the 20th best price from the counterparty)
28
+ QUEUE (the best price on the same side of the order book)
29
+ QUEUE_5 (the 5th best price on the same side of the order book)
30
+ QUEUE_10 (the 10th best price on the same side of the order book)
31
+ QUEUE_20 (the 20th best price on the same side of the order book)
32
+ """
33
+
34
+ NONE = "NONE"
35
+ OPPONENT = "OPPONENT"
36
+ OPPONENT_5 = "OPPONENT_5"
37
+ OPPONENT_10 = "OPPONENT_10"
38
+ OPPONENT_20 = "OPPONENT_20"
39
+ QUEUE = "QUEUE"
40
+ QUEUE_5 = "QUEUE_5"
41
+ QUEUE_10 = "QUEUE_10"
42
+ QUEUE_20 = "QUEUE_20"
43
+
44
+
45
+ class BinanceTriggerType(Enum):
46
+ MARK_PRICE = "MARK_PRICE"
47
+ CONTRACT_PRICE = "CONTRACT_PRICE"
48
+
49
+
50
+ class BinanceAccountEventReasonType(Enum):
51
+ DEPOSIT = "DEPOSIT"
52
+ WITHDRAW = "WITHDRAW"
53
+ ORDER = "ORDER"
54
+ FUNDING_FEE = "FUNDING_FEE"
55
+ WITHDRAW_REJECT = "WITHDRAW_REJECT"
56
+ ADJUSTMENT = "ADJUSTMENT"
57
+ INSURANCE_CLEAR = "INSURANCE_CLEAR"
58
+ ADMIN_DEPOSIT = "ADMIN_DEPOSIT"
59
+ ADMIN_WITHDRAW = "ADMIN_WITHDRAW"
60
+ MARGIN_TRANSFER = "MARGIN_TRANSFER"
61
+ MARGIN_TYPE_CHANGE = "MARGIN_TYPE_CHANGE"
62
+ ASSET_TRANSFER = "ASSET_TRANSFER"
63
+ OPTIONS_PREMIUM_FEE = "OPTIONS_PREMIUM_FEE"
64
+ OPTIONS_SETTLE_PROFIT = "OPTIONS_SETTLE_PROFIT"
65
+ AUTO_EXCHANGE = "AUTO_EXCHANGE"
66
+ COIN_SWAP_DEPOSIT = "COIN_SWAP_DEPOSIT"
67
+ COIN_SWAP_WITHDRAW = "COIN_SWAP_WITHDRAW"
68
+
69
+
70
+ class BinanceBusinessUnit(Enum):
71
+ """
72
+ Represents a Binance business unit.
73
+ """
74
+
75
+ UM = "UM"
76
+ CM = "CM"
77
+
78
+
79
+ class BinanceFuturesWorkingType(Enum):
80
+ """
81
+ Represents a Binance Futures working type.
82
+ """
83
+
84
+ MARK_PRICE = "MARK_PRICE"
85
+ CONTRACT_PRICE = "CONTRACT_PRICE"
86
+
87
+
88
+ class BinanceTimeInForce(Enum):
89
+ """
90
+ Represents a Binance order time in force.
91
+ """
92
+
93
+ GTC = "GTC"
94
+ IOC = "IOC"
95
+ FOK = "FOK"
96
+ GTX = "GTX" # FUTURES only, Good-Till-Crossing (Post Only)
97
+ GTD = "GTD" # FUTURES only
98
+ GTE_GTC = "GTE_GTC" # Undocumented
99
+
100
+
101
+ class BinanceOrderSide(Enum):
102
+ """
103
+ Represents a Binance order side.
104
+ """
105
+
106
+ BUY = "BUY"
107
+ SELL = "SELL"
108
+
109
+
110
+ class BinanceKlineInterval(Enum):
111
+ """
112
+ Represents a Binance kline chart interval.
113
+ """
114
+
115
+ SECOND_1 = "1s"
116
+ MINUTE_1 = "1m"
117
+ MINUTE_3 = "3m"
118
+ MINUTE_5 = "5m"
119
+ MINUTE_15 = "15m"
120
+ MINUTE_30 = "30m"
121
+ HOUR_1 = "1h"
122
+ HOUR_2 = "2h"
123
+ HOUR_4 = "4h"
124
+ HOUR_6 = "6h"
125
+ HOUR_8 = "8h"
126
+ HOUR_12 = "12h"
127
+ DAY_1 = "1d"
128
+ DAY_3 = "3d"
129
+ WEEK_1 = "1w"
130
+ MONTH_1 = "1M"
131
+
132
+
133
+ class BinanceWsEventType(Enum):
134
+ TRADE = "trade"
135
+ AGG_TRADE = "aggTrade"
136
+ BOOK_TICKER = "bookTicker"
137
+ KLINE = "kline"
138
+ MARK_PRICE_UPDATE = "markPriceUpdate"
139
+ DEPTH_UPDATE = "depthUpdate"
140
+
141
+
142
+ class BinanceUserDataStreamWsEventType(Enum):
143
+ TRADE_LITE = "TRADE_LITE"
144
+ MARGIN_CALL = "MARGIN_CALL"
145
+ ACCOUNT_UPDATE = "ACCOUNT_UPDATE"
146
+ ORDER_TRADE_UPDATE = "ORDER_TRADE_UPDATE"
147
+ POSITION_HISTORY_UPDATE = "POSITION_HISTORY_UPDATE"
148
+ ACCOUNT_CONFIG_UPDATE = "ACCOUNT_CONFIG_UPDATE"
149
+ STRATEGY_UPDATE = "STRATEGY_UPDATE"
150
+ GRID_UPDATE = "GRID_UPDATE"
151
+ CONDITIONAL_ORDER_TIGGER_REJECT = "CONDITIONAL_ORDER_TIGGER_REJECT"
152
+ OUT_BOUND_ACCOUNT_POSITION = "outboundAccountPosition"
153
+ BALANCE_UPDATE = "balanceUpdate"
154
+ EXECUTION_REPORT = "executionReport"
155
+ LISTING_STATUS = "listingStatus"
156
+ LISTEN_KEY_EXPIRED = "listenKeyExpired"
157
+ OPEN_ORDER_LOSS = "openOrderLoss"
158
+ LIABILITY_CHANGE = "liabilityChange"
159
+ RISK_LEVEL_CHANGE = "RISK_LEVEL_CHANGE"
160
+ CONDITIONAL_ORDER_TRADE_UPDATE = "CONDITIONAL_ORDER_TRADE_UPDATE"
161
+
162
+
163
+ class BinanceOrderType(Enum):
164
+ LIMIT = "LIMIT"
165
+ MARKET = "MARKET"
166
+
167
+ STOP = "STOP" # futures only
168
+ TAKE_PROFIT = "TAKE_PROFIT" # futures/spot in spot it is MARKET order in futures it is LIMIT order
169
+ TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET" # futures only
170
+ STOP_MARKET = "STOP_MARKET" # futures only
171
+
172
+ STOP_LOSS = "STOP_LOSS" # spot only
173
+ STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT" # spot only
174
+ TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT" # spot only
175
+
176
+ LIMIT_MAKER = "LIMIT_MAKER" # spot only
177
+ TRAILING_STOP_MARKET = "TRAILING_STOP_MARKET"
178
+
179
+ @property
180
+ def is_market(self):
181
+ return self in (
182
+ self.STOP_MARKET,
183
+ self.TAKE_PROFIT_MARKET,
184
+ self.STOP_LOSS,
185
+ self.TAKE_PROFIT,
186
+ self.MARKET,
187
+ )
188
+
189
+ @property
190
+ def is_limit(self):
191
+ return self in (
192
+ self.TAKE_PROFIT_LIMIT,
193
+ self.STOP_LOSS_LIMIT,
194
+ self.STOP,
195
+ self.TAKE_PROFIT,
196
+ self.LIMIT,
197
+ )
198
+
199
+
200
+ class BinanceExecutionType(Enum):
201
+ NEW = "NEW"
202
+ CANCELED = "CANCELED"
203
+ REJECTED = "REJECTED"
204
+ TRADE = "TRADE"
205
+ EXPIRED = "EXPIRED"
206
+ CALCULATED = "CALCULATED"
207
+ TRADE_PREVENTION = "TRADE_PREVENTION"
208
+ AMENDMENT = "AMENDMENT"
209
+
210
+
211
+ class BinanceOrderStatus(Enum):
212
+ NEW = "NEW"
213
+ PARTIALLY_FILLED = "PARTIALLY_FILLED"
214
+ FILLED = "FILLED"
215
+ CANCELED = "CANCELED"
216
+ EXPIRED = "EXPIRED"
217
+ EXPIRED_IN_MATCH = "EXPIRED_IN_MATCH"
218
+
219
+
220
+ class BinancePositionSide(Enum):
221
+ BOTH = "BOTH"
222
+ LONG = "LONG"
223
+ SHORT = "SHORT"
224
+
225
+ def parse_to_position_side(self) -> PositionSide:
226
+ if self == self.BOTH:
227
+ return PositionSide.FLAT
228
+ elif self == self.LONG:
229
+ return PositionSide.LONG
230
+ elif self == self.SHORT:
231
+ return PositionSide.SHORT
232
+ raise RuntimeError(f"Invalid position side: {self}")
233
+
234
+
235
+ class BinanceAccountType(AccountType):
236
+ SPOT = "SPOT"
237
+ MARGIN = "MARGIN"
238
+ ISOLATED_MARGIN = "ISOLATED_MARGIN"
239
+ USD_M_FUTURE = "USD_M_FUTURE"
240
+ COIN_M_FUTURE = "COIN_M_FUTURE"
241
+ PORTFOLIO_MARGIN = "PORTFOLIO_MARGIN"
242
+ SPOT_TESTNET = "SPOT_TESTNET"
243
+ USD_M_FUTURE_TESTNET = "USD_M_FUTURE_TESTNET"
244
+ COIN_M_FUTURE_TESTNET = "COIN_M_FUTURE_TESTNET"
245
+
246
+ @property
247
+ def exchange_id(self):
248
+ return "binance"
249
+
250
+ @property
251
+ def is_spot(self):
252
+ return self in (self.SPOT, self.SPOT_TESTNET)
253
+
254
+ @property
255
+ def is_margin(self):
256
+ return self in (self.MARGIN,)
257
+
258
+ @property
259
+ def is_isolated_margin(self):
260
+ return self in (self.ISOLATED_MARGIN,)
261
+
262
+ @property
263
+ def is_isolated_margin_or_margin(self):
264
+ return self in (self.MARGIN, self.ISOLATED_MARGIN)
265
+
266
+ @property
267
+ def is_spot_or_margin(self):
268
+ return self in (self.SPOT, self.MARGIN, self.ISOLATED_MARGIN, self.SPOT_TESTNET)
269
+
270
+ @property
271
+ def is_future(self):
272
+ return self in (
273
+ self.USD_M_FUTURE,
274
+ self.COIN_M_FUTURE,
275
+ self.USD_M_FUTURE_TESTNET,
276
+ self.COIN_M_FUTURE_TESTNET,
277
+ )
278
+
279
+ @property
280
+ def is_linear(self):
281
+ return self in (self.USD_M_FUTURE, self.USD_M_FUTURE_TESTNET)
282
+
283
+ @property
284
+ def is_inverse(self):
285
+ return self in (self.COIN_M_FUTURE, self.COIN_M_FUTURE_TESTNET)
286
+
287
+ @property
288
+ def is_portfolio_margin(self):
289
+ return self in (self.PORTFOLIO_MARGIN,)
290
+
291
+ @property
292
+ def is_testnet(self):
293
+ return self in (
294
+ self.SPOT_TESTNET,
295
+ self.USD_M_FUTURE_TESTNET,
296
+ self.COIN_M_FUTURE_TESTNET,
297
+ )
298
+
299
+ @property
300
+ def base_url(self):
301
+ return BASE_URLS[self]
302
+
303
+ @property
304
+ def ws_url(self):
305
+ return STREAM_URLS[self]
306
+
307
+ @property
308
+ def ws_order_url(self):
309
+ return WS_ORDER_URLS.get(self, None)
310
+
311
+ @property
312
+ def ws_public_url(self):
313
+ """Channel URL for high-freq public streams (bookTicker, depth). USD-M Futures only."""
314
+ return FUTURES_PUBLIC_STREAM_URLS.get(self)
315
+
316
+ @property
317
+ def ws_market_url(self):
318
+ """Channel URL for regular market streams (aggTrade, markPrice, kline). USD-M Futures only."""
319
+ return FUTURES_MARKET_STREAM_URLS.get(self)
320
+
321
+ @property
322
+ def ws_private_url(self):
323
+ """Channel URL for private user-data streams (listenKey). USD-M Futures only."""
324
+ return FUTURES_PRIVATE_STREAM_URLS.get(self)
325
+
326
+
327
+ class EndpointsType(Enum):
328
+ USER_DATA_STREAM = "USER_DATA_STREAM"
329
+ ACCOUNT = "ACCOUNT"
330
+ TRADING = "TRADING"
331
+ MARKET = "MARKET"
332
+ GENERAL = "GENERAL"
333
+
334
+
335
+ BASE_URLS = {
336
+ BinanceAccountType.SPOT: "https://api.binance.com",
337
+ BinanceAccountType.MARGIN: "https://api.binance.com",
338
+ BinanceAccountType.ISOLATED_MARGIN: "https://api.binance.com",
339
+ BinanceAccountType.USD_M_FUTURE: "https://fapi.binance.com",
340
+ BinanceAccountType.COIN_M_FUTURE: "https://dapi.binance.com",
341
+ BinanceAccountType.PORTFOLIO_MARGIN: "https://papi.binance.com",
342
+ BinanceAccountType.SPOT_TESTNET: "https://demo-api.binance.com",
343
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "https://demo-fapi.binance.com",
344
+ BinanceAccountType.COIN_M_FUTURE_TESTNET: "https://demo-dapi.binance.com",
345
+ }
346
+
347
+ STREAM_URLS = {
348
+ BinanceAccountType.SPOT: "wss://stream.binance.com:9443",
349
+ BinanceAccountType.MARGIN: "wss://stream.binance.com:9443",
350
+ BinanceAccountType.ISOLATED_MARGIN: "wss://stream.binance.com:9443",
351
+ BinanceAccountType.USD_M_FUTURE: "wss://fstream.binance.com",
352
+ BinanceAccountType.COIN_M_FUTURE: "wss://dstream.binance.com",
353
+ BinanceAccountType.PORTFOLIO_MARGIN: "wss://fstream.binance.com/pm",
354
+ BinanceAccountType.SPOT_TESTNET: "wss://demo-stream.binance.com",
355
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "wss://fstream.binancefuture.com",
356
+ BinanceAccountType.COIN_M_FUTURE_TESTNET: "wss://dstream.binancefuture.com",
357
+ }
358
+
359
+ # USD-M Futures channel-specific base URLs (Binance WebSocket URL migration)
360
+ # Public → high-freq book data: bookTicker, depth
361
+ # Market → regular market data: aggTrade, markPrice, kline, ticker, etc.
362
+ # Private → user data stream: listenKey / ORDER_TRADE_UPDATE / ACCOUNT_UPDATE
363
+ FUTURES_PUBLIC_STREAM_URLS = {
364
+ BinanceAccountType.USD_M_FUTURE: "wss://fstream.binance.com/public",
365
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "wss://fstream.binancefuture.com/public",
366
+ }
367
+
368
+ FUTURES_MARKET_STREAM_URLS = {
369
+ BinanceAccountType.USD_M_FUTURE: "wss://fstream.binance.com/market",
370
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "wss://fstream.binancefuture.com/market",
371
+ }
372
+
373
+ FUTURES_PRIVATE_STREAM_URLS = {
374
+ BinanceAccountType.USD_M_FUTURE: "wss://fstream.binance.com/private",
375
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "wss://fstream.binancefuture.com/private",
376
+ }
377
+
378
+ WS_ORDER_URLS = {
379
+ BinanceAccountType.SPOT: "wss://ws-api.binance.com:443/ws-api/v3",
380
+ BinanceAccountType.SPOT_TESTNET: "wss://demo-ws-api.binance.com/ws-api/v3",
381
+ BinanceAccountType.USD_M_FUTURE: "wss://ws-fapi.binance.com/ws-fapi/v1",
382
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "wss://testnet.binancefuture.com/ws-fapi/v1",
383
+ BinanceAccountType.COIN_M_FUTURE: "wss://ws-dapi.binance.com/ws-dapi/v1",
384
+ BinanceAccountType.COIN_M_FUTURE_TESTNET: "wss://testnet.binancefuture.com/ws-dapi/v1",
385
+ }
386
+
387
+
388
+ ENDPOINTS = {
389
+ EndpointsType.USER_DATA_STREAM: {
390
+ BinanceAccountType.SPOT: "/api/v3/userDataStream",
391
+ BinanceAccountType.MARGIN: "/sapi/v1/userDataStream",
392
+ BinanceAccountType.ISOLATED_MARGIN: "/sapi/v1/userDataStream/isolated",
393
+ BinanceAccountType.USD_M_FUTURE: "/fapi/v1/listenKey",
394
+ BinanceAccountType.COIN_M_FUTURE: "/dapi/v1/listenKey",
395
+ BinanceAccountType.PORTFOLIO_MARGIN: "/papi/v1/listenKey",
396
+ BinanceAccountType.SPOT_TESTNET: "/api/v3/userDataStream",
397
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "/fapi/v1/listenKey",
398
+ BinanceAccountType.COIN_M_FUTURE_TESTNET: "/dapi/v1/listenKey",
399
+ },
400
+ EndpointsType.TRADING: {
401
+ BinanceAccountType.SPOT: "/api/v3",
402
+ BinanceAccountType.MARGIN: "/sapi/v1",
403
+ BinanceAccountType.ISOLATED_MARGIN: "/sapi/v1",
404
+ BinanceAccountType.USD_M_FUTURE: "/fapi/v1",
405
+ BinanceAccountType.COIN_M_FUTURE: "/dapi/v1",
406
+ BinanceAccountType.PORTFOLIO_MARGIN: "/papi/v1",
407
+ BinanceAccountType.SPOT_TESTNET: "/api/v3",
408
+ BinanceAccountType.USD_M_FUTURE_TESTNET: "/fapi/v1",
409
+ BinanceAccountType.COIN_M_FUTURE_TESTNET: "/dapi/v1",
410
+ },
411
+ }
412
+
413
+
414
+ class BinanceEnumParser:
415
+ _binance_trigger_type_map = {
416
+ BinanceTriggerType.MARK_PRICE: TriggerType.MARK_PRICE,
417
+ BinanceTriggerType.CONTRACT_PRICE: TriggerType.LAST_PRICE,
418
+ }
419
+
420
+ _binance_kline_interval_map = {
421
+ BinanceKlineInterval.SECOND_1: KlineInterval.SECOND_1,
422
+ BinanceKlineInterval.MINUTE_1: KlineInterval.MINUTE_1,
423
+ BinanceKlineInterval.MINUTE_3: KlineInterval.MINUTE_3,
424
+ BinanceKlineInterval.MINUTE_5: KlineInterval.MINUTE_5,
425
+ BinanceKlineInterval.MINUTE_15: KlineInterval.MINUTE_15,
426
+ BinanceKlineInterval.MINUTE_30: KlineInterval.MINUTE_30,
427
+ BinanceKlineInterval.HOUR_1: KlineInterval.HOUR_1,
428
+ BinanceKlineInterval.HOUR_2: KlineInterval.HOUR_2,
429
+ BinanceKlineInterval.HOUR_4: KlineInterval.HOUR_4,
430
+ BinanceKlineInterval.HOUR_6: KlineInterval.HOUR_6,
431
+ BinanceKlineInterval.HOUR_8: KlineInterval.HOUR_8,
432
+ BinanceKlineInterval.HOUR_12: KlineInterval.HOUR_12,
433
+ BinanceKlineInterval.DAY_1: KlineInterval.DAY_1,
434
+ BinanceKlineInterval.DAY_3: KlineInterval.DAY_3,
435
+ BinanceKlineInterval.WEEK_1: KlineInterval.WEEK_1,
436
+ BinanceKlineInterval.MONTH_1: KlineInterval.MONTH_1,
437
+ }
438
+
439
+ _binance_order_status_map = {
440
+ BinanceOrderStatus.NEW: OrderStatus.ACCEPTED,
441
+ BinanceOrderStatus.PARTIALLY_FILLED: OrderStatus.PARTIALLY_FILLED,
442
+ BinanceOrderStatus.FILLED: OrderStatus.FILLED,
443
+ BinanceOrderStatus.CANCELED: OrderStatus.CANCELED,
444
+ BinanceOrderStatus.EXPIRED: OrderStatus.EXPIRED,
445
+ BinanceOrderStatus.EXPIRED_IN_MATCH: OrderStatus.EXPIRED,
446
+ }
447
+
448
+ _binance_position_side_map = {
449
+ BinancePositionSide.LONG: PositionSide.LONG,
450
+ BinancePositionSide.SHORT: PositionSide.SHORT,
451
+ BinancePositionSide.BOTH: PositionSide.FLAT,
452
+ }
453
+
454
+ _binance_order_side_map = {
455
+ BinanceOrderSide.BUY: OrderSide.BUY,
456
+ BinanceOrderSide.SELL: OrderSide.SELL,
457
+ }
458
+
459
+ _binance_order_time_in_force_map = {
460
+ BinanceTimeInForce.IOC: TimeInForce.IOC,
461
+ BinanceTimeInForce.GTC: TimeInForce.GTC,
462
+ BinanceTimeInForce.FOK: TimeInForce.FOK,
463
+ BinanceTimeInForce.GTX: TimeInForce.GTC, # FUTURES only
464
+ }
465
+
466
+ _binance_order_type_map = {
467
+ BinanceOrderType.LIMIT: OrderType.LIMIT,
468
+ BinanceOrderType.MARKET: OrderType.MARKET,
469
+ }
470
+
471
+ # ref1: https://developers.binance.com/docs/zh-CN/derivatives/usds-margined-futures/trade/rest-api
472
+ # ref2: https://developers.binance.com/docs/zh-CN/derivatives/coin-margined-futures/trade
473
+ _binance_futures_order_type_map = {
474
+ BinanceOrderType.LIMIT: OrderType.LIMIT,
475
+ BinanceOrderType.MARKET: OrderType.MARKET,
476
+ BinanceOrderType.STOP: OrderType.STOP_LOSS_LIMIT,
477
+ BinanceOrderType.TAKE_PROFIT: OrderType.TAKE_PROFIT_LIMIT,
478
+ BinanceOrderType.STOP_MARKET: OrderType.STOP_LOSS_MARKET,
479
+ BinanceOrderType.TAKE_PROFIT_MARKET: OrderType.TAKE_PROFIT_MARKET,
480
+ }
481
+
482
+ # ref: https://developers.binance.com/docs/zh-CN/binance-spot-api-docs/rest-api/trading-endpoints
483
+ _binance_spot_order_type_map = {
484
+ BinanceOrderType.LIMIT: OrderType.LIMIT,
485
+ BinanceOrderType.MARKET: OrderType.MARKET,
486
+ BinanceOrderType.STOP_LOSS: OrderType.STOP_LOSS_MARKET,
487
+ BinanceOrderType.STOP_LOSS_LIMIT: OrderType.STOP_LOSS_LIMIT,
488
+ BinanceOrderType.TAKE_PROFIT: OrderType.TAKE_PROFIT_MARKET,
489
+ BinanceOrderType.TAKE_PROFIT_LIMIT: OrderType.TAKE_PROFIT_LIMIT,
490
+ BinanceOrderType.LIMIT_MAKER: OrderType.POST_ONLY,
491
+ }
492
+
493
+ _order_status_to_binance_map = {v: k for k, v in _binance_order_status_map.items()}
494
+ _order_status_to_binance_map[OrderStatus.EXPIRED] = BinanceOrderStatus.EXPIRED
495
+ _position_side_to_binance_map = {
496
+ v: k for k, v in _binance_position_side_map.items()
497
+ }
498
+ _order_side_to_binance_map = {v: k for k, v in _binance_order_side_map.items()}
499
+ _time_in_force_to_binance_map = {
500
+ v: k for k, v in _binance_order_time_in_force_map.items()
501
+ }
502
+ _time_in_force_to_binance_map[TimeInForce.GTC] = BinanceTimeInForce.GTC
503
+ _order_type_to_binance_map = {v: k for k, v in _binance_order_type_map.items()}
504
+ _kline_interval_to_binance_map = {
505
+ v: k for k, v in _binance_kline_interval_map.items()
506
+ }
507
+
508
+ _futures_order_type_to_binance_map = {
509
+ v: k for k, v in _binance_futures_order_type_map.items()
510
+ }
511
+ _spot_order_type_to_binance_map = {
512
+ v: k for k, v in _binance_spot_order_type_map.items()
513
+ }
514
+ _trigger_type_to_binance_map = {v: k for k, v in _binance_trigger_type_map.items()}
515
+
516
+ @classmethod
517
+ def parse_kline_interval(cls, interval: BinanceKlineInterval) -> KlineInterval:
518
+ return cls._binance_kline_interval_map[interval]
519
+
520
+ @classmethod
521
+ def parse_order_status(cls, status: BinanceOrderStatus) -> OrderStatus:
522
+ return cls._binance_order_status_map[status]
523
+
524
+ @classmethod
525
+ def parse_futures_order_type(
526
+ cls,
527
+ order_type: BinanceOrderType,
528
+ time_in_force: BinanceTimeInForce | None = None,
529
+ ) -> OrderType:
530
+ if time_in_force == BinanceTimeInForce.GTX:
531
+ # GTX is a special case for futures, it is a post-only order
532
+ return OrderType.POST_ONLY
533
+ return cls._binance_futures_order_type_map[order_type]
534
+
535
+ @classmethod
536
+ def parse_spot_order_type(cls, order_type: BinanceOrderType) -> OrderType:
537
+ return cls._binance_spot_order_type_map[order_type]
538
+
539
+ @classmethod
540
+ def parse_trigger_type(cls, trigger_type: BinanceTriggerType) -> TriggerType:
541
+ return cls._binance_trigger_type_map[trigger_type]
542
+
543
+ @classmethod
544
+ def parse_position_side(cls, side: BinancePositionSide) -> PositionSide:
545
+ return cls._binance_position_side_map[side]
546
+
547
+ @classmethod
548
+ def parse_order_side(cls, side: BinanceOrderSide) -> OrderSide:
549
+ return cls._binance_order_side_map[side]
550
+
551
+ @classmethod
552
+ def parse_time_in_force(cls, tif: BinanceTimeInForce) -> TimeInForce:
553
+ return cls._binance_order_time_in_force_map[tif]
554
+
555
+ @classmethod
556
+ def parse_order_type(cls, order_type: BinanceOrderType) -> OrderType:
557
+ return cls._binance_order_type_map[order_type]
558
+
559
+ @classmethod
560
+ def to_binance_order_status(cls, status: OrderStatus) -> BinanceOrderStatus:
561
+ return cls._order_status_to_binance_map[status]
562
+
563
+ @classmethod
564
+ def to_binance_position_side(cls, side: PositionSide) -> BinancePositionSide:
565
+ return cls._position_side_to_binance_map[side]
566
+
567
+ @classmethod
568
+ def to_binance_order_side(cls, side: OrderSide) -> BinanceOrderSide:
569
+ return cls._order_side_to_binance_map[side]
570
+
571
+ @classmethod
572
+ def to_binance_time_in_force(cls, tif: TimeInForce) -> BinanceTimeInForce:
573
+ return cls._time_in_force_to_binance_map[tif]
574
+
575
+ @classmethod
576
+ def to_binance_order_type(cls, order_type: OrderType) -> BinanceOrderType:
577
+ return cls._order_type_to_binance_map[order_type]
578
+
579
+ @classmethod
580
+ def to_binance_futures_order_type(cls, order_type: OrderType) -> BinanceOrderType:
581
+ return cls._futures_order_type_to_binance_map[order_type]
582
+
583
+ @classmethod
584
+ def to_binance_spot_order_type(cls, order_type: OrderType) -> BinanceOrderType:
585
+ return cls._spot_order_type_to_binance_map[order_type]
586
+
587
+ @classmethod
588
+ def to_binance_trigger_type(cls, trigger_type: TriggerType) -> BinanceTriggerType:
589
+ return cls._trigger_type_to_binance_map[trigger_type]
590
+
591
+ @classmethod
592
+ def to_binance_kline_interval(cls, interval: KlineInterval) -> BinanceKlineInterval:
593
+ if interval not in cls._kline_interval_to_binance_map:
594
+ raise KlineSupportedError(
595
+ f"Kline interval {interval} is not supported by Binance"
596
+ )
597
+ return cls._kline_interval_to_binance_map[interval]
598
+
599
+
600
+ class BinanceRateLimitType(Enum):
601
+ ORDERS = "ORDERS"
602
+ REQUEST_WEIGHT = "REQUEST_WEIGHT"
603
+ RAW_REQUESTS = "RAW_REQUESTS"
604
+
605
+
606
+ class BinanceRateLimiter(RateLimiter):
607
+ # /api/v3 rate limits
608
+ # [
609
+ # {
610
+ # "rateLimitType": "REQUEST_WEIGHT",
611
+ # "interval": "MINUTE",
612
+ # "intervalNum": 1,
613
+ # "limit": 6000,
614
+ # },
615
+ # {
616
+ # "rateLimitType": "ORDERS",
617
+ # "interval": "SECOND",
618
+ # "intervalNum": 10,
619
+ # "limit": 100,
620
+ # },
621
+ # {
622
+ # "rateLimitType": "ORDERS",
623
+ # "interval": "DAY",
624
+ # "intervalNum": 1,
625
+ # "limit": 200000,
626
+ # },
627
+ # {
628
+ # "rateLimitType": "RAW_REQUESTS",
629
+ # "interval": "MINUTE",
630
+ # "intervalNum": 5,
631
+ # "limit": 61000,
632
+ # },
633
+ # ]
634
+
635
+ # /fapi/v1 rate limits
636
+ # [
637
+ # {
638
+ # "rateLimitType": "REQUEST_WEIGHT",
639
+ # "interval": "MINUTE",
640
+ # "intervalNum": 1,
641
+ # "limit": 2400,
642
+ # },
643
+ # {
644
+ # "rateLimitType": "ORDERS",
645
+ # "interval": "MINUTE",
646
+ # "intervalNum": 1,
647
+ # "limit": 1200,
648
+ # },
649
+ # {
650
+ # "rateLimitType": "ORDERS",
651
+ # "interval": "SECOND",
652
+ # "intervalNum": 10,
653
+ # "limit": 300,
654
+ # },
655
+ # ]
656
+
657
+ # [
658
+ # {
659
+ # "rateLimitType": "REQUEST_WEIGHT",
660
+ # "interval": "MINUTE",
661
+ # "intervalNum": 1,
662
+ # "limit": 2400,
663
+ # },
664
+ # {
665
+ # "rateLimitType": "ORDERS",
666
+ # "interval": "MINUTE",
667
+ # "intervalNum": 1,
668
+ # "limit": 1200,
669
+ # },
670
+ # ]
671
+
672
+ _BLOCKING_WEIGHT_TIMEOUT = 60
673
+ _BLOCKING_RAW_REQUEST_TIMEOUT = 300
674
+ _NON_BLOCKING_TIMEOUT = -1
675
+
676
+ def __init__(self, enable_rate_limit: bool = True):
677
+ self._enabled = enable_rate_limit
678
+ self._api_weight_limit = Throttled(
679
+ quota=rate_limiter.per_min(6000),
680
+ timeout=self._NON_BLOCKING_TIMEOUT,
681
+ using=RateLimiterType.FIXED_WINDOW.value,
682
+ )
683
+ self._api_raw_req_limit = Throttled(
684
+ quota=rate_limiter.per_duration(timedelta(minutes=5), limit=61000),
685
+ timeout=self._NON_BLOCKING_TIMEOUT,
686
+ using=RateLimiterType.FIXED_WINDOW.value,
687
+ )
688
+ self._api_order_sec_limit = Throttled(
689
+ quota=rate_limiter.per_duration(timedelta(seconds=10), limit=100),
690
+ timeout=self._NON_BLOCKING_TIMEOUT,
691
+ using=RateLimiterType.FIXED_WINDOW.value,
692
+ )
693
+ self._api_order_day_limit = Throttled(
694
+ quota=rate_limiter.per_day(200000),
695
+ timeout=self._NON_BLOCKING_TIMEOUT,
696
+ using=RateLimiterType.FIXED_WINDOW.value,
697
+ )
698
+
699
+ self._fapi_weight_limit = Throttled(
700
+ quota=rate_limiter.per_min(2400),
701
+ timeout=self._NON_BLOCKING_TIMEOUT,
702
+ using=RateLimiterType.FIXED_WINDOW.value,
703
+ )
704
+ self._fapi_order_sec_limit = Throttled(
705
+ quota=rate_limiter.per_duration(timedelta(seconds=10), limit=300),
706
+ timeout=self._NON_BLOCKING_TIMEOUT,
707
+ using=RateLimiterType.FIXED_WINDOW.value,
708
+ )
709
+ self._fapi_order_min_limit = Throttled(
710
+ quota=rate_limiter.per_min(1200),
711
+ timeout=self._NON_BLOCKING_TIMEOUT,
712
+ using=RateLimiterType.FIXED_WINDOW.value,
713
+ )
714
+ self._dapi_weight_limit = Throttled(
715
+ quota=rate_limiter.per_min(2400),
716
+ timeout=self._NON_BLOCKING_TIMEOUT,
717
+ using=RateLimiterType.FIXED_WINDOW.value,
718
+ )
719
+ self._dapi_order_min_limit = Throttled(
720
+ quota=rate_limiter.per_min(1200),
721
+ timeout=self._NON_BLOCKING_TIMEOUT,
722
+ using=RateLimiterType.FIXED_WINDOW.value,
723
+ )
724
+
725
+ self._papi_weight_limit = Throttled(
726
+ quota=rate_limiter.per_min(6000),
727
+ timeout=self._NON_BLOCKING_TIMEOUT,
728
+ using=RateLimiterType.FIXED_WINDOW.value,
729
+ )
730
+ self._papi_order_min_limit = Throttled(
731
+ quota=rate_limiter.per_min(1200),
732
+ timeout=self._NON_BLOCKING_TIMEOUT,
733
+ using=RateLimiterType.FIXED_WINDOW.value,
734
+ )
735
+
736
+ @staticmethod
737
+ def _raise_if_limited(
738
+ result: RateLimitResult, message: str, api_type: str, rate_limit_type: str
739
+ ):
740
+ if result.limited:
741
+ raise BinanceRateLimitError(
742
+ message,
743
+ retry_after=result.state.retry_after,
744
+ api_type=api_type,
745
+ rate_limit_type=rate_limit_type,
746
+ )
747
+
748
+ async def api_weight_limit(self, cost: int):
749
+ if not self._enabled:
750
+ return
751
+ result = await self._api_weight_limit.limit(
752
+ key="/api",
753
+ cost=cost,
754
+ timeout=self._BLOCKING_WEIGHT_TIMEOUT,
755
+ )
756
+ self._raise_if_limited(
757
+ result, "SPOT API weight limit exceeded", "api", "weight"
758
+ )
759
+ result = await self._api_raw_req_limit.limit(
760
+ key="/api",
761
+ cost=1,
762
+ timeout=self._BLOCKING_RAW_REQUEST_TIMEOUT,
763
+ )
764
+ self._raise_if_limited(
765
+ result,
766
+ "SPOT API raw request limit exceeded",
767
+ "api",
768
+ "raw_requests",
769
+ )
770
+
771
+ async def api_order_limit(
772
+ self,
773
+ cost: int,
774
+ order_sec_cost: int = 1,
775
+ order_day_cost: int = 1,
776
+ ):
777
+ if not self._enabled:
778
+ return
779
+ result = await self._api_order_sec_limit.limit(
780
+ key="/api",
781
+ cost=order_sec_cost,
782
+ timeout=self._NON_BLOCKING_TIMEOUT,
783
+ )
784
+ self._raise_if_limited(
785
+ result, "SPOT API order limit (10s) exceeded", "api", "orders"
786
+ )
787
+ result = await self._api_order_day_limit.limit(
788
+ key="/api",
789
+ cost=order_day_cost,
790
+ timeout=self._NON_BLOCKING_TIMEOUT,
791
+ )
792
+ self._raise_if_limited(
793
+ result, "SPOT API order limit (day) exceeded", "api", "orders"
794
+ )
795
+ result = await self._api_weight_limit.limit(
796
+ key="/api",
797
+ cost=cost,
798
+ timeout=self._NON_BLOCKING_TIMEOUT,
799
+ )
800
+ self._raise_if_limited(
801
+ result,
802
+ "SPOT API weight limit exceeded (order)",
803
+ "api",
804
+ "weight",
805
+ )
806
+ result = await self._api_raw_req_limit.limit(
807
+ key="/api",
808
+ cost=1,
809
+ timeout=self._NON_BLOCKING_TIMEOUT,
810
+ )
811
+ self._raise_if_limited(
812
+ result,
813
+ "SPOT API raw request limit exceeded (order)",
814
+ "api",
815
+ "raw_requests",
816
+ )
817
+
818
+ async def fapi_weight_limit(self, cost: int):
819
+ if not self._enabled:
820
+ return
821
+ result = await self._fapi_weight_limit.limit(
822
+ key="/fapi",
823
+ cost=cost,
824
+ timeout=self._BLOCKING_WEIGHT_TIMEOUT,
825
+ )
826
+ self._raise_if_limited(
827
+ result, "USD-M Futures weight limit exceeded", "fapi", "weight"
828
+ )
829
+
830
+ async def fapi_order_limit(
831
+ self,
832
+ cost: int = 1,
833
+ order_sec_cost: int = 1,
834
+ order_min_cost: int = 1,
835
+ ):
836
+ if not self._enabled:
837
+ return
838
+ result = await self._fapi_order_sec_limit.limit(
839
+ key="/fapi",
840
+ cost=order_sec_cost,
841
+ timeout=self._NON_BLOCKING_TIMEOUT,
842
+ )
843
+ self._raise_if_limited(
844
+ result, "USD-M Futures order limit (10s) exceeded", "fapi", "orders"
845
+ )
846
+ result = await self._fapi_order_min_limit.limit(
847
+ key="/fapi",
848
+ cost=order_min_cost,
849
+ timeout=self._NON_BLOCKING_TIMEOUT,
850
+ )
851
+ self._raise_if_limited(
852
+ result, "USD-M Futures order limit (1m) exceeded", "fapi", "orders"
853
+ )
854
+ result = await self._fapi_weight_limit.limit(
855
+ key="/fapi",
856
+ cost=cost,
857
+ timeout=self._NON_BLOCKING_TIMEOUT,
858
+ )
859
+ self._raise_if_limited(
860
+ result,
861
+ "USD-M Futures weight limit exceeded (order)",
862
+ "fapi",
863
+ "weight",
864
+ )
865
+
866
+ async def dapi_weight_limit(self, cost: int):
867
+ if not self._enabled:
868
+ return
869
+ result = await self._dapi_weight_limit.limit(
870
+ key="/dapi",
871
+ cost=cost,
872
+ timeout=self._BLOCKING_WEIGHT_TIMEOUT,
873
+ )
874
+ self._raise_if_limited(
875
+ result, "COIN-M Futures weight limit exceeded", "dapi", "weight"
876
+ )
877
+
878
+ async def dapi_order_limit(self, cost: int = 1, order_min_cost: int = 1):
879
+ if not self._enabled:
880
+ return
881
+ result = await self._dapi_order_min_limit.limit(
882
+ key="/dapi",
883
+ cost=order_min_cost,
884
+ timeout=self._NON_BLOCKING_TIMEOUT,
885
+ )
886
+ self._raise_if_limited(
887
+ result, "COIN-M Futures order limit (1m) exceeded", "dapi", "orders"
888
+ )
889
+ result = await self._dapi_weight_limit.limit(
890
+ key="/dapi",
891
+ cost=cost,
892
+ timeout=self._NON_BLOCKING_TIMEOUT,
893
+ )
894
+ self._raise_if_limited(
895
+ result,
896
+ "COIN-M Futures weight limit exceeded (order)",
897
+ "dapi",
898
+ "weight",
899
+ )
900
+
901
+ async def papi_weight_limit(self, cost: int):
902
+ if not self._enabled:
903
+ return
904
+ result = await self._papi_weight_limit.limit(
905
+ key="/papi",
906
+ cost=cost,
907
+ timeout=self._BLOCKING_WEIGHT_TIMEOUT,
908
+ )
909
+ self._raise_if_limited(
910
+ result, "Portfolio Margin weight limit exceeded", "papi", "weight"
911
+ )
912
+
913
+ async def papi_order_limit(self, cost: int = 1, order_min_cost: int = 1):
914
+ if not self._enabled:
915
+ return
916
+ result = await self._papi_order_min_limit.limit(
917
+ key="/papi",
918
+ cost=order_min_cost,
919
+ timeout=self._NON_BLOCKING_TIMEOUT,
920
+ )
921
+ self._raise_if_limited(
922
+ result, "Portfolio Margin order limit (1m) exceeded", "papi", "orders"
923
+ )
924
+ result = await self._papi_weight_limit.limit(
925
+ key="/papi",
926
+ cost=cost,
927
+ timeout=self._NON_BLOCKING_TIMEOUT,
928
+ )
929
+ self._raise_if_limited(
930
+ result,
931
+ "Portfolio Margin weight limit exceeded (order)",
932
+ "papi",
933
+ "weight",
934
+ )