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,570 @@
|
|
|
1
|
+
import hmac
|
|
2
|
+
import hashlib
|
|
3
|
+
import msgspec
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
from urllib.parse import urljoin, urlencode
|
|
6
|
+
import aiohttp
|
|
7
|
+
import asyncio
|
|
8
|
+
from yarl import URL
|
|
9
|
+
from decimal import Decimal
|
|
10
|
+
from walrasquant.base import ApiClient, RetryManager
|
|
11
|
+
from walrasquant.exchange.bybit.constants import (
|
|
12
|
+
BybitBaseUrl,
|
|
13
|
+
BybitRateLimiter,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from walrasquant.exchange.bybit.error import BybitError
|
|
17
|
+
from walrasquant.core.nautilius_core import hmac_signature, LiveClock
|
|
18
|
+
from walrasquant.exchange.bybit.schema import (
|
|
19
|
+
BybitResponse,
|
|
20
|
+
BybitOrderResponse,
|
|
21
|
+
BybitPositionResponse,
|
|
22
|
+
BybitOrderHistoryResponse,
|
|
23
|
+
BybitOpenOrdersResponse,
|
|
24
|
+
BybitWalletBalanceResponse,
|
|
25
|
+
BybitKlineResponse,
|
|
26
|
+
BybitIndexKlineResponse,
|
|
27
|
+
BybitBatchOrderResponse,
|
|
28
|
+
BybitBatchCancelOrderResponse,
|
|
29
|
+
BybitTickersResponse,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BybitApiClient(ApiClient):
|
|
34
|
+
_limiter: BybitRateLimiter
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
clock: LiveClock,
|
|
39
|
+
api_key: Optional[str] = None,
|
|
40
|
+
secret: Optional[str] = None,
|
|
41
|
+
timeout: int = 10,
|
|
42
|
+
testnet: bool = False,
|
|
43
|
+
enable_rate_limit: bool = True,
|
|
44
|
+
max_retries: int = 0,
|
|
45
|
+
delay_initial_ms: int = 100,
|
|
46
|
+
delay_max_ms: int = 800,
|
|
47
|
+
backoff_factor: int = 2,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
### Testnet:
|
|
51
|
+
`https://api-testnet.bybit.com`
|
|
52
|
+
|
|
53
|
+
### Mainnet:
|
|
54
|
+
(both endpoints are available):
|
|
55
|
+
`https://api.bybit.com`
|
|
56
|
+
`https://api.bytick.com`
|
|
57
|
+
|
|
58
|
+
### Important:
|
|
59
|
+
Netherland users: use `https://api.bybit.nl` for mainnet
|
|
60
|
+
Hong Kong users: use `https://api.byhkbit.com` for mainnet
|
|
61
|
+
Turkey users: use `https://api.bybit-tr.com` for mainnet
|
|
62
|
+
Kazakhstan users: use `https://api.bybit.kz` for mainnet
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
super().__init__(
|
|
66
|
+
clock=clock,
|
|
67
|
+
api_key=api_key,
|
|
68
|
+
secret=secret,
|
|
69
|
+
timeout=timeout,
|
|
70
|
+
rate_limiter=BybitRateLimiter(enable_rate_limit),
|
|
71
|
+
retry_manager=RetryManager(
|
|
72
|
+
max_retries=max_retries,
|
|
73
|
+
delay_initial_ms=delay_initial_ms,
|
|
74
|
+
delay_max_ms=delay_max_ms,
|
|
75
|
+
backoff_factor=backoff_factor,
|
|
76
|
+
exc_types=(BybitError,),
|
|
77
|
+
retry_check=lambda e: (
|
|
78
|
+
isinstance(e, BybitError)
|
|
79
|
+
and e.code
|
|
80
|
+
in [
|
|
81
|
+
429,
|
|
82
|
+
10000,
|
|
83
|
+
10016,
|
|
84
|
+
3400214,
|
|
85
|
+
170007,
|
|
86
|
+
177002,
|
|
87
|
+
500001,
|
|
88
|
+
]
|
|
89
|
+
), # please refer to https://bybit-exchange.github.io/docs/v5/error
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
self._recv_window = 5000
|
|
93
|
+
|
|
94
|
+
if testnet:
|
|
95
|
+
self._base_url = BybitBaseUrl.TESTNET.base_url
|
|
96
|
+
else:
|
|
97
|
+
self._base_url = BybitBaseUrl.MAINNET_1.base_url
|
|
98
|
+
|
|
99
|
+
self._headers = {
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
"User-Agent": "TradingBot/1.0",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if api_key:
|
|
105
|
+
self._headers["X-BAPI-API-KEY"] = api_key
|
|
106
|
+
|
|
107
|
+
self._msg_decoder = msgspec.json.Decoder()
|
|
108
|
+
self._msg_encoder = msgspec.json.Encoder()
|
|
109
|
+
self._response_decoder = msgspec.json.Decoder(BybitResponse)
|
|
110
|
+
self._order_response_decoder = msgspec.json.Decoder(BybitOrderResponse)
|
|
111
|
+
self._position_response_decoder = msgspec.json.Decoder(BybitPositionResponse)
|
|
112
|
+
self._order_history_response_decoder = msgspec.json.Decoder(
|
|
113
|
+
BybitOrderHistoryResponse
|
|
114
|
+
)
|
|
115
|
+
self._open_orders_response_decoder = msgspec.json.Decoder(
|
|
116
|
+
BybitOpenOrdersResponse
|
|
117
|
+
)
|
|
118
|
+
self._wallet_balance_response_decoder = msgspec.json.Decoder(
|
|
119
|
+
BybitWalletBalanceResponse
|
|
120
|
+
)
|
|
121
|
+
self._kline_response_decoder = msgspec.json.Decoder(BybitKlineResponse)
|
|
122
|
+
self._index_kline_response_decoder = msgspec.json.Decoder(
|
|
123
|
+
BybitIndexKlineResponse
|
|
124
|
+
)
|
|
125
|
+
self._batch_order_response_decoder = msgspec.json.Decoder(
|
|
126
|
+
BybitBatchOrderResponse
|
|
127
|
+
)
|
|
128
|
+
self._batch_cancel_order_response_decoder = msgspec.json.Decoder(
|
|
129
|
+
BybitBatchCancelOrderResponse
|
|
130
|
+
)
|
|
131
|
+
self._tickers_response_decoder = msgspec.json.Decoder(BybitTickersResponse)
|
|
132
|
+
|
|
133
|
+
def _generate_signature(self, payload: str) -> List[str]:
|
|
134
|
+
api_key = self._api_key or ""
|
|
135
|
+
secret = self._secret or ""
|
|
136
|
+
timestamp = str(self._clock.timestamp_ms())
|
|
137
|
+
|
|
138
|
+
param = str(timestamp) + api_key + str(self._recv_window) + payload
|
|
139
|
+
hash = hmac.new(bytes(secret, "utf-8"), param.encode("utf-8"), hashlib.sha256)
|
|
140
|
+
signature = hash.hexdigest()
|
|
141
|
+
return [signature, timestamp]
|
|
142
|
+
|
|
143
|
+
def _generate_signature_v2(self, payload: str) -> List[str]:
|
|
144
|
+
api_key = self._api_key or ""
|
|
145
|
+
secret = self._secret or ""
|
|
146
|
+
timestamp = str(self._clock.timestamp_ms())
|
|
147
|
+
param = f"{timestamp}{api_key}{self._recv_window}{payload}"
|
|
148
|
+
signature = hmac_signature(secret, param) # return hex digest string
|
|
149
|
+
return [signature, timestamp]
|
|
150
|
+
|
|
151
|
+
async def _fetch(
|
|
152
|
+
self,
|
|
153
|
+
method: str,
|
|
154
|
+
base_url: Optional[str],
|
|
155
|
+
endpoint: str,
|
|
156
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
157
|
+
signed: bool = False,
|
|
158
|
+
):
|
|
159
|
+
assert self._retry_manager is not None
|
|
160
|
+
await self._limiter.ip_limit()
|
|
161
|
+
result = await self._retry_manager.run(
|
|
162
|
+
name=f"{method} {endpoint}",
|
|
163
|
+
func=self._fetch_async,
|
|
164
|
+
method=method,
|
|
165
|
+
base_url=base_url,
|
|
166
|
+
endpoint=endpoint,
|
|
167
|
+
payload=payload,
|
|
168
|
+
signed=signed,
|
|
169
|
+
)
|
|
170
|
+
assert result is not None
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
async def _fetch_async(
|
|
174
|
+
self,
|
|
175
|
+
method: str,
|
|
176
|
+
base_url: Optional[str],
|
|
177
|
+
endpoint: str,
|
|
178
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
179
|
+
signed: bool = False,
|
|
180
|
+
):
|
|
181
|
+
assert base_url is not None
|
|
182
|
+
await self._init_session()
|
|
183
|
+
|
|
184
|
+
url = urljoin(base_url, endpoint)
|
|
185
|
+
payload = payload or {}
|
|
186
|
+
|
|
187
|
+
payload_str = (
|
|
188
|
+
urlencode(payload)
|
|
189
|
+
if method == "GET"
|
|
190
|
+
else self._msg_encoder.encode(payload).decode("utf-8")
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
headers = self._headers
|
|
194
|
+
if signed:
|
|
195
|
+
signature, timestamp = self._generate_signature_v2(payload_str)
|
|
196
|
+
headers = {
|
|
197
|
+
**headers,
|
|
198
|
+
"X-BAPI-TIMESTAMP": timestamp,
|
|
199
|
+
"X-BAPI-SIGN": signature,
|
|
200
|
+
"X-BAPI-RECV-WINDOW": str(self._recv_window),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if method == "GET":
|
|
204
|
+
url += f"?{payload_str}"
|
|
205
|
+
payload_str = None
|
|
206
|
+
|
|
207
|
+
assert self._session is not None
|
|
208
|
+
try:
|
|
209
|
+
self._log.debug(f"Request: {url} {payload_str}")
|
|
210
|
+
async with self._session.request(
|
|
211
|
+
method=method,
|
|
212
|
+
url=url,
|
|
213
|
+
headers=headers,
|
|
214
|
+
data=payload_str,
|
|
215
|
+
) as response:
|
|
216
|
+
raw = await response.read()
|
|
217
|
+
if response.status >= 400:
|
|
218
|
+
error_data = self._msg_decoder.decode(raw) if raw else {}
|
|
219
|
+
ret_msg = (
|
|
220
|
+
error_data.get("retMsg", "Unknown error")
|
|
221
|
+
if isinstance(error_data, dict)
|
|
222
|
+
else str(error_data)
|
|
223
|
+
)
|
|
224
|
+
raise BybitError(
|
|
225
|
+
code=response.status,
|
|
226
|
+
message=ret_msg,
|
|
227
|
+
)
|
|
228
|
+
bybit_response: BybitResponse = self._response_decoder.decode(raw)
|
|
229
|
+
if bybit_response.retCode == 0:
|
|
230
|
+
return raw
|
|
231
|
+
else:
|
|
232
|
+
raise BybitError(
|
|
233
|
+
code=bybit_response.retCode,
|
|
234
|
+
message=bybit_response.retMsg,
|
|
235
|
+
)
|
|
236
|
+
except asyncio.TimeoutError as e:
|
|
237
|
+
self._log.error(f"Timeout {method} Url: {url} - {e}")
|
|
238
|
+
raise
|
|
239
|
+
except aiohttp.ClientError as e:
|
|
240
|
+
self._log.error(f"Request Error {method} Url: {url} - {e}")
|
|
241
|
+
raise
|
|
242
|
+
except Exception as e:
|
|
243
|
+
self._log.error(f"Error {method} Url: {url} - {e}")
|
|
244
|
+
raise
|
|
245
|
+
|
|
246
|
+
async def post_v5_order_create(
|
|
247
|
+
self,
|
|
248
|
+
category: str,
|
|
249
|
+
symbol: str,
|
|
250
|
+
side: str,
|
|
251
|
+
order_type: str,
|
|
252
|
+
qty: Decimal,
|
|
253
|
+
**kwargs,
|
|
254
|
+
) -> BybitOrderResponse:
|
|
255
|
+
"""
|
|
256
|
+
https://bybit-exchange.github.io/docs/v5/order/create-order
|
|
257
|
+
"""
|
|
258
|
+
endpoint = "/v5/order/create"
|
|
259
|
+
payload = {
|
|
260
|
+
"category": category,
|
|
261
|
+
"symbol": symbol,
|
|
262
|
+
"side": side,
|
|
263
|
+
"orderType": order_type,
|
|
264
|
+
"qty": str(qty),
|
|
265
|
+
**kwargs,
|
|
266
|
+
}
|
|
267
|
+
await self._limiter.order_limit(category, endpoint, cost=1)
|
|
268
|
+
raw = await self._fetch("POST", self._base_url, endpoint, payload, signed=True)
|
|
269
|
+
return self._order_response_decoder.decode(raw)
|
|
270
|
+
|
|
271
|
+
async def post_v5_order_cancel(
|
|
272
|
+
self, category: str, symbol: str, **kwargs
|
|
273
|
+
) -> BybitOrderResponse:
|
|
274
|
+
"""
|
|
275
|
+
https://bybit-exchange.github.io/docs/v5/order/cancel-order
|
|
276
|
+
"""
|
|
277
|
+
endpoint = "/v5/order/cancel"
|
|
278
|
+
payload = {
|
|
279
|
+
"category": category,
|
|
280
|
+
"symbol": symbol,
|
|
281
|
+
**kwargs,
|
|
282
|
+
}
|
|
283
|
+
# await self._limiter.order_limit(category, endpoint, cost=1)
|
|
284
|
+
raw = await self._fetch("POST", self._base_url, endpoint, payload, signed=True)
|
|
285
|
+
return self._order_response_decoder.decode(raw)
|
|
286
|
+
|
|
287
|
+
async def get_v5_position_list(
|
|
288
|
+
self, category: str, **kwargs
|
|
289
|
+
) -> BybitPositionResponse:
|
|
290
|
+
endpoint = "/v5/position/list"
|
|
291
|
+
payload = {
|
|
292
|
+
"category": category,
|
|
293
|
+
**kwargs,
|
|
294
|
+
}
|
|
295
|
+
await self._limiter.query_limit(endpoint)
|
|
296
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
297
|
+
raw = await self._fetch_async(
|
|
298
|
+
"GET", self._base_url, endpoint, payload, signed=True
|
|
299
|
+
)
|
|
300
|
+
return self._position_response_decoder.decode(raw)
|
|
301
|
+
|
|
302
|
+
async def get_v5_order_realtime(self, category: str, **kwargs):
|
|
303
|
+
"""
|
|
304
|
+
https://bybit-exchange.github.io/docs/v5/order/open-order
|
|
305
|
+
"""
|
|
306
|
+
endpoint = "/v5/order/realtime"
|
|
307
|
+
payload = {
|
|
308
|
+
"category": category,
|
|
309
|
+
**kwargs,
|
|
310
|
+
}
|
|
311
|
+
raw = await self._fetch("GET", self._base_url, endpoint, payload, signed=True)
|
|
312
|
+
return self._open_orders_response_decoder.decode(raw)
|
|
313
|
+
|
|
314
|
+
async def get_v5_order_history(self, category: str, **kwargs):
|
|
315
|
+
"""
|
|
316
|
+
https://bybit-exchange.github.io/docs/v5/order/order-list
|
|
317
|
+
"""
|
|
318
|
+
endpoint = "/v5/order/history"
|
|
319
|
+
payload = {
|
|
320
|
+
"category": category,
|
|
321
|
+
**kwargs,
|
|
322
|
+
}
|
|
323
|
+
raw = await self._fetch("GET", self._base_url, endpoint, payload, signed=True)
|
|
324
|
+
return self._order_history_response_decoder.decode(raw)
|
|
325
|
+
|
|
326
|
+
async def get_v5_account_wallet_balance(
|
|
327
|
+
self, account_type: str, **kwargs
|
|
328
|
+
) -> BybitWalletBalanceResponse:
|
|
329
|
+
endpoint = "/v5/account/wallet-balance"
|
|
330
|
+
payload = {
|
|
331
|
+
"accountType": account_type,
|
|
332
|
+
**kwargs,
|
|
333
|
+
}
|
|
334
|
+
await self._limiter.query_limit(endpoint)
|
|
335
|
+
raw = await self._fetch_async(
|
|
336
|
+
"GET", self._base_url, endpoint, payload, signed=True
|
|
337
|
+
)
|
|
338
|
+
return self._wallet_balance_response_decoder.decode(raw)
|
|
339
|
+
|
|
340
|
+
async def post_v5_order_amend(
|
|
341
|
+
self,
|
|
342
|
+
category: str,
|
|
343
|
+
symbol: str,
|
|
344
|
+
orderId: str | None = None,
|
|
345
|
+
orderLinkId: str | None = None,
|
|
346
|
+
orderIv: str | None = None,
|
|
347
|
+
triggerPrice: str | None = None,
|
|
348
|
+
qty: str | None = None,
|
|
349
|
+
price: str | None = None,
|
|
350
|
+
tpslMode: str | None = None,
|
|
351
|
+
takeProfit: str | None = None,
|
|
352
|
+
stopLoss: str | None = None,
|
|
353
|
+
tpTriggerBy: str | None = None,
|
|
354
|
+
slTriggerBy: str | None = None,
|
|
355
|
+
triggerBy: str | None = None,
|
|
356
|
+
tpLimitPrice: str | None = None,
|
|
357
|
+
slLimitPrice: str | None = None,
|
|
358
|
+
):
|
|
359
|
+
"""
|
|
360
|
+
https://bybit-exchange.github.io/docs/v5/order/amend-order
|
|
361
|
+
"""
|
|
362
|
+
endpoint = "/v5/order/amend"
|
|
363
|
+
payload = {
|
|
364
|
+
"category": category,
|
|
365
|
+
"symbol": symbol,
|
|
366
|
+
"orderId": orderId,
|
|
367
|
+
"orderLinkId": orderLinkId,
|
|
368
|
+
"orderIv": orderIv,
|
|
369
|
+
"triggerPrice": triggerPrice,
|
|
370
|
+
"qty": qty,
|
|
371
|
+
"price": price,
|
|
372
|
+
"tpslMode": tpslMode,
|
|
373
|
+
"takeProfit": takeProfit,
|
|
374
|
+
"stopLoss": stopLoss,
|
|
375
|
+
"tpTriggerBy": tpTriggerBy,
|
|
376
|
+
"slTriggerBy": slTriggerBy,
|
|
377
|
+
"triggerBy": triggerBy,
|
|
378
|
+
"tpLimitPrice": tpLimitPrice,
|
|
379
|
+
"slLimitPrice": slLimitPrice,
|
|
380
|
+
}
|
|
381
|
+
await self._limiter.order_limit(category, endpoint, cost=1)
|
|
382
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
383
|
+
raw = await self._fetch("POST", self._base_url, endpoint, payload, signed=True)
|
|
384
|
+
return self._order_response_decoder.decode(raw)
|
|
385
|
+
|
|
386
|
+
async def post_v5_order_cancel_all(
|
|
387
|
+
self,
|
|
388
|
+
category: str,
|
|
389
|
+
symbol: str | None = None,
|
|
390
|
+
baseCoin: str | None = None,
|
|
391
|
+
settleCoin: str | None = None,
|
|
392
|
+
orderFilter: str | None = None,
|
|
393
|
+
stopOrderType: str | None = None,
|
|
394
|
+
):
|
|
395
|
+
endpoint = "/v5/order/cancel-all"
|
|
396
|
+
payload = {
|
|
397
|
+
"category": category,
|
|
398
|
+
"symbol": symbol,
|
|
399
|
+
"baseCoin": baseCoin,
|
|
400
|
+
"settleCoin": settleCoin,
|
|
401
|
+
"orderFilter": orderFilter,
|
|
402
|
+
"stopOrderType": stopOrderType,
|
|
403
|
+
}
|
|
404
|
+
# await self._limiter.order_limit(category, endpoint, cost=1)
|
|
405
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
406
|
+
raw = await self._fetch("POST", self._base_url, endpoint, payload, signed=True)
|
|
407
|
+
return self._msg_decoder.decode(raw)
|
|
408
|
+
|
|
409
|
+
async def get_v5_market_kline(
|
|
410
|
+
self,
|
|
411
|
+
category: str,
|
|
412
|
+
symbol: str,
|
|
413
|
+
interval: str,
|
|
414
|
+
start: int | None = None,
|
|
415
|
+
end: int | None = None,
|
|
416
|
+
limit: int | None = None,
|
|
417
|
+
):
|
|
418
|
+
endpoint = "/v5/market/kline"
|
|
419
|
+
payload = {
|
|
420
|
+
"category": category,
|
|
421
|
+
"symbol": symbol,
|
|
422
|
+
"interval": interval,
|
|
423
|
+
"start": start,
|
|
424
|
+
"end": end,
|
|
425
|
+
"limit": limit,
|
|
426
|
+
}
|
|
427
|
+
await self._limiter.public_limit(endpoint)
|
|
428
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
429
|
+
raw = await self._fetch_async(
|
|
430
|
+
"GET", self._base_url, endpoint, payload, signed=False
|
|
431
|
+
)
|
|
432
|
+
return self._kline_response_decoder.decode(raw)
|
|
433
|
+
|
|
434
|
+
async def get_v5_market_index_price_kline(
|
|
435
|
+
self,
|
|
436
|
+
category: str,
|
|
437
|
+
symbol: str,
|
|
438
|
+
interval: str,
|
|
439
|
+
start: int | None = None,
|
|
440
|
+
end: int | None = None,
|
|
441
|
+
limit: int | None = None,
|
|
442
|
+
):
|
|
443
|
+
endpoint = "/v5/market/index-price-kline"
|
|
444
|
+
payload = {
|
|
445
|
+
"category": category,
|
|
446
|
+
"symbol": symbol,
|
|
447
|
+
"interval": interval,
|
|
448
|
+
"start": start,
|
|
449
|
+
"end": end,
|
|
450
|
+
"limit": limit,
|
|
451
|
+
}
|
|
452
|
+
await self._limiter.public_limit(endpoint)
|
|
453
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
454
|
+
raw = await self._fetch_async(
|
|
455
|
+
"GET", self._base_url, endpoint, payload, signed=False
|
|
456
|
+
)
|
|
457
|
+
return self._index_kline_response_decoder.decode(raw)
|
|
458
|
+
|
|
459
|
+
async def post_v5_order_create_batch(
|
|
460
|
+
self,
|
|
461
|
+
category: str,
|
|
462
|
+
request: List[Dict[str, Any]],
|
|
463
|
+
) -> BybitBatchOrderResponse:
|
|
464
|
+
"""
|
|
465
|
+
Place multiple orders in a single request
|
|
466
|
+
https://bybit-exchange.github.io/docs/v5/order/batch-place
|
|
467
|
+
|
|
468
|
+
Maximum orders per request:
|
|
469
|
+
- Option: 20 orders
|
|
470
|
+
- Inverse: 20 orders
|
|
471
|
+
- Linear: 20 orders
|
|
472
|
+
- Spot: 10 orders
|
|
473
|
+
"""
|
|
474
|
+
endpoint = "/v5/order/create-batch"
|
|
475
|
+
payload = {
|
|
476
|
+
"category": category,
|
|
477
|
+
"request": request,
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
await self._limiter.order_limit(category, endpoint, cost=len(request))
|
|
481
|
+
|
|
482
|
+
raw = await self._fetch("POST", self._base_url, endpoint, payload, signed=True)
|
|
483
|
+
return self._batch_order_response_decoder.decode(raw)
|
|
484
|
+
|
|
485
|
+
async def post_v5_order_cancel_batch(
|
|
486
|
+
self,
|
|
487
|
+
category: str,
|
|
488
|
+
request: List[Dict[str, Any]],
|
|
489
|
+
) -> BybitBatchCancelOrderResponse:
|
|
490
|
+
"""
|
|
491
|
+
Cancel multiple orders in a single request
|
|
492
|
+
https://bybit-exchange.github.io/docs/v5/order/batch-cancel
|
|
493
|
+
|
|
494
|
+
Maximum orders per request:
|
|
495
|
+
- Linear / Inverse: 20 orders
|
|
496
|
+
- Spot: 10 orders
|
|
497
|
+
"""
|
|
498
|
+
endpoint = "/v5/order/cancel-batch"
|
|
499
|
+
payload = {
|
|
500
|
+
"category": category,
|
|
501
|
+
"request": request,
|
|
502
|
+
}
|
|
503
|
+
# await self._limiter.order_limit(category, endpoint, cost=len(request))
|
|
504
|
+
raw = await self._fetch("POST", self._base_url, endpoint, payload, signed=True)
|
|
505
|
+
return self._batch_cancel_order_response_decoder.decode(raw)
|
|
506
|
+
|
|
507
|
+
async def get_v5_market_tickers(
|
|
508
|
+
self,
|
|
509
|
+
category: str,
|
|
510
|
+
symbol: str | None = None,
|
|
511
|
+
base_coin: str | None = None,
|
|
512
|
+
exp_date: str | None = None,
|
|
513
|
+
) -> BybitTickersResponse:
|
|
514
|
+
"""
|
|
515
|
+
GET /v5/market/tickers
|
|
516
|
+
|
|
517
|
+
Query for the latest price snapshot, best bid/ask price, and trading volume
|
|
518
|
+
in the last 24 hours.
|
|
519
|
+
|
|
520
|
+
Covers: Spot / USDT contract / USDC contract / Inverse contract / Option
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
category: Product type (spot, linear, inverse, option)
|
|
524
|
+
symbol: Symbol name (optional), like BTCUSDT, uppercase only
|
|
525
|
+
base_coin: Base coin (optional), uppercase only. Apply to option only
|
|
526
|
+
exp_date: Expiry date (optional), e.g., 25DEC22. Apply to option only
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
BybitTickersResponse: Response containing ticker data
|
|
530
|
+
"""
|
|
531
|
+
endpoint = "/v5/market/tickers"
|
|
532
|
+
payload = {
|
|
533
|
+
"category": category,
|
|
534
|
+
"symbol": symbol,
|
|
535
|
+
"baseCoin": base_coin,
|
|
536
|
+
"expDate": exp_date,
|
|
537
|
+
}
|
|
538
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
539
|
+
|
|
540
|
+
await self._limiter.public_limit(endpoint)
|
|
541
|
+
raw = await self._fetch_async(
|
|
542
|
+
"GET", self._base_url, endpoint, payload, signed=False
|
|
543
|
+
)
|
|
544
|
+
return self._tickers_response_decoder.decode(raw)
|
|
545
|
+
|
|
546
|
+
async def get_v5_position_limit_info(
|
|
547
|
+
self,
|
|
548
|
+
symbol: str,
|
|
549
|
+
) -> Dict[str, Any]:
|
|
550
|
+
"""
|
|
551
|
+
GET /v5/position/limit-info
|
|
552
|
+
|
|
553
|
+
Retrieve position limit information for a specific symbol and category.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
category: Product type (linear, inverse)
|
|
557
|
+
symbol: Symbol name, like BTCUSDT, uppercase only
|
|
558
|
+
Returns:
|
|
559
|
+
Dict[str, Any]: Response containing position limit information
|
|
560
|
+
"""
|
|
561
|
+
endpoint = "/v5/position/limit-info"
|
|
562
|
+
payload = {
|
|
563
|
+
"symbol": symbol,
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
await self._limiter.query_limit(endpoint)
|
|
567
|
+
raw = await self._fetch_async(
|
|
568
|
+
"GET", self._base_url, endpoint, payload, signed=True
|
|
569
|
+
)
|
|
570
|
+
return self._msg_decoder.decode(raw)
|