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.
- walrasquant/__init__.py +7 -0
- walrasquant/aggregation.py +449 -0
- walrasquant/backends/__init__.py +5 -0
- walrasquant/backends/db.py +109 -0
- walrasquant/backends/db_memory.py +61 -0
- walrasquant/backends/db_postgresql.py +321 -0
- walrasquant/backends/db_sqlite.py +310 -0
- walrasquant/base/__init__.py +24 -0
- walrasquant/base/api_client.py +46 -0
- walrasquant/base/connector.py +863 -0
- walrasquant/base/ems.py +794 -0
- walrasquant/base/exchange.py +213 -0
- walrasquant/base/oms.py +428 -0
- walrasquant/base/retry.py +220 -0
- walrasquant/base/sms.py +545 -0
- walrasquant/base/ws_client.py +408 -0
- walrasquant/config.py +284 -0
- walrasquant/constants.py +413 -0
- walrasquant/core/__init__.py +0 -0
- walrasquant/core/cache.py +688 -0
- walrasquant/core/clock.py +59 -0
- walrasquant/core/connection.py +41 -0
- walrasquant/core/entity.py +504 -0
- walrasquant/core/nautilius_core.py +103 -0
- walrasquant/core/registry.py +41 -0
- walrasquant/engine.py +745 -0
- walrasquant/error.py +34 -0
- walrasquant/exchange/__init__.py +13 -0
- walrasquant/exchange/base_factory.py +172 -0
- walrasquant/exchange/binance/__init__.py +30 -0
- walrasquant/exchange/binance/connector.py +1093 -0
- walrasquant/exchange/binance/constants.py +934 -0
- walrasquant/exchange/binance/ems.py +140 -0
- walrasquant/exchange/binance/error.py +48 -0
- walrasquant/exchange/binance/exchange.py +144 -0
- walrasquant/exchange/binance/factory.py +115 -0
- walrasquant/exchange/binance/oms.py +1807 -0
- walrasquant/exchange/binance/rest_api.py +1653 -0
- walrasquant/exchange/binance/schema.py +1063 -0
- walrasquant/exchange/binance/websockets.py +389 -0
- walrasquant/exchange/bitget/__init__.py +28 -0
- walrasquant/exchange/bitget/connector.py +578 -0
- walrasquant/exchange/bitget/constants.py +392 -0
- walrasquant/exchange/bitget/ems.py +202 -0
- walrasquant/exchange/bitget/error.py +36 -0
- walrasquant/exchange/bitget/exchange.py +128 -0
- walrasquant/exchange/bitget/factory.py +135 -0
- walrasquant/exchange/bitget/oms.py +1619 -0
- walrasquant/exchange/bitget/rest_api.py +610 -0
- walrasquant/exchange/bitget/schema.py +885 -0
- walrasquant/exchange/bitget/websockets.py +753 -0
- walrasquant/exchange/bybit/__init__.py +32 -0
- walrasquant/exchange/bybit/connector.py +819 -0
- walrasquant/exchange/bybit/constants.py +479 -0
- walrasquant/exchange/bybit/ems.py +93 -0
- walrasquant/exchange/bybit/error.py +36 -0
- walrasquant/exchange/bybit/exchange.py +108 -0
- walrasquant/exchange/bybit/factory.py +128 -0
- walrasquant/exchange/bybit/oms.py +1195 -0
- walrasquant/exchange/bybit/rest_api.py +570 -0
- walrasquant/exchange/bybit/schema.py +867 -0
- walrasquant/exchange/bybit/websockets.py +307 -0
- walrasquant/exchange/hyperliquid/__init__.py +28 -0
- walrasquant/exchange/hyperliquid/connector.py +370 -0
- walrasquant/exchange/hyperliquid/constants.py +371 -0
- walrasquant/exchange/hyperliquid/ems.py +156 -0
- walrasquant/exchange/hyperliquid/error.py +48 -0
- walrasquant/exchange/hyperliquid/exchange.py +120 -0
- walrasquant/exchange/hyperliquid/factory.py +135 -0
- walrasquant/exchange/hyperliquid/oms.py +1081 -0
- walrasquant/exchange/hyperliquid/rest_api.py +348 -0
- walrasquant/exchange/hyperliquid/schema.py +583 -0
- walrasquant/exchange/hyperliquid/websockets.py +592 -0
- walrasquant/exchange/okx/__init__.py +25 -0
- walrasquant/exchange/okx/connector.py +931 -0
- walrasquant/exchange/okx/constants.py +518 -0
- walrasquant/exchange/okx/ems.py +144 -0
- walrasquant/exchange/okx/error.py +66 -0
- walrasquant/exchange/okx/exchange.py +102 -0
- walrasquant/exchange/okx/factory.py +138 -0
- walrasquant/exchange/okx/oms.py +1199 -0
- walrasquant/exchange/okx/rest_api.py +799 -0
- walrasquant/exchange/okx/schema.py +1449 -0
- walrasquant/exchange/okx/websockets.py +420 -0
- walrasquant/exchange/registry.py +201 -0
- walrasquant/execution/__init__.py +24 -0
- walrasquant/execution/algorithm.py +968 -0
- walrasquant/execution/algorithms/__init__.py +3 -0
- walrasquant/execution/algorithms/twap.py +392 -0
- walrasquant/execution/config.py +34 -0
- walrasquant/execution/constants.py +27 -0
- walrasquant/execution/schema.py +62 -0
- walrasquant/indicator.py +382 -0
- walrasquant/push.py +77 -0
- walrasquant/schema.py +755 -0
- walrasquant/strategy.py +1805 -0
- walrasquant/tools/__init__.py +0 -0
- walrasquant/tools/pm2_wrapper.py +1016 -0
- walrasquant/web/__init__.py +26 -0
- walrasquant/web/app.py +157 -0
- walrasquant/web/server.py +92 -0
- walrasquant_lib-0.4.20.dist-info/METADATA +162 -0
- walrasquant_lib-0.4.20.dist-info/RECORD +105 -0
- walrasquant_lib-0.4.20.dist-info/WHEEL +4 -0
- walrasquant_lib-0.4.20.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import msgspec
|
|
3
|
+
from typing import Dict, Any, Optional, Union, List, cast
|
|
4
|
+
import base64
|
|
5
|
+
from urllib.parse import urlencode
|
|
6
|
+
import aiohttp
|
|
7
|
+
from walrasquant.base import ApiClient, RetryManager
|
|
8
|
+
from walrasquant.exchange.okx.constants import (
|
|
9
|
+
OkxRateLimiter,
|
|
10
|
+
)
|
|
11
|
+
from walrasquant.exchange.okx.error import OkxHttpError, OkxRequestError, retry_check
|
|
12
|
+
from walrasquant.exchange.okx.schema import (
|
|
13
|
+
OkxPlaceOrderResponse,
|
|
14
|
+
OkxCancelOrderResponse,
|
|
15
|
+
OkxGeneralResponse,
|
|
16
|
+
OkxErrorResponse,
|
|
17
|
+
OkxBalanceResponse,
|
|
18
|
+
OkxPositionResponse,
|
|
19
|
+
OkxCandlesticksResponse,
|
|
20
|
+
OkxSavingsBalanceResponse,
|
|
21
|
+
OkxSavingsPurchaseRedemptResponse,
|
|
22
|
+
OkxSavingsLendingRateSummaryResponse,
|
|
23
|
+
OkxSavingsLendingRateHistoryResponse,
|
|
24
|
+
OkxAssetTransferResponse,
|
|
25
|
+
OkxAmendOrderResponse,
|
|
26
|
+
OkxFinanceStakingDefiRedeemResponse,
|
|
27
|
+
OkxFinanceStakingDefiPurchaseResponse,
|
|
28
|
+
OkxFinanceStakingDefiOffersResponse,
|
|
29
|
+
OkxAccountConfigResponse,
|
|
30
|
+
OkxIndexCandlesticksResponse,
|
|
31
|
+
OkxBatchOrderResponse,
|
|
32
|
+
OkxCancelBatchOrderResponse,
|
|
33
|
+
OkxTickersResponse,
|
|
34
|
+
OkxOrderResponse,
|
|
35
|
+
)
|
|
36
|
+
from walrasquant.core.nautilius_core import hmac_signature, LiveClock
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OkxApiClient(ApiClient):
|
|
40
|
+
_limiter: OkxRateLimiter
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
clock: LiveClock,
|
|
45
|
+
api_key: Optional[str] = None,
|
|
46
|
+
secret: Optional[str] = None,
|
|
47
|
+
passphrase: Optional[str] = None,
|
|
48
|
+
testnet: bool = False,
|
|
49
|
+
timeout: int = 10,
|
|
50
|
+
enable_rate_limit: bool = True,
|
|
51
|
+
max_retries: int = 0,
|
|
52
|
+
delay_initial_ms: int = 100,
|
|
53
|
+
delay_max_ms: int = 800,
|
|
54
|
+
backoff_factor: int = 2,
|
|
55
|
+
):
|
|
56
|
+
super().__init__(
|
|
57
|
+
clock=clock,
|
|
58
|
+
api_key=api_key,
|
|
59
|
+
secret=secret,
|
|
60
|
+
timeout=timeout,
|
|
61
|
+
rate_limiter=OkxRateLimiter(enable_rate_limit),
|
|
62
|
+
retry_manager=RetryManager(
|
|
63
|
+
max_retries=max_retries,
|
|
64
|
+
delay_initial_ms=delay_initial_ms,
|
|
65
|
+
delay_max_ms=delay_max_ms,
|
|
66
|
+
backoff_factor=backoff_factor,
|
|
67
|
+
exc_types=(OkxRequestError, aiohttp.ClientError),
|
|
68
|
+
retry_check=retry_check,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
self._base_url = "https://www.okx.com"
|
|
73
|
+
self._passphrase = passphrase
|
|
74
|
+
self._testnet = testnet
|
|
75
|
+
self._place_order_decoder = msgspec.json.Decoder(OkxPlaceOrderResponse)
|
|
76
|
+
self._cancel_order_decoder = msgspec.json.Decoder(OkxCancelOrderResponse)
|
|
77
|
+
self._general_response_decoder = msgspec.json.Decoder(OkxGeneralResponse)
|
|
78
|
+
self._error_response_decoder = msgspec.json.Decoder(OkxErrorResponse)
|
|
79
|
+
self._balance_response_decoder = msgspec.json.Decoder(
|
|
80
|
+
OkxBalanceResponse, strict=False
|
|
81
|
+
)
|
|
82
|
+
self._position_response_decoder = msgspec.json.Decoder(
|
|
83
|
+
OkxPositionResponse, strict=False
|
|
84
|
+
)
|
|
85
|
+
self._candles_response_decoder = msgspec.json.Decoder(
|
|
86
|
+
OkxCandlesticksResponse, strict=False
|
|
87
|
+
)
|
|
88
|
+
self._index_candles_response_decoder = msgspec.json.Decoder(
|
|
89
|
+
OkxIndexCandlesticksResponse, strict=False
|
|
90
|
+
)
|
|
91
|
+
self._savings_balance_response_decoder = msgspec.json.Decoder(
|
|
92
|
+
OkxSavingsBalanceResponse, strict=False
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self._savings_purchase_redempt_response_decoder = msgspec.json.Decoder(
|
|
96
|
+
OkxSavingsPurchaseRedemptResponse, strict=False
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self._savings_lending_rate_summary_response_decoder = msgspec.json.Decoder(
|
|
100
|
+
OkxSavingsLendingRateSummaryResponse, strict=False
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
self._savings_lending_rate_history_response_decoder = msgspec.json.Decoder(
|
|
104
|
+
OkxSavingsLendingRateHistoryResponse, strict=False
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self._asset_transfer_response_decoder = msgspec.json.Decoder(
|
|
108
|
+
OkxAssetTransferResponse, strict=False
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
self._amend_order_response_decoder = msgspec.json.Decoder(
|
|
112
|
+
OkxAmendOrderResponse, strict=False
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
self._finance_staking_defi_redeem_response_decoder = msgspec.json.Decoder(
|
|
116
|
+
OkxFinanceStakingDefiRedeemResponse, strict=False
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self._finance_staking_defi_purchase_response_decoder = msgspec.json.Decoder(
|
|
120
|
+
OkxFinanceStakingDefiPurchaseResponse, strict=False
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
self._finance_staking_defi_offers_response_decoder = msgspec.json.Decoder(
|
|
124
|
+
OkxFinanceStakingDefiOffersResponse, strict=False
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self._account_config_response_decoder = msgspec.json.Decoder(
|
|
128
|
+
OkxAccountConfigResponse, strict=False
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
self._batch_order_response_decoder = msgspec.json.Decoder(OkxBatchOrderResponse)
|
|
132
|
+
|
|
133
|
+
self._cancel_batch_order_response_decoder = msgspec.json.Decoder(
|
|
134
|
+
OkxCancelBatchOrderResponse
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self._tickers_response_decoder = msgspec.json.Decoder(OkxTickersResponse)
|
|
138
|
+
|
|
139
|
+
self._order_response_decoder = msgspec.json.Decoder(
|
|
140
|
+
OkxOrderResponse, strict=False
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
self._headers = {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
"User-Agent": "TradingBot/1.0",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if self._testnet:
|
|
149
|
+
self._headers["x-simulated-trading"] = "1"
|
|
150
|
+
|
|
151
|
+
async def get_api_v5_account_balance(
|
|
152
|
+
self, ccy: str | None = None
|
|
153
|
+
) -> OkxBalanceResponse:
|
|
154
|
+
"""
|
|
155
|
+
https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-balance
|
|
156
|
+
"""
|
|
157
|
+
endpoint = "/api/v5/account/balance"
|
|
158
|
+
payload = {"ccy": ccy} if ccy else {}
|
|
159
|
+
cost = self._get_rate_limit_cost(1)
|
|
160
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
161
|
+
raw = await self._fetch_async("GET", endpoint, payload=payload, signed=True)
|
|
162
|
+
return self._balance_response_decoder.decode(raw)
|
|
163
|
+
|
|
164
|
+
async def get_api_v5_account_positions(
|
|
165
|
+
self,
|
|
166
|
+
inst_type: str | None = None,
|
|
167
|
+
inst_id: str | None = None,
|
|
168
|
+
pos_id: str | None = None,
|
|
169
|
+
) -> OkxPositionResponse:
|
|
170
|
+
"""
|
|
171
|
+
https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-positions
|
|
172
|
+
"""
|
|
173
|
+
endpoint = "/api/v5/account/positions"
|
|
174
|
+
payload = {
|
|
175
|
+
k: v
|
|
176
|
+
for k, v in {
|
|
177
|
+
"instType": inst_type,
|
|
178
|
+
"instId": inst_id,
|
|
179
|
+
"posId": pos_id,
|
|
180
|
+
}.items()
|
|
181
|
+
if v is not None
|
|
182
|
+
}
|
|
183
|
+
cost = self._get_rate_limit_cost(1)
|
|
184
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
185
|
+
raw = await self._fetch_async("GET", endpoint, payload=payload, signed=True)
|
|
186
|
+
return self._position_response_decoder.decode(raw)
|
|
187
|
+
|
|
188
|
+
async def post_api_v5_trade_order(
|
|
189
|
+
self,
|
|
190
|
+
inst_id: str,
|
|
191
|
+
td_mode: str,
|
|
192
|
+
side: str,
|
|
193
|
+
ord_type: str,
|
|
194
|
+
sz: str,
|
|
195
|
+
**kwargs,
|
|
196
|
+
) -> OkxPlaceOrderResponse:
|
|
197
|
+
"""
|
|
198
|
+
Place a new order
|
|
199
|
+
https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-place-order
|
|
200
|
+
|
|
201
|
+
{'arg': {'channel': 'orders', 'instType': 'ANY', 'uid': '611800569950521616'}, 'data': [{'instType': 'SWAP', 'instId': 'BTC-USDT-SWAP', 'tgtCcy': '', 'ccy': '', 'ordId': '1993784914940116992', 'clOrdId': '', 'algoClOrdId': '', 'algoId': '', 'tag': '', 'px': '80000', 'sz': '0.1', 'notionalUsd': '80.0128', 'ordType': 'limit', 'side': 'buy', 'posSide': 'long', 'tdMode': 'cross', 'accFillSz': '0', 'fillNotionalUsd': '', 'avgPx': '0', 'state': 'canceled', 'lever': '3', 'pnl': '0', 'feeCcy': 'USDT', 'fee': '0', 'rebateCcy': 'USDT', 'rebate': '0', 'category': 'normal', 'uTime': '1731921825881', 'cTime': '1731921820806', 'source': '', 'reduceOnly': 'false', 'cancelSource': '1', 'quickMgnType': '', 'stpId': '', 'stpMode': 'cancel_maker', 'attachAlgoClOrdId': '', 'lastPx': '91880', 'isTpLimit': 'false', 'slTriggerPx': '', 'slTriggerPxType': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'slOrdPx': '', 'fillPx': '', 'tradeId': '', 'fillSz': '0', 'fillTime': '', 'fillPnl': '0', 'fillFee': '0', 'fillFeeCcy': '', 'execType': '', 'fillPxVol': '', 'fillPxUsd': '', 'fillMarkVol': '', 'fillFwdPx': '', 'fillMarkPx': '', 'amendSource': '', 'reqId': '', 'amendResult': '', 'code': '0', 'msg': '', 'pxType': '', 'pxUsd': '', 'pxVol': '', 'linkedAlgoOrd': {'algoId': ''}, 'attachAlgoOrds': []}]}
|
|
202
|
+
"""
|
|
203
|
+
endpoint = "/api/v5/trade/order"
|
|
204
|
+
payload = {
|
|
205
|
+
"instId": inst_id,
|
|
206
|
+
"tdMode": td_mode,
|
|
207
|
+
"side": side,
|
|
208
|
+
"ordType": ord_type,
|
|
209
|
+
"sz": sz,
|
|
210
|
+
**kwargs,
|
|
211
|
+
}
|
|
212
|
+
cost = self._get_rate_limit_cost(1)
|
|
213
|
+
await self._limiter.order_limit(endpoint, cost=cost)
|
|
214
|
+
raw = await self._fetch("POST", endpoint, payload=payload, signed=True)
|
|
215
|
+
return self._place_order_decoder.decode(raw)
|
|
216
|
+
|
|
217
|
+
async def post_api_v5_trade_cancel_order(
|
|
218
|
+
self, instId: str, clOrdId: str
|
|
219
|
+
) -> OkxCancelOrderResponse:
|
|
220
|
+
"""
|
|
221
|
+
Cancel an existing order
|
|
222
|
+
https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-cancel-order
|
|
223
|
+
"""
|
|
224
|
+
endpoint = "/api/v5/trade/cancel-order"
|
|
225
|
+
payload = {"instId": instId, "clOrdId": clOrdId}
|
|
226
|
+
|
|
227
|
+
# cost = self._get_rate_limit_cost(1)
|
|
228
|
+
# await self._limiter.order_limit(endpoint, cost=cost)
|
|
229
|
+
raw = await self._fetch("POST", endpoint, payload=payload, signed=True)
|
|
230
|
+
return self._cancel_order_decoder.decode(raw)
|
|
231
|
+
|
|
232
|
+
async def post_api_v5_trade_cancel_batch_order(
|
|
233
|
+
self, payload: list[dict]
|
|
234
|
+
) -> OkxCancelBatchOrderResponse:
|
|
235
|
+
"""
|
|
236
|
+
Cancel multiple orders in batch
|
|
237
|
+
https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-cancel-batch-orders
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
payload: List of dictionaries, each containing:
|
|
241
|
+
- instId (str): Instrument ID, e.g. BTC-USDT
|
|
242
|
+
- ordId (str, optional): Order ID
|
|
243
|
+
- clOrdId (str, optional): Client Order ID
|
|
244
|
+
Note: Either ordId or clOrdId is required for each order
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
OkxCancelBatchOrderResponse: Response containing cancellation results
|
|
248
|
+
"""
|
|
249
|
+
endpoint = "/api/v5/trade/cancel-batch-orders"
|
|
250
|
+
# cost = len(payload)
|
|
251
|
+
# await self._limiter.order_limit(endpoint, cost=cost)
|
|
252
|
+
raw = await self._fetch(
|
|
253
|
+
"POST",
|
|
254
|
+
endpoint,
|
|
255
|
+
payload=payload,
|
|
256
|
+
signed=True,
|
|
257
|
+
handled_okx_codes=("0", "1", "2"),
|
|
258
|
+
)
|
|
259
|
+
return self._cancel_batch_order_response_decoder.decode(raw)
|
|
260
|
+
|
|
261
|
+
async def get_api_v5_market_history_index_candles(
|
|
262
|
+
self,
|
|
263
|
+
instId: str,
|
|
264
|
+
bar: str | None = None,
|
|
265
|
+
after: str | None = None,
|
|
266
|
+
before: str | None = None,
|
|
267
|
+
limit: int | None = None,
|
|
268
|
+
) -> OkxIndexCandlesticksResponse:
|
|
269
|
+
"""
|
|
270
|
+
GET /api/v5/market/history-index-candles
|
|
271
|
+
"""
|
|
272
|
+
endpoint = "/api/v5/market/history-index-candles"
|
|
273
|
+
payload = {
|
|
274
|
+
"instId": instId,
|
|
275
|
+
"bar": bar.replace("candle", "") if bar else None,
|
|
276
|
+
"after": after,
|
|
277
|
+
"before": before,
|
|
278
|
+
"limit": str(limit) if limit is not None else None,
|
|
279
|
+
}
|
|
280
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
281
|
+
cost = self._get_rate_limit_cost(1)
|
|
282
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
283
|
+
raw = await self._fetch_async("GET", endpoint, payload=payload, signed=False)
|
|
284
|
+
return self._index_candles_response_decoder.decode(raw)
|
|
285
|
+
|
|
286
|
+
async def get_api_v5_market_candles(
|
|
287
|
+
self,
|
|
288
|
+
instId: str,
|
|
289
|
+
bar: str | None = None,
|
|
290
|
+
after: str | None = None,
|
|
291
|
+
before: str | None = None,
|
|
292
|
+
limit: int | None = None,
|
|
293
|
+
) -> OkxCandlesticksResponse:
|
|
294
|
+
# the default bar is 1m
|
|
295
|
+
endpoint = "/api/v5/market/candles"
|
|
296
|
+
payload = {
|
|
297
|
+
k: v
|
|
298
|
+
for k, v in {
|
|
299
|
+
"instId": instId,
|
|
300
|
+
"bar": bar.replace("candle", "") if bar else None,
|
|
301
|
+
"after": after,
|
|
302
|
+
"before": before,
|
|
303
|
+
"limit": str(limit),
|
|
304
|
+
}.items()
|
|
305
|
+
if v is not None
|
|
306
|
+
}
|
|
307
|
+
cost = self._get_rate_limit_cost(1)
|
|
308
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
309
|
+
raw = await self._fetch_async("GET", endpoint, payload=payload, signed=False)
|
|
310
|
+
return self._candles_response_decoder.decode(raw)
|
|
311
|
+
|
|
312
|
+
async def get_api_v5_market_history_candles(
|
|
313
|
+
self,
|
|
314
|
+
instId: str,
|
|
315
|
+
bar: str | None = None,
|
|
316
|
+
after: str | None = None,
|
|
317
|
+
before: str | None = None,
|
|
318
|
+
limit: str | None = None,
|
|
319
|
+
) -> OkxCandlesticksResponse:
|
|
320
|
+
# the default bar is 1m
|
|
321
|
+
endpoint = "/api/v5/market/history-candles"
|
|
322
|
+
payload = {
|
|
323
|
+
k: v
|
|
324
|
+
for k, v in {
|
|
325
|
+
"instId": instId,
|
|
326
|
+
"bar": bar.replace("candle", "") if bar else None,
|
|
327
|
+
"after": after,
|
|
328
|
+
"before": before,
|
|
329
|
+
"limit": str(limit),
|
|
330
|
+
}.items()
|
|
331
|
+
if v is not None
|
|
332
|
+
}
|
|
333
|
+
cost = self._get_rate_limit_cost(1)
|
|
334
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
335
|
+
raw = await self._fetch_async("GET", endpoint, payload=payload, signed=False)
|
|
336
|
+
return self._candles_response_decoder.decode(raw)
|
|
337
|
+
|
|
338
|
+
async def get_api_v5_finance_savings_balance(
|
|
339
|
+
self,
|
|
340
|
+
ccy: str | None = None,
|
|
341
|
+
) -> OkxSavingsBalanceResponse:
|
|
342
|
+
"""
|
|
343
|
+
GET /api/v5/finance/savings/balance
|
|
344
|
+
"""
|
|
345
|
+
endpoint = "/api/v5/finance/savings/balance"
|
|
346
|
+
payload = {"ccy": ccy} if ccy else None
|
|
347
|
+
raw = await self._fetch("GET", endpoint, payload=payload, signed=True)
|
|
348
|
+
return self._savings_balance_response_decoder.decode(raw)
|
|
349
|
+
|
|
350
|
+
async def post_api_v5_finance_savings_purchase_redempt(
|
|
351
|
+
self,
|
|
352
|
+
ccy: str,
|
|
353
|
+
amt: str,
|
|
354
|
+
side: str,
|
|
355
|
+
rate: str | None = None,
|
|
356
|
+
) -> OkxSavingsPurchaseRedemptResponse:
|
|
357
|
+
"""
|
|
358
|
+
POST /api/v5/finance/savings/purchase-redempt
|
|
359
|
+
"""
|
|
360
|
+
endpoint = "/api/v5/finance/savings/purchase-redempt"
|
|
361
|
+
payload = {
|
|
362
|
+
"ccy": ccy,
|
|
363
|
+
"amt": amt,
|
|
364
|
+
"side": side,
|
|
365
|
+
}
|
|
366
|
+
if rate:
|
|
367
|
+
payload["rate"] = rate
|
|
368
|
+
|
|
369
|
+
raw = await self._fetch("POST", endpoint, payload=payload, signed=True)
|
|
370
|
+
return self._savings_purchase_redempt_response_decoder.decode(raw)
|
|
371
|
+
|
|
372
|
+
async def get_api_v5_finance_savings_lending_rate_summary(
|
|
373
|
+
self,
|
|
374
|
+
ccy: str | None = None,
|
|
375
|
+
) -> OkxSavingsLendingRateSummaryResponse:
|
|
376
|
+
"""
|
|
377
|
+
/api/v5/finance/savings/lending-rate-summary
|
|
378
|
+
"""
|
|
379
|
+
endpoint = "/api/v5/finance/savings/lending-rate-summary"
|
|
380
|
+
payload = {"ccy": ccy} if ccy else None
|
|
381
|
+
raw = await self._fetch("GET", endpoint, payload=payload, signed=False)
|
|
382
|
+
return self._savings_lending_rate_summary_response_decoder.decode(raw)
|
|
383
|
+
|
|
384
|
+
async def get_api_v5_finance_savings_lending_rate_history(
|
|
385
|
+
self,
|
|
386
|
+
ccy: str | None = None,
|
|
387
|
+
after: str | None = None,
|
|
388
|
+
before: str | None = None,
|
|
389
|
+
limit: str | None = None,
|
|
390
|
+
) -> OkxSavingsLendingRateHistoryResponse:
|
|
391
|
+
"""
|
|
392
|
+
GET /api/v5/finance/savings/lending-rate-history
|
|
393
|
+
"""
|
|
394
|
+
endpoint = "/api/v5/finance/savings/lending-rate-history"
|
|
395
|
+
payload = {
|
|
396
|
+
"ccy": ccy,
|
|
397
|
+
"after": after,
|
|
398
|
+
"before": before,
|
|
399
|
+
"limit": limit,
|
|
400
|
+
}
|
|
401
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
402
|
+
raw = await self._fetch("GET", endpoint, payload=payload, signed=False)
|
|
403
|
+
return self._savings_lending_rate_history_response_decoder.decode(raw)
|
|
404
|
+
|
|
405
|
+
async def post_api_v5_asset_transfer(
|
|
406
|
+
self,
|
|
407
|
+
ccy: str,
|
|
408
|
+
amt: str,
|
|
409
|
+
from_acct: str, # from
|
|
410
|
+
to_acct: str, # to
|
|
411
|
+
type: str = "0",
|
|
412
|
+
subAcct: Optional[str] = None,
|
|
413
|
+
loanTrans: bool = False,
|
|
414
|
+
omitPosRisk: bool = False,
|
|
415
|
+
clientId: Optional[str] = None,
|
|
416
|
+
) -> OkxAssetTransferResponse:
|
|
417
|
+
"""
|
|
418
|
+
POST /api/v5/asset/transfer
|
|
419
|
+
"""
|
|
420
|
+
endpoint = "/api/v5/asset/transfer"
|
|
421
|
+
payload = {
|
|
422
|
+
"ccy": ccy,
|
|
423
|
+
"amt": amt,
|
|
424
|
+
"from": from_acct,
|
|
425
|
+
"to": to_acct,
|
|
426
|
+
"type": type,
|
|
427
|
+
"subAcct": subAcct,
|
|
428
|
+
"loanTrans": loanTrans,
|
|
429
|
+
"omitPosRisk": omitPosRisk,
|
|
430
|
+
"clientId": clientId,
|
|
431
|
+
}
|
|
432
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
433
|
+
raw = await self._fetch("POST", endpoint, payload=payload, signed=True)
|
|
434
|
+
return self._asset_transfer_response_decoder.decode(raw)
|
|
435
|
+
|
|
436
|
+
async def post_api_v5_trade_amend_order(
|
|
437
|
+
self,
|
|
438
|
+
instId: str,
|
|
439
|
+
cxlOnFail: bool = False,
|
|
440
|
+
ordId: Optional[str] = None,
|
|
441
|
+
clOrdId: Optional[str] = None,
|
|
442
|
+
reqId: Optional[str] = None,
|
|
443
|
+
newSz: Optional[str] = None,
|
|
444
|
+
newPx: Optional[str] = None,
|
|
445
|
+
newPxUsd: Optional[str] = None,
|
|
446
|
+
newPxVol: Optional[str] = None,
|
|
447
|
+
attachAlgoOrds: Optional[List[dict]] = None,
|
|
448
|
+
):
|
|
449
|
+
"""
|
|
450
|
+
https://www.okx.com/docs-v5/en/#order-book-trading-trade-post-amend-order
|
|
451
|
+
POST /api/v5/trade/amend-order
|
|
452
|
+
"""
|
|
453
|
+
endpoint = "/api/v5/trade/amend-order"
|
|
454
|
+
payload = {
|
|
455
|
+
"instId": instId,
|
|
456
|
+
"cxlOnFail": cxlOnFail,
|
|
457
|
+
"ordId": ordId,
|
|
458
|
+
"clOrdId": clOrdId,
|
|
459
|
+
"reqId": reqId,
|
|
460
|
+
"newSz": newSz,
|
|
461
|
+
"newPx": newPx,
|
|
462
|
+
"newPxUsd": newPxUsd,
|
|
463
|
+
"newPxVol": newPxVol,
|
|
464
|
+
"attachAlgoOrds": attachAlgoOrds,
|
|
465
|
+
}
|
|
466
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
467
|
+
cost = self._get_rate_limit_cost(1)
|
|
468
|
+
await self._limiter.order_limit(endpoint, cost=cost)
|
|
469
|
+
raw = await self._fetch("POST", endpoint, payload=payload, signed=True)
|
|
470
|
+
return self._amend_order_response_decoder.decode(raw)
|
|
471
|
+
|
|
472
|
+
async def post_api_v5_finance_staking_defi_redeem(
|
|
473
|
+
self, ordId: str, protocolType: str, allowEarlyRedeem: bool = False
|
|
474
|
+
):
|
|
475
|
+
"""
|
|
476
|
+
POST /api/v5/finance/staking-defi/redeem
|
|
477
|
+
"""
|
|
478
|
+
endpoint = "/api/v5/finance/staking-defi/redeem"
|
|
479
|
+
payload = {
|
|
480
|
+
"ordId": ordId,
|
|
481
|
+
"protocolType": protocolType,
|
|
482
|
+
"allowEarlyRedeem": allowEarlyRedeem,
|
|
483
|
+
}
|
|
484
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
485
|
+
raw = await self._fetch("POST", endpoint, payload=payload, signed=True)
|
|
486
|
+
return self._finance_staking_defi_redeem_response_decoder.decode(raw)
|
|
487
|
+
|
|
488
|
+
async def post_api_v5_trade_batch_orders(
|
|
489
|
+
self, payload: list[dict]
|
|
490
|
+
) -> OkxBatchOrderResponse:
|
|
491
|
+
"""
|
|
492
|
+
POST /api/v5/trade/batch-orders
|
|
493
|
+
"""
|
|
494
|
+
endpoint = "/api/v5/trade/batch-orders"
|
|
495
|
+
cost = len(payload)
|
|
496
|
+
await self._limiter.order_limit(endpoint, cost=cost)
|
|
497
|
+
raw = await self._fetch(
|
|
498
|
+
"POST",
|
|
499
|
+
endpoint,
|
|
500
|
+
payload=payload,
|
|
501
|
+
signed=True,
|
|
502
|
+
handled_okx_codes=("0", "1", "2"),
|
|
503
|
+
)
|
|
504
|
+
return self._batch_order_response_decoder.decode(raw)
|
|
505
|
+
|
|
506
|
+
async def post_api_v5_finance_staking_defi_purchase(
|
|
507
|
+
self,
|
|
508
|
+
productId: str,
|
|
509
|
+
investData: list[dict],
|
|
510
|
+
term: Optional[str] = None,
|
|
511
|
+
tag: Optional[str] = None,
|
|
512
|
+
):
|
|
513
|
+
"""
|
|
514
|
+
productId String Yes Product ID
|
|
515
|
+
investData Array of objects Yes Investment data
|
|
516
|
+
> ccy String Yes Investment currency, e.g. BTC
|
|
517
|
+
> amt String Yes Investment amount
|
|
518
|
+
term String Conditional Investment term
|
|
519
|
+
Investment term must be specified for fixed-term product
|
|
520
|
+
tag String No Order tag
|
|
521
|
+
A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 16 characters.
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
endpoint = "/api/v5/finance/staking-defi/purchase"
|
|
525
|
+
payload = {
|
|
526
|
+
"productId": productId,
|
|
527
|
+
"investData": investData,
|
|
528
|
+
"term": term,
|
|
529
|
+
"tag": tag,
|
|
530
|
+
}
|
|
531
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
532
|
+
raw = await self._fetch("POST", endpoint, payload=payload, signed=True)
|
|
533
|
+
return self._finance_staking_defi_purchase_response_decoder.decode(raw)
|
|
534
|
+
|
|
535
|
+
async def get_api_v5_finance_staking_defi_offers(
|
|
536
|
+
self,
|
|
537
|
+
productId: Optional[str] = None,
|
|
538
|
+
protocolType: Optional[str] = None,
|
|
539
|
+
ccy: Optional[str] = None,
|
|
540
|
+
):
|
|
541
|
+
endpoint = "/api/v5/finance/staking-defi/offers"
|
|
542
|
+
payload = {
|
|
543
|
+
"productId": productId,
|
|
544
|
+
"protocolType": protocolType,
|
|
545
|
+
"ccy": ccy,
|
|
546
|
+
}
|
|
547
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
548
|
+
raw = await self._fetch("GET", endpoint, payload=payload, signed=True)
|
|
549
|
+
return self._finance_staking_defi_offers_response_decoder.decode(raw)
|
|
550
|
+
|
|
551
|
+
async def get_api_v5_account_config(self):
|
|
552
|
+
"""
|
|
553
|
+
GET /api/v5/account/config
|
|
554
|
+
"""
|
|
555
|
+
endpoint = "/api/v5/account/config"
|
|
556
|
+
cost = self._get_rate_limit_cost(1)
|
|
557
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
558
|
+
raw = await self._fetch_async("GET", endpoint, signed=True)
|
|
559
|
+
return self._account_config_response_decoder.decode(raw)
|
|
560
|
+
|
|
561
|
+
def _generate_signature(self, message: str) -> str:
|
|
562
|
+
assert self._secret is not None
|
|
563
|
+
hex_digest = hmac_signature(self._secret, message)
|
|
564
|
+
digest = bytes.fromhex(hex_digest)
|
|
565
|
+
return base64.b64encode(digest).decode()
|
|
566
|
+
|
|
567
|
+
def _get_signature(
|
|
568
|
+
self, ts: str, method: str, request_path: str, payload: Optional[bytes]
|
|
569
|
+
) -> str:
|
|
570
|
+
body = payload.decode() if payload else ""
|
|
571
|
+
sign_str = f"{ts}{method}{request_path}{body}"
|
|
572
|
+
signature = self._generate_signature(sign_str)
|
|
573
|
+
return signature
|
|
574
|
+
|
|
575
|
+
def _get_timestamp(self) -> str:
|
|
576
|
+
return (
|
|
577
|
+
self._clock.utc_now()
|
|
578
|
+
.isoformat(timespec="milliseconds")
|
|
579
|
+
.replace("+00:00", "Z")
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
def _get_headers(
|
|
583
|
+
self, ts: str, method: str, request_path: str, payload: Optional[bytes]
|
|
584
|
+
) -> Dict[str, Any]:
|
|
585
|
+
assert self._api_key is not None and self._passphrase is not None
|
|
586
|
+
headers = self._headers
|
|
587
|
+
signature = self._get_signature(ts, method, request_path, payload)
|
|
588
|
+
headers.update(
|
|
589
|
+
{
|
|
590
|
+
"OK-ACCESS-KEY": self._api_key,
|
|
591
|
+
"OK-ACCESS-SIGN": signature,
|
|
592
|
+
"OK-ACCESS-TIMESTAMP": ts,
|
|
593
|
+
"OK-ACCESS-PASSPHRASE": self._passphrase,
|
|
594
|
+
}
|
|
595
|
+
)
|
|
596
|
+
return headers
|
|
597
|
+
|
|
598
|
+
async def _fetch(
|
|
599
|
+
self,
|
|
600
|
+
method: str,
|
|
601
|
+
endpoint: str,
|
|
602
|
+
payload: Union[Dict[str, Any], list, None] = None,
|
|
603
|
+
signed: bool = False,
|
|
604
|
+
handled_okx_codes: tuple[str, ...] = ("0",),
|
|
605
|
+
) -> bytes:
|
|
606
|
+
"""
|
|
607
|
+
Fetch data from the OKX API asynchronously.
|
|
608
|
+
"""
|
|
609
|
+
assert self._retry_manager is not None
|
|
610
|
+
result = await self._retry_manager.run(
|
|
611
|
+
name=f"{method} {endpoint}",
|
|
612
|
+
func=self._fetch_async,
|
|
613
|
+
method=method,
|
|
614
|
+
endpoint=endpoint,
|
|
615
|
+
payload=payload,
|
|
616
|
+
signed=signed,
|
|
617
|
+
handled_okx_codes=handled_okx_codes,
|
|
618
|
+
)
|
|
619
|
+
assert result is not None
|
|
620
|
+
return result
|
|
621
|
+
|
|
622
|
+
async def _fetch_async(
|
|
623
|
+
self,
|
|
624
|
+
method: str,
|
|
625
|
+
endpoint: str,
|
|
626
|
+
payload: Union[Dict[str, Any], list, None] = None,
|
|
627
|
+
signed: bool = False,
|
|
628
|
+
handled_okx_codes: tuple[str, ...] = ("0",),
|
|
629
|
+
) -> bytes:
|
|
630
|
+
await self._init_session(self._base_url)
|
|
631
|
+
|
|
632
|
+
request_path = endpoint
|
|
633
|
+
headers = self._headers
|
|
634
|
+
timestamp = self._get_timestamp()
|
|
635
|
+
|
|
636
|
+
payload = payload or {}
|
|
637
|
+
|
|
638
|
+
payload_json = (
|
|
639
|
+
urlencode(payload) if method == "GET" else msgspec.json.encode(payload)
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
if method == "GET":
|
|
643
|
+
if payload_json:
|
|
644
|
+
request_path += f"?{payload_json}"
|
|
645
|
+
payload_json = None
|
|
646
|
+
|
|
647
|
+
if signed and self._api_key:
|
|
648
|
+
headers = self._get_headers(
|
|
649
|
+
timestamp, method, request_path, cast(Optional[bytes], payload_json)
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
assert self._session is not None
|
|
653
|
+
try:
|
|
654
|
+
self._log.debug(
|
|
655
|
+
f"{method} {request_path} Headers: {headers} payload: {payload_json}"
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
async with self._session.request(
|
|
659
|
+
method=method,
|
|
660
|
+
url=request_path,
|
|
661
|
+
headers=headers,
|
|
662
|
+
data=payload_json,
|
|
663
|
+
) as response:
|
|
664
|
+
raw = await response.read()
|
|
665
|
+
|
|
666
|
+
if response.status >= 400:
|
|
667
|
+
error_data = msgspec.json.decode(raw)
|
|
668
|
+
code = error_data.get("code", str(response.status))
|
|
669
|
+
msg = error_data.get("msg", "Unknown error")
|
|
670
|
+
raise OkxHttpError(
|
|
671
|
+
status_code=response.status,
|
|
672
|
+
message=f"code={code}, msg={msg}",
|
|
673
|
+
headers=response.headers,
|
|
674
|
+
)
|
|
675
|
+
okx_response = self._general_response_decoder.decode(raw)
|
|
676
|
+
if okx_response.code in handled_okx_codes:
|
|
677
|
+
return raw
|
|
678
|
+
|
|
679
|
+
okx_error_response = self._error_response_decoder.decode(raw)
|
|
680
|
+
for data in okx_error_response.data:
|
|
681
|
+
if data.sCode != "0":
|
|
682
|
+
raise OkxRequestError(
|
|
683
|
+
error_code=int(data.sCode),
|
|
684
|
+
status_code=response.status,
|
|
685
|
+
message=data.sMsg,
|
|
686
|
+
)
|
|
687
|
+
raise OkxRequestError(
|
|
688
|
+
error_code=int(okx_error_response.code),
|
|
689
|
+
status_code=response.status,
|
|
690
|
+
message=okx_error_response.msg,
|
|
691
|
+
)
|
|
692
|
+
except asyncio.TimeoutError as e:
|
|
693
|
+
self._log.error(f"Timeout {method} {request_path} {e}")
|
|
694
|
+
raise
|
|
695
|
+
except aiohttp.ClientError as e:
|
|
696
|
+
self._log.error(f"Request Error {method} {request_path} {e}")
|
|
697
|
+
raise
|
|
698
|
+
except Exception as e:
|
|
699
|
+
self._log.error(f"Error {method} {request_path} {e}")
|
|
700
|
+
raise
|
|
701
|
+
|
|
702
|
+
async def get_api_v5_market_tickers(
|
|
703
|
+
self,
|
|
704
|
+
inst_type: str,
|
|
705
|
+
uly: str | None = None,
|
|
706
|
+
inst_family: str | None = None,
|
|
707
|
+
) -> OkxTickersResponse:
|
|
708
|
+
"""
|
|
709
|
+
GET /api/v5/market/tickers
|
|
710
|
+
|
|
711
|
+
Retrieve the latest price snapshot, best bid/ask price, and trading volume
|
|
712
|
+
in the last 24 hours for multiple instruments.
|
|
713
|
+
|
|
714
|
+
Rate Limit: 20 requests per 2 seconds
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
inst_type: Instrument type (SPOT, SWAP, FUTURES, OPTION)
|
|
718
|
+
uly: Underlying (optional), e.g. BTC-USD. Applicable to FUTURES/SWAP/OPTION
|
|
719
|
+
inst_family: Instrument family (optional). Applicable to FUTURES/SWAP/OPTION
|
|
720
|
+
|
|
721
|
+
Returns:
|
|
722
|
+
OkxTickersResponse: Response containing ticker data for multiple instruments
|
|
723
|
+
"""
|
|
724
|
+
endpoint = "/api/v5/market/tickers"
|
|
725
|
+
payload = {
|
|
726
|
+
"instType": inst_type,
|
|
727
|
+
"uly": uly,
|
|
728
|
+
"instFamily": inst_family,
|
|
729
|
+
}
|
|
730
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
731
|
+
|
|
732
|
+
cost = self._get_rate_limit_cost(1)
|
|
733
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
734
|
+
raw = await self._fetch_async("GET", endpoint, payload=payload, signed=False)
|
|
735
|
+
return self._tickers_response_decoder.decode(raw)
|
|
736
|
+
|
|
737
|
+
async def get_api_v5_market_ticker(self, inst_id: str) -> OkxTickersResponse:
|
|
738
|
+
"""
|
|
739
|
+
GET /api/v5/market/ticker
|
|
740
|
+
|
|
741
|
+
Retrieve the latest price snapshot, best bid/ask price, and trading volume
|
|
742
|
+
in the last 24 hours for a single instrument.
|
|
743
|
+
|
|
744
|
+
Rate Limit: 20 requests per 2 seconds
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
inst_id: Instrument ID (e.g., BTC-USD-SWAP)
|
|
748
|
+
|
|
749
|
+
Returns:
|
|
750
|
+
OkxTickersResponse: Response containing ticker data for the single instrument
|
|
751
|
+
"""
|
|
752
|
+
endpoint = "/api/v5/market/ticker"
|
|
753
|
+
payload = {"instId": inst_id}
|
|
754
|
+
|
|
755
|
+
cost = self._get_rate_limit_cost(1)
|
|
756
|
+
await self._limiter.query_limit(endpoint, cost=cost)
|
|
757
|
+
raw = await self._fetch_async("GET", endpoint, payload=payload, signed=False)
|
|
758
|
+
return self._tickers_response_decoder.decode(raw)
|
|
759
|
+
|
|
760
|
+
async def get_api_v5_trade_order(
|
|
761
|
+
self,
|
|
762
|
+
inst_id: str,
|
|
763
|
+
ord_id: str | None = None,
|
|
764
|
+
cl_ord_id: str | None = None,
|
|
765
|
+
) -> OkxOrderResponse:
|
|
766
|
+
"""
|
|
767
|
+
GET /api/v5/trade/order
|
|
768
|
+
|
|
769
|
+
Retrieve order details. Either ordId or clOrdId is required.
|
|
770
|
+
If both are passed, ordId will be used.
|
|
771
|
+
|
|
772
|
+
Rate Limit: 60 requests per 2 seconds
|
|
773
|
+
|
|
774
|
+
Args:
|
|
775
|
+
inst_id: Instrument ID (e.g., BTC-USDT). Only applicable to live instruments
|
|
776
|
+
ord_id: Order ID. Either ordId or clOrdId is required
|
|
777
|
+
cl_ord_id: Client Order ID as assigned by the client. Either ordId or clOrdId is required
|
|
778
|
+
|
|
779
|
+
Returns:
|
|
780
|
+
OkxOrderResponse: Response containing order details
|
|
781
|
+
|
|
782
|
+
Raises:
|
|
783
|
+
ValueError: If neither ord_id nor cl_ord_id is provided
|
|
784
|
+
"""
|
|
785
|
+
if not ord_id and not cl_ord_id:
|
|
786
|
+
raise ValueError("Either ord_id or cl_ord_id must be provided")
|
|
787
|
+
|
|
788
|
+
endpoint = "/api/v5/trade/order"
|
|
789
|
+
payload = {"instId": inst_id}
|
|
790
|
+
|
|
791
|
+
if ord_id:
|
|
792
|
+
payload["ordId"] = ord_id
|
|
793
|
+
if cl_ord_id:
|
|
794
|
+
payload["clOrdId"] = cl_ord_id
|
|
795
|
+
|
|
796
|
+
cost = self._get_rate_limit_cost(1)
|
|
797
|
+
await self._limiter.order_limit(endpoint, cost=cost)
|
|
798
|
+
raw = await self._fetch("GET", endpoint, payload=payload, signed=True)
|
|
799
|
+
return self._order_response_decoder.decode(raw)
|