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,753 @@
|
|
|
1
|
+
import hmac
|
|
2
|
+
import base64
|
|
3
|
+
import asyncio
|
|
4
|
+
import picows
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, List, Dict
|
|
7
|
+
|
|
8
|
+
from walrasquant.base import WSClient
|
|
9
|
+
from walrasquant.core.entity import TaskManager
|
|
10
|
+
from walrasquant.core.nautilius_core import LiveClock
|
|
11
|
+
from walrasquant.exchange.bitget.constants import (
|
|
12
|
+
BitgetAccountType,
|
|
13
|
+
BitgetKlineInterval,
|
|
14
|
+
BitgetRateLimiter,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def user_pong_callback(self, frame: picows.WSFrame) -> bool:
|
|
19
|
+
return (
|
|
20
|
+
frame.msg_type == picows.WSMsgType.TEXT
|
|
21
|
+
and frame.get_payload_as_memoryview() == b"pong"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BitgetWSClient(WSClient):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
account_type: BitgetAccountType,
|
|
29
|
+
handler: Callable[..., Any],
|
|
30
|
+
task_manager: TaskManager,
|
|
31
|
+
clock: LiveClock,
|
|
32
|
+
api_key: str | None = None,
|
|
33
|
+
secret: str | None = None,
|
|
34
|
+
passphrase: str | None = None,
|
|
35
|
+
custom_url: str | None = None,
|
|
36
|
+
max_subscriptions_per_client: int | None = None,
|
|
37
|
+
max_clients: int | None = None,
|
|
38
|
+
):
|
|
39
|
+
self._api_key = api_key
|
|
40
|
+
self._secret = secret
|
|
41
|
+
self._passphrase = passphrase
|
|
42
|
+
self._account_type = account_type
|
|
43
|
+
|
|
44
|
+
if custom_url:
|
|
45
|
+
url = custom_url
|
|
46
|
+
elif self.is_private:
|
|
47
|
+
url = f"{account_type.stream_url}/private"
|
|
48
|
+
else:
|
|
49
|
+
url = f"{account_type.stream_url}/public"
|
|
50
|
+
|
|
51
|
+
super().__init__(
|
|
52
|
+
url,
|
|
53
|
+
handler=handler,
|
|
54
|
+
task_manager=task_manager,
|
|
55
|
+
clock=clock,
|
|
56
|
+
specific_ping_msg=b"ping",
|
|
57
|
+
auto_ping_strategy="ping_periodically",
|
|
58
|
+
ping_idle_timeout=30,
|
|
59
|
+
ping_reply_timeout=5,
|
|
60
|
+
user_pong_callback=user_pong_callback,
|
|
61
|
+
max_subscriptions_per_client=max_subscriptions_per_client,
|
|
62
|
+
max_clients=max_clients,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def is_private(self):
|
|
67
|
+
return (
|
|
68
|
+
self._api_key is not None
|
|
69
|
+
or self._secret is not None
|
|
70
|
+
or self._passphrase is not None
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def _get_auth_payload(self):
|
|
74
|
+
timestamp = self._clock.timestamp_ms()
|
|
75
|
+
message = f"{timestamp}GET/user/verify"
|
|
76
|
+
|
|
77
|
+
# Create HMAC-SHA256 signature
|
|
78
|
+
mac = hmac.new(
|
|
79
|
+
bytes(self._secret, encoding="utf8"), # type: ignore
|
|
80
|
+
bytes(message, encoding="utf-8"),
|
|
81
|
+
digestmod="sha256",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Get the digest and encode with base64
|
|
85
|
+
signature = base64.b64encode(mac.digest()).decode("utf-8")
|
|
86
|
+
|
|
87
|
+
# Form the login payload
|
|
88
|
+
payload = {
|
|
89
|
+
"op": "login",
|
|
90
|
+
"args": [
|
|
91
|
+
{
|
|
92
|
+
"apiKey": self._api_key,
|
|
93
|
+
"passphrase": self._passphrase, # If required
|
|
94
|
+
"timestamp": timestamp,
|
|
95
|
+
"sign": signature,
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return payload
|
|
101
|
+
|
|
102
|
+
async def _auth(self, client_id: int | None = None):
|
|
103
|
+
self.send(self._get_auth_payload(), client_id=client_id)
|
|
104
|
+
await asyncio.sleep(5)
|
|
105
|
+
|
|
106
|
+
def _send_payload(
|
|
107
|
+
self,
|
|
108
|
+
params: List[Dict[str, Any]],
|
|
109
|
+
op: str = "subscribe",
|
|
110
|
+
chunk_size: int = 100,
|
|
111
|
+
client_id: int | None = None,
|
|
112
|
+
):
|
|
113
|
+
# Split params into chunks of 100 if length exceeds 100
|
|
114
|
+
params_chunks = [
|
|
115
|
+
params[i : i + chunk_size] for i in range(0, len(params), chunk_size)
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
for chunk in params_chunks:
|
|
119
|
+
payload = {"op": op, "args": chunk}
|
|
120
|
+
self.send(payload, client_id=client_id)
|
|
121
|
+
|
|
122
|
+
def _subscribe(self, params: List[Dict[str, Any]]):
|
|
123
|
+
assigned = self._register_subscriptions(params)
|
|
124
|
+
if not assigned:
|
|
125
|
+
return
|
|
126
|
+
for client_id, client_params in assigned.items():
|
|
127
|
+
for param in client_params:
|
|
128
|
+
formatted_param = ".".join(param.values())
|
|
129
|
+
self._log.debug(f"Subscribing to {formatted_param}...")
|
|
130
|
+
if self._is_client_connected(client_id):
|
|
131
|
+
self._send_payload(client_params, op="subscribe", client_id=client_id)
|
|
132
|
+
|
|
133
|
+
def _unsubscribe(self, params: List[Dict[str, Any]]):
|
|
134
|
+
removed = self._unregister_subscriptions(params)
|
|
135
|
+
if not removed:
|
|
136
|
+
return
|
|
137
|
+
for client_id, client_params in removed.items():
|
|
138
|
+
for param in client_params:
|
|
139
|
+
formatted_param = ".".join(param.values())
|
|
140
|
+
self._log.debug(f"Unsubscribing from {formatted_param}...")
|
|
141
|
+
self._send_payload(client_params, op="unsubscribe", client_id=client_id)
|
|
142
|
+
|
|
143
|
+
def subscribe_depth(self, symbols: List[str], inst_type: str, channel: str):
|
|
144
|
+
if channel not in ["books1", "books5", "books15"]:
|
|
145
|
+
raise ValueError(f"Invalid channel: {channel}")
|
|
146
|
+
|
|
147
|
+
params = [
|
|
148
|
+
{"instType": inst_type, "channel": channel, "instId": symbol}
|
|
149
|
+
for symbol in symbols
|
|
150
|
+
]
|
|
151
|
+
self._subscribe(params)
|
|
152
|
+
|
|
153
|
+
def unsubscribe_depth(self, symbols: List[str], inst_type: str, channel: str):
|
|
154
|
+
if channel not in ["books1", "books5", "books15"]:
|
|
155
|
+
raise ValueError(f"Invalid channel: {channel}")
|
|
156
|
+
|
|
157
|
+
params = [
|
|
158
|
+
{"instType": inst_type, "channel": channel, "instId": symbol}
|
|
159
|
+
for symbol in symbols
|
|
160
|
+
]
|
|
161
|
+
self._unsubscribe(params)
|
|
162
|
+
|
|
163
|
+
def subscribe_candlesticks(
|
|
164
|
+
self, symbols: List[str], inst_type: str, interval: BitgetKlineInterval
|
|
165
|
+
):
|
|
166
|
+
params = [
|
|
167
|
+
{"instType": inst_type, "channel": interval.value, "instId": symbol}
|
|
168
|
+
for symbol in symbols
|
|
169
|
+
]
|
|
170
|
+
self._subscribe(params)
|
|
171
|
+
|
|
172
|
+
def unsubscribe_candlesticks(
|
|
173
|
+
self, symbols: List[str], inst_type: str, interval: BitgetKlineInterval
|
|
174
|
+
):
|
|
175
|
+
params = [
|
|
176
|
+
{"instType": inst_type, "channel": interval.value, "instId": symbol}
|
|
177
|
+
for symbol in symbols
|
|
178
|
+
]
|
|
179
|
+
self._unsubscribe(params)
|
|
180
|
+
|
|
181
|
+
def subscribe_trade(self, symbols: List[str], inst_type: str):
|
|
182
|
+
params = [
|
|
183
|
+
{"instType": inst_type, "channel": "trade", "instId": symbol}
|
|
184
|
+
for symbol in symbols
|
|
185
|
+
]
|
|
186
|
+
self._subscribe(params)
|
|
187
|
+
|
|
188
|
+
def unsubscribe_trade(self, symbols: List[str], inst_type: str):
|
|
189
|
+
params = [
|
|
190
|
+
{"instType": inst_type, "channel": "trade", "instId": symbol}
|
|
191
|
+
for symbol in symbols
|
|
192
|
+
]
|
|
193
|
+
self._unsubscribe(params)
|
|
194
|
+
|
|
195
|
+
def subscribe_ticker(self, symbols: List[str], inst_type: str):
|
|
196
|
+
params = [
|
|
197
|
+
{"instType": inst_type, "channel": "ticker", "instId": symbol}
|
|
198
|
+
for symbol in symbols
|
|
199
|
+
]
|
|
200
|
+
self._subscribe(params)
|
|
201
|
+
|
|
202
|
+
def unsubscribe_ticker(self, symbols: List[str], inst_type: str):
|
|
203
|
+
params = [
|
|
204
|
+
{"instType": inst_type, "channel": "ticker", "instId": symbol}
|
|
205
|
+
for symbol in symbols
|
|
206
|
+
]
|
|
207
|
+
self._unsubscribe(params)
|
|
208
|
+
|
|
209
|
+
def subscribe_account(self, inst_types: List[str] | str):
|
|
210
|
+
if isinstance(inst_types, str):
|
|
211
|
+
inst_types = [inst_types]
|
|
212
|
+
params = [
|
|
213
|
+
{"instType": inst_type, "channel": "account", "coin": "default"}
|
|
214
|
+
for inst_type in inst_types
|
|
215
|
+
]
|
|
216
|
+
self._subscribe(params)
|
|
217
|
+
|
|
218
|
+
def subscribe_positions(self, inst_types: List[str] | str):
|
|
219
|
+
if isinstance(inst_types, str):
|
|
220
|
+
inst_types = [inst_types]
|
|
221
|
+
params = [
|
|
222
|
+
{"instType": inst_type, "channel": "positions", "instId": "default"}
|
|
223
|
+
for inst_type in inst_types
|
|
224
|
+
]
|
|
225
|
+
self._subscribe(params)
|
|
226
|
+
|
|
227
|
+
def subscribe_orders(self, inst_types: List[str] | str):
|
|
228
|
+
if isinstance(inst_types, str):
|
|
229
|
+
inst_types = [inst_types]
|
|
230
|
+
params = [
|
|
231
|
+
{"instType": inst_type, "channel": "orders", "instId": "default"}
|
|
232
|
+
for inst_type in inst_types
|
|
233
|
+
]
|
|
234
|
+
self._subscribe(params)
|
|
235
|
+
|
|
236
|
+
############ UTA subscribe ###########
|
|
237
|
+
### Better Performance with v3 API ###
|
|
238
|
+
|
|
239
|
+
def subscribe_depth_v3(self, symbols: List[str], inst_type: str, topic: str):
|
|
240
|
+
if topic not in ["books1", "books5", "books15"]:
|
|
241
|
+
raise ValueError(f"Invalid channel: {topic}")
|
|
242
|
+
|
|
243
|
+
params = [
|
|
244
|
+
{"instType": inst_type, "topic": topic, "symbol": symbol}
|
|
245
|
+
for symbol in symbols
|
|
246
|
+
]
|
|
247
|
+
self._subscribe(params)
|
|
248
|
+
|
|
249
|
+
def unsubscribe_depth_v3(self, symbols: List[str], inst_type: str, topic: str):
|
|
250
|
+
if topic not in ["books1", "books5", "books15"]:
|
|
251
|
+
raise ValueError(f"Invalid channel: {topic}")
|
|
252
|
+
|
|
253
|
+
params = [
|
|
254
|
+
{"instType": inst_type, "topic": topic, "symbol": symbol}
|
|
255
|
+
for symbol in symbols
|
|
256
|
+
]
|
|
257
|
+
self._unsubscribe(params)
|
|
258
|
+
|
|
259
|
+
def subscribe_trades_v3(self, symbols: List[str], inst_type: str):
|
|
260
|
+
params = [
|
|
261
|
+
{"instType": inst_type, "topic": "publicTrade", "symbol": symbol}
|
|
262
|
+
for symbol in symbols
|
|
263
|
+
]
|
|
264
|
+
self._subscribe(params)
|
|
265
|
+
|
|
266
|
+
def unsubscribe_trades_v3(self, symbols: List[str], inst_type: str):
|
|
267
|
+
params = [
|
|
268
|
+
{"instType": inst_type, "topic": "publicTrade", "symbol": symbol}
|
|
269
|
+
for symbol in symbols
|
|
270
|
+
]
|
|
271
|
+
self._unsubscribe(params)
|
|
272
|
+
|
|
273
|
+
def subscribe_candlestick_v3(
|
|
274
|
+
self, symbols: List[str], inst_type: str, interval: BitgetKlineInterval
|
|
275
|
+
):
|
|
276
|
+
params = [
|
|
277
|
+
{
|
|
278
|
+
"instType": inst_type,
|
|
279
|
+
"topic": "kline",
|
|
280
|
+
"symbol": symbol,
|
|
281
|
+
"interval": interval.value,
|
|
282
|
+
}
|
|
283
|
+
for symbol in symbols
|
|
284
|
+
]
|
|
285
|
+
self._subscribe(params)
|
|
286
|
+
|
|
287
|
+
def unsubscribe_candlestick_v3(
|
|
288
|
+
self, symbols: List[str], inst_type: str, interval: BitgetKlineInterval
|
|
289
|
+
):
|
|
290
|
+
params = [
|
|
291
|
+
{
|
|
292
|
+
"instType": inst_type,
|
|
293
|
+
"topic": "kline",
|
|
294
|
+
"symbol": symbol,
|
|
295
|
+
"interval": interval.value,
|
|
296
|
+
}
|
|
297
|
+
for symbol in symbols
|
|
298
|
+
]
|
|
299
|
+
self._unsubscribe(params)
|
|
300
|
+
|
|
301
|
+
async def _resubscribe_for_client(
|
|
302
|
+
self, client_id: int, subscriptions: List[Dict[str, Any]]
|
|
303
|
+
):
|
|
304
|
+
if not subscriptions:
|
|
305
|
+
return
|
|
306
|
+
if self.is_private:
|
|
307
|
+
await self._auth(client_id=client_id)
|
|
308
|
+
self._send_payload(subscriptions, client_id=client_id)
|
|
309
|
+
|
|
310
|
+
def subscribe_v3_order(self):
|
|
311
|
+
params = [{"instType": "UTA", "topic": "order"}]
|
|
312
|
+
self._subscribe(params)
|
|
313
|
+
|
|
314
|
+
def subscribe_v3_position(self):
|
|
315
|
+
params = [{"instType": "UTA", "topic": "position"}]
|
|
316
|
+
self._subscribe(params)
|
|
317
|
+
|
|
318
|
+
def subscribe_v3_account(self):
|
|
319
|
+
params = [{"instType": "UTA", "topic": "account"}]
|
|
320
|
+
self._subscribe(params)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class BitgetWSApiClient(WSClient):
|
|
324
|
+
def __init__(
|
|
325
|
+
self,
|
|
326
|
+
account_type: BitgetAccountType,
|
|
327
|
+
api_key: str,
|
|
328
|
+
secret: str,
|
|
329
|
+
passphrase: str,
|
|
330
|
+
handler: Callable[..., Any],
|
|
331
|
+
task_manager: TaskManager,
|
|
332
|
+
clock: LiveClock,
|
|
333
|
+
enable_rate_limit: bool,
|
|
334
|
+
):
|
|
335
|
+
self._api_key = api_key
|
|
336
|
+
self._secret = secret
|
|
337
|
+
self._passphrase = passphrase
|
|
338
|
+
self._account_type = account_type
|
|
339
|
+
|
|
340
|
+
# Use V2 WebSocket API for trading
|
|
341
|
+
url = f"{account_type.stream_url}/private"
|
|
342
|
+
|
|
343
|
+
self._limiter = BitgetRateLimiter(
|
|
344
|
+
enable_rate_limit=enable_rate_limit,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
super().__init__(
|
|
348
|
+
url,
|
|
349
|
+
handler=handler,
|
|
350
|
+
task_manager=task_manager,
|
|
351
|
+
clock=clock,
|
|
352
|
+
specific_ping_msg=b"ping",
|
|
353
|
+
auto_ping_strategy="ping_periodically",
|
|
354
|
+
ping_idle_timeout=30,
|
|
355
|
+
ping_reply_timeout=5,
|
|
356
|
+
user_pong_callback=user_pong_callback,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def _get_auth_payload(self):
|
|
360
|
+
timestamp = self._clock.timestamp_ms()
|
|
361
|
+
message = f"{timestamp}GET/user/verify"
|
|
362
|
+
|
|
363
|
+
# Create HMAC-SHA256 signature
|
|
364
|
+
mac = hmac.new(
|
|
365
|
+
bytes(self._secret, encoding="utf8"),
|
|
366
|
+
bytes(message, encoding="utf-8"),
|
|
367
|
+
digestmod="sha256",
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Get the digest and encode with base64
|
|
371
|
+
signature = base64.b64encode(mac.digest()).decode("utf-8")
|
|
372
|
+
|
|
373
|
+
# Form the login payload
|
|
374
|
+
payload = {
|
|
375
|
+
"op": "login",
|
|
376
|
+
"args": [
|
|
377
|
+
{
|
|
378
|
+
"apiKey": self._api_key,
|
|
379
|
+
"passphrase": self._passphrase,
|
|
380
|
+
"timestamp": timestamp,
|
|
381
|
+
"sign": signature,
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return payload
|
|
387
|
+
|
|
388
|
+
async def _auth(self, client_id: int | None = None):
|
|
389
|
+
self.send(self._get_auth_payload(), client_id=client_id)
|
|
390
|
+
await asyncio.sleep(5)
|
|
391
|
+
|
|
392
|
+
def _submit(
|
|
393
|
+
self, id: str, instType: str, instId: str, channel: str, params: Dict[str, Any]
|
|
394
|
+
):
|
|
395
|
+
payload = {
|
|
396
|
+
"op": "trade",
|
|
397
|
+
"args": [
|
|
398
|
+
{
|
|
399
|
+
"id": id,
|
|
400
|
+
"instType": instType,
|
|
401
|
+
"instId": instId,
|
|
402
|
+
"channel": channel,
|
|
403
|
+
"params": params,
|
|
404
|
+
}
|
|
405
|
+
],
|
|
406
|
+
}
|
|
407
|
+
self._log.debug(str(payload))
|
|
408
|
+
self.send(payload)
|
|
409
|
+
|
|
410
|
+
def _uta_submit(
|
|
411
|
+
self, id: str, topic: str, category: str, args: List[Dict[str, Any]]
|
|
412
|
+
):
|
|
413
|
+
payload = {
|
|
414
|
+
"op": "trade",
|
|
415
|
+
"id": id,
|
|
416
|
+
"topic": topic,
|
|
417
|
+
"category": category,
|
|
418
|
+
"args": args,
|
|
419
|
+
}
|
|
420
|
+
self._log.debug(str(payload))
|
|
421
|
+
self.send(payload)
|
|
422
|
+
|
|
423
|
+
async def spot_place_order(
|
|
424
|
+
self,
|
|
425
|
+
id: str,
|
|
426
|
+
instId: str,
|
|
427
|
+
orderType: str,
|
|
428
|
+
side: str,
|
|
429
|
+
size: str,
|
|
430
|
+
force: str,
|
|
431
|
+
**kwargs,
|
|
432
|
+
):
|
|
433
|
+
params = {
|
|
434
|
+
"orderType": orderType,
|
|
435
|
+
"side": side,
|
|
436
|
+
"size": size,
|
|
437
|
+
"force": force,
|
|
438
|
+
**kwargs,
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
await self._limiter.order_limit("/api/v2/spot/trade/place-order")
|
|
442
|
+
self._submit(
|
|
443
|
+
id=id, instType="SPOT", instId=instId, channel="place-order", params=params
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
async def spot_cancel_order(
|
|
447
|
+
self,
|
|
448
|
+
id: str,
|
|
449
|
+
instId: str,
|
|
450
|
+
clientOid: str,
|
|
451
|
+
):
|
|
452
|
+
params = {
|
|
453
|
+
"clientOid": clientOid,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# await self._limiter.order_limit("/api/v2/spot/trade/cancel-order")
|
|
457
|
+
self._submit(
|
|
458
|
+
id=id, instType="SPOT", instId=instId, channel="cancel-order", params=params
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
async def future_place_order(
|
|
462
|
+
self,
|
|
463
|
+
id: str,
|
|
464
|
+
instId: str,
|
|
465
|
+
orderType: str,
|
|
466
|
+
side: str,
|
|
467
|
+
size: str,
|
|
468
|
+
force: str,
|
|
469
|
+
marginCoin: str,
|
|
470
|
+
marginMode: str,
|
|
471
|
+
instType: str,
|
|
472
|
+
**kwargs,
|
|
473
|
+
):
|
|
474
|
+
params = {
|
|
475
|
+
"orderType": orderType,
|
|
476
|
+
"side": side,
|
|
477
|
+
"size": size,
|
|
478
|
+
"force": force,
|
|
479
|
+
"marginCoin": marginCoin,
|
|
480
|
+
"marginMode": marginMode,
|
|
481
|
+
**kwargs,
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
await self._limiter.order_limit("/api/v2/mix/order/place-order")
|
|
485
|
+
self._submit(
|
|
486
|
+
id=id,
|
|
487
|
+
instType=instType,
|
|
488
|
+
instId=instId,
|
|
489
|
+
channel="place-order",
|
|
490
|
+
params=params,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
async def future_cancel_order(
|
|
494
|
+
self,
|
|
495
|
+
id: str,
|
|
496
|
+
instId: str,
|
|
497
|
+
clientOid: str,
|
|
498
|
+
instType: str,
|
|
499
|
+
):
|
|
500
|
+
params = {
|
|
501
|
+
"clientOid": clientOid,
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
# await self._limiter.order_limit("/api/v2/mix/order/cancel-order")
|
|
505
|
+
self._submit(
|
|
506
|
+
id=id,
|
|
507
|
+
instType=instType,
|
|
508
|
+
instId=instId,
|
|
509
|
+
channel="cancel-order",
|
|
510
|
+
params=params,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
async def uta_place_order(
|
|
514
|
+
self,
|
|
515
|
+
id: str,
|
|
516
|
+
category: str,
|
|
517
|
+
symbol: str,
|
|
518
|
+
orderType: str,
|
|
519
|
+
qty: str,
|
|
520
|
+
price: str,
|
|
521
|
+
side: str,
|
|
522
|
+
**kwargs,
|
|
523
|
+
):
|
|
524
|
+
args = [
|
|
525
|
+
{
|
|
526
|
+
"orderType": orderType,
|
|
527
|
+
"price": price,
|
|
528
|
+
"qty": qty,
|
|
529
|
+
"side": side,
|
|
530
|
+
"symbol": symbol,
|
|
531
|
+
**kwargs,
|
|
532
|
+
}
|
|
533
|
+
]
|
|
534
|
+
|
|
535
|
+
await self._limiter.order_limit("/api/v3/trade/place-order")
|
|
536
|
+
self._uta_submit(id=f"n{id}", topic="place-order", category=category, args=args)
|
|
537
|
+
|
|
538
|
+
async def uta_cancel_order(
|
|
539
|
+
self,
|
|
540
|
+
id: str,
|
|
541
|
+
clientOid: str,
|
|
542
|
+
):
|
|
543
|
+
args = [
|
|
544
|
+
{
|
|
545
|
+
"clientOid": clientOid,
|
|
546
|
+
}
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
# await self._limiter.order_limit("/api/v3/trade/cancel-order")
|
|
550
|
+
self._uta_submit(
|
|
551
|
+
id=f"c{id}",
|
|
552
|
+
topic="cancel-order",
|
|
553
|
+
category="", # No category specified in UTA cancel order
|
|
554
|
+
args=args,
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
async def _resubscribe_for_client(self, client_id: int, subscriptions: List[Any]):
|
|
558
|
+
await self._auth(client_id=client_id)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# async def main():
|
|
562
|
+
# from walrasquant.constants import settings
|
|
563
|
+
# from walrasquant.core.entity import TaskManager
|
|
564
|
+
# from walrasquant.core.nautilius_core import setup_nexus_core, UUID4
|
|
565
|
+
|
|
566
|
+
# API_KEY = settings.BITGET.DEMO1.API_KEY
|
|
567
|
+
# SECRET = settings.BITGET.DEMO1.SECRET
|
|
568
|
+
# PASSPHRASE = settings.BITGET.DEMO1.PASSPHRASE
|
|
569
|
+
|
|
570
|
+
# log_guard, _, clock = setup_nexus_core( # noqa
|
|
571
|
+
# trader_id="bnc-test",
|
|
572
|
+
# level_stdout="DEBUG",
|
|
573
|
+
# )
|
|
574
|
+
|
|
575
|
+
# task_manager = TaskManager(
|
|
576
|
+
# loop=asyncio.get_event_loop(),
|
|
577
|
+
# )
|
|
578
|
+
|
|
579
|
+
# ws_api_client = BitgetWSApiClient(
|
|
580
|
+
# account_type=BitgetAccountType.UTA_DEMO,
|
|
581
|
+
# api_key=API_KEY,
|
|
582
|
+
# secret=SECRET,
|
|
583
|
+
# passphrase=PASSPHRASE,
|
|
584
|
+
# handler=lambda msg: print(msg),
|
|
585
|
+
# task_manager=task_manager,
|
|
586
|
+
# clock=clock,
|
|
587
|
+
# enable_rate_limit=True,
|
|
588
|
+
# )
|
|
589
|
+
|
|
590
|
+
# await ws_api_client.connect()
|
|
591
|
+
# await ws_api_client.spot_place_order(
|
|
592
|
+
# id=UUID4().value,
|
|
593
|
+
# instId="BTCUSDT",
|
|
594
|
+
# orderType="limit",
|
|
595
|
+
# side="buy",
|
|
596
|
+
# size="0.001",
|
|
597
|
+
# price="116881",
|
|
598
|
+
# force="gtc",
|
|
599
|
+
# )
|
|
600
|
+
|
|
601
|
+
# await ws_api_client.spot_cancel_order(
|
|
602
|
+
# id="9ee1e7d2-99a1-4ff5-82f7-473f02ff38e7",
|
|
603
|
+
# instId="BTCUSDT",
|
|
604
|
+
# orderId="1344423929063153665",
|
|
605
|
+
# )
|
|
606
|
+
|
|
607
|
+
# await ws_api_client.uta_place_order(
|
|
608
|
+
# id=UUID4().value,
|
|
609
|
+
# category="spot", # SPOT MARGIN USDT-FUTURES COIN-FUTURES USDC-FUTURES
|
|
610
|
+
# symbol="BTCUSDT",
|
|
611
|
+
# orderType="limit",
|
|
612
|
+
# qty="0.001",
|
|
613
|
+
# price="110536",
|
|
614
|
+
# side="buy",
|
|
615
|
+
# )
|
|
616
|
+
|
|
617
|
+
# await ws_api_client.uta_cancel_order(
|
|
618
|
+
# id="3c14b801-ecc0-47f9-8126-56604d9f4c33",
|
|
619
|
+
# instId="BTCUSDT",
|
|
620
|
+
# orderId="1344515561982640128",
|
|
621
|
+
# )
|
|
622
|
+
|
|
623
|
+
# await task_manager.wait()
|
|
624
|
+
|
|
625
|
+
# {
|
|
626
|
+
# "event": "trade",
|
|
627
|
+
# "arg": [
|
|
628
|
+
# {
|
|
629
|
+
# "id": "30862609-4cb0-45cb-8562-992aaf232ee8",
|
|
630
|
+
# "instType": "SPOT",
|
|
631
|
+
# "channel": "place-order",
|
|
632
|
+
# "instId": "BTCUSDT",
|
|
633
|
+
# "params": {
|
|
634
|
+
# "orderId": "1344419561630867456",
|
|
635
|
+
# "clientOid": "0021c683-5e7e-416e-8dbb-dd34a0c3f97c",
|
|
636
|
+
# },
|
|
637
|
+
# }
|
|
638
|
+
# ],
|
|
639
|
+
# "code": 0,
|
|
640
|
+
# "msg": "Success",
|
|
641
|
+
# "ts": 1756260522296,
|
|
642
|
+
# }
|
|
643
|
+
|
|
644
|
+
# {
|
|
645
|
+
# "event": "error",
|
|
646
|
+
# "arg": [
|
|
647
|
+
# {
|
|
648
|
+
# "id": "1c6dbaae-4599-4371-9517-f800bc2f0dfb",
|
|
649
|
+
# "instType": "SPOT",
|
|
650
|
+
# "channel": "place-order",
|
|
651
|
+
# "instId": "BTCUSDT",
|
|
652
|
+
# "params": {
|
|
653
|
+
# "orderType": "limit",
|
|
654
|
+
# "side": "buy",
|
|
655
|
+
# "force": "gtc",
|
|
656
|
+
# "price": "80000",
|
|
657
|
+
# "size": "0.000001",
|
|
658
|
+
# },
|
|
659
|
+
# }
|
|
660
|
+
# ],
|
|
661
|
+
# "code": 43027,
|
|
662
|
+
# "msg": "The minimum order value 1 is not met",
|
|
663
|
+
# "ts": 1756260596285,
|
|
664
|
+
# }
|
|
665
|
+
|
|
666
|
+
# {
|
|
667
|
+
# "event": "trade",
|
|
668
|
+
# "arg": [
|
|
669
|
+
# {
|
|
670
|
+
# "id": "30862609-4cb0-45cb-8562-992aaf232ee8",
|
|
671
|
+
# "instType": "SPOT",
|
|
672
|
+
# "channel": "cancel-order",
|
|
673
|
+
# "instId": "BTCUSDT",
|
|
674
|
+
# "params": {"orderId": "1344419561630867456"},
|
|
675
|
+
# }
|
|
676
|
+
# ],
|
|
677
|
+
# "code": 0,
|
|
678
|
+
# "msg": "Success",
|
|
679
|
+
# "ts": 1756260769404,
|
|
680
|
+
# }
|
|
681
|
+
|
|
682
|
+
# {
|
|
683
|
+
# "event": "error",
|
|
684
|
+
# "arg": [
|
|
685
|
+
# {
|
|
686
|
+
# "id": "9ee1e7d2-99a1-4ff5-82f7-473f02ff38e7",
|
|
687
|
+
# "instType": "SPOT",
|
|
688
|
+
# "channel": "cancel-order",
|
|
689
|
+
# "instId": "BTCUSDT",
|
|
690
|
+
# "params": {"orderId": "1344423929063153665"},
|
|
691
|
+
# }
|
|
692
|
+
# ],
|
|
693
|
+
# "code": 43001,
|
|
694
|
+
# "msg": "The order does not exist",
|
|
695
|
+
# "ts": 1756261603661,
|
|
696
|
+
# }
|
|
697
|
+
|
|
698
|
+
##### UTA ACCOUNT #####
|
|
699
|
+
|
|
700
|
+
# {
|
|
701
|
+
# "event": "error",
|
|
702
|
+
# "id": "bb63695d-b11f-4c6d-8166-460c612dc252",
|
|
703
|
+
# "code": "41101",
|
|
704
|
+
# "msg": "Param category=SPOT error",
|
|
705
|
+
# }
|
|
706
|
+
|
|
707
|
+
# {
|
|
708
|
+
# "event": "error",
|
|
709
|
+
# "id": "401822f1-4014-4bf0-af07-723fcbe391d4",
|
|
710
|
+
# "code": "25206",
|
|
711
|
+
# "msg": "BTC/USDT trading price cannot exceed 5%",
|
|
712
|
+
# }
|
|
713
|
+
|
|
714
|
+
# {
|
|
715
|
+
# "event": "trade",
|
|
716
|
+
# "id": "90ab565b-f863-4377-b275-08020b6c0536",
|
|
717
|
+
# "category": "spot",
|
|
718
|
+
# "topic": "place-order",
|
|
719
|
+
# "args": [
|
|
720
|
+
# {
|
|
721
|
+
# "symbol": "BTCUSDT",
|
|
722
|
+
# "orderId": "1344507887740108800",
|
|
723
|
+
# "clientOid": "1344507887740108801",
|
|
724
|
+
# "cTime": "1756281580862",
|
|
725
|
+
# }
|
|
726
|
+
# ],
|
|
727
|
+
# "code": "0",
|
|
728
|
+
# "msg": "Success",
|
|
729
|
+
# "ts": "1756281580865",
|
|
730
|
+
# }
|
|
731
|
+
|
|
732
|
+
# {
|
|
733
|
+
# "event": "error",
|
|
734
|
+
# "id": "90ab565b-f863-4377-b275-08020b6c0536",
|
|
735
|
+
# "code": "25204",
|
|
736
|
+
# "msg": "Order does not exist",
|
|
737
|
+
# }
|
|
738
|
+
|
|
739
|
+
# {
|
|
740
|
+
# "event": "trade",
|
|
741
|
+
# "id": "3c14b801-ecc0-47f9-8126-56604d9f4c33",
|
|
742
|
+
# "topic": "cancel-order",
|
|
743
|
+
# "args": [
|
|
744
|
+
# {"orderId": "1344515561982640128", "clientOid": "1344515561982640129"}
|
|
745
|
+
# ],
|
|
746
|
+
# "code": "0",
|
|
747
|
+
# "msg": "Success",
|
|
748
|
+
# "ts": "1756283452147",
|
|
749
|
+
# }
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
# if __name__ == "__main__":
|
|
753
|
+
# asyncio.run(main())
|