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,578 @@
|
|
|
1
|
+
import msgspec
|
|
2
|
+
|
|
3
|
+
# import asyncio
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
from walrasquant.base import PublicConnector, PrivateConnector
|
|
6
|
+
from walrasquant.config import OrderQueryConfig
|
|
7
|
+
from walrasquant.core.nautilius_core import MessageBus, LiveClock
|
|
8
|
+
from walrasquant.core.entity import TaskManager
|
|
9
|
+
from walrasquant.core.cache import AsyncCache
|
|
10
|
+
from walrasquant.core.registry import OrderRegistry
|
|
11
|
+
from walrasquant.schema import (
|
|
12
|
+
BookL1,
|
|
13
|
+
Trade,
|
|
14
|
+
Kline,
|
|
15
|
+
# BookL2,
|
|
16
|
+
# BookOrderData,
|
|
17
|
+
# FundingRate,
|
|
18
|
+
# IndexPrice,
|
|
19
|
+
# MarkPrice,
|
|
20
|
+
KlineList,
|
|
21
|
+
Ticker,
|
|
22
|
+
)
|
|
23
|
+
from walrasquant.constants import (
|
|
24
|
+
KlineInterval,
|
|
25
|
+
BookLevel,
|
|
26
|
+
)
|
|
27
|
+
from walrasquant.exchange.bitget.schema import (
|
|
28
|
+
BitgetMarket,
|
|
29
|
+
BitgetWsUtaGeneralMsg,
|
|
30
|
+
BitgetWsUtaArgMsg,
|
|
31
|
+
BitgetBooks1WsMsg,
|
|
32
|
+
BitgetWsTradeWsMsg,
|
|
33
|
+
BitgetWsCandleWsMsg,
|
|
34
|
+
)
|
|
35
|
+
from walrasquant.exchange.bitget.rest_api import BitgetApiClient
|
|
36
|
+
from walrasquant.exchange.bitget.websockets import BitgetWSClient
|
|
37
|
+
from walrasquant.exchange.bitget.oms import BitgetOrderManagementSystem
|
|
38
|
+
from walrasquant.exchange.bitget.constants import (
|
|
39
|
+
BitgetAccountType,
|
|
40
|
+
BitgetKlineInterval,
|
|
41
|
+
BitgetUtaInstType,
|
|
42
|
+
BitgetEnumParser,
|
|
43
|
+
BitgetInstType,
|
|
44
|
+
)
|
|
45
|
+
from walrasquant.exchange.bitget.exchange import BitgetExchangeManager
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BitgetPublicConnector(PublicConnector):
|
|
49
|
+
_uta_inst_type_map = {
|
|
50
|
+
BitgetUtaInstType.SPOT: "spot",
|
|
51
|
+
BitgetUtaInstType.USDC_FUTURES: "linear",
|
|
52
|
+
BitgetUtaInstType.USDT_FUTURES: "linear",
|
|
53
|
+
BitgetUtaInstType.COIN_FUTURES: "inverse",
|
|
54
|
+
}
|
|
55
|
+
_inst_type_map = {
|
|
56
|
+
BitgetInstType.SPOT: "spot",
|
|
57
|
+
BitgetInstType.USDC_FUTURES: "linear",
|
|
58
|
+
BitgetInstType.USDT_FUTURES: "linear",
|
|
59
|
+
BitgetInstType.COIN_FUTURES: "inverse",
|
|
60
|
+
}
|
|
61
|
+
_api_client: BitgetApiClient
|
|
62
|
+
_ws_client: BitgetWSClient
|
|
63
|
+
_account_type: BitgetAccountType
|
|
64
|
+
_market: Dict[str, BitgetMarket]
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
account_type: BitgetAccountType,
|
|
69
|
+
exchange: BitgetExchangeManager,
|
|
70
|
+
msgbus: MessageBus,
|
|
71
|
+
clock: LiveClock,
|
|
72
|
+
task_manager: TaskManager,
|
|
73
|
+
custom_url: str | None = None,
|
|
74
|
+
enable_rate_limit: bool = True,
|
|
75
|
+
max_subscriptions_per_client: int | None = None,
|
|
76
|
+
max_clients: int | None = None,
|
|
77
|
+
):
|
|
78
|
+
if not account_type.is_uta:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
"Public connector only supports UTA account type."
|
|
81
|
+
"Please set `account_type` to `BitgetAccountType.UTA` or `BitgetAccountType.UTA_DEMO`."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
super().__init__(
|
|
85
|
+
account_type=account_type,
|
|
86
|
+
market=exchange.market,
|
|
87
|
+
market_id=exchange.market_id,
|
|
88
|
+
exchange_id=exchange.exchange_id,
|
|
89
|
+
ws_client=BitgetWSClient(
|
|
90
|
+
account_type=account_type,
|
|
91
|
+
handler=self._ws_msg_handler,
|
|
92
|
+
clock=clock,
|
|
93
|
+
task_manager=task_manager,
|
|
94
|
+
custom_url=custom_url,
|
|
95
|
+
max_subscriptions_per_client=max_subscriptions_per_client,
|
|
96
|
+
max_clients=max_clients,
|
|
97
|
+
),
|
|
98
|
+
clock=clock,
|
|
99
|
+
msgbus=msgbus,
|
|
100
|
+
api_client=BitgetApiClient(
|
|
101
|
+
clock=clock,
|
|
102
|
+
testnet=account_type.is_testnet,
|
|
103
|
+
enable_rate_limit=enable_rate_limit,
|
|
104
|
+
),
|
|
105
|
+
task_manager=task_manager,
|
|
106
|
+
)
|
|
107
|
+
self._testnet = account_type.is_testnet
|
|
108
|
+
self._ws_msg_general_decoder = msgspec.json.Decoder(BitgetWsUtaGeneralMsg)
|
|
109
|
+
self._ws_books1_decoder = msgspec.json.Decoder(BitgetBooks1WsMsg)
|
|
110
|
+
self._ws_trade_decoder = msgspec.json.Decoder(BitgetWsTradeWsMsg)
|
|
111
|
+
self._ws_candle_decoder = msgspec.json.Decoder(BitgetWsCandleWsMsg)
|
|
112
|
+
|
|
113
|
+
def _get_inst_type(self, market: BitgetMarket):
|
|
114
|
+
if market.spot:
|
|
115
|
+
return "spot"
|
|
116
|
+
elif market.linear:
|
|
117
|
+
return "usdt-futures" if market.quote == "USDT" else "usdc-futures"
|
|
118
|
+
elif market.inverse:
|
|
119
|
+
return "coin-futures"
|
|
120
|
+
|
|
121
|
+
def _uta_inst_type_suffix(self, inst_type: BitgetUtaInstType):
|
|
122
|
+
return self._uta_inst_type_map[inst_type]
|
|
123
|
+
|
|
124
|
+
def _inst_type_suffix(self, inst_type: BitgetInstType):
|
|
125
|
+
return self._inst_type_map[inst_type]
|
|
126
|
+
|
|
127
|
+
async def request_klines(
|
|
128
|
+
self,
|
|
129
|
+
symbol: str,
|
|
130
|
+
interval: KlineInterval,
|
|
131
|
+
limit: int | None = None,
|
|
132
|
+
start_time: int | None = None,
|
|
133
|
+
end_time: int | None = None,
|
|
134
|
+
) -> KlineList:
|
|
135
|
+
"""Request klines"""
|
|
136
|
+
raise NotImplementedError
|
|
137
|
+
|
|
138
|
+
async def request_ticker(
|
|
139
|
+
self,
|
|
140
|
+
symbol: str,
|
|
141
|
+
) -> Ticker:
|
|
142
|
+
"""Request 24hr ticker data"""
|
|
143
|
+
market = self._market.get(symbol)
|
|
144
|
+
if not market:
|
|
145
|
+
raise ValueError(f"Symbol is not available: {symbol}")
|
|
146
|
+
|
|
147
|
+
if market.spot:
|
|
148
|
+
category = "SPOT"
|
|
149
|
+
elif market.linear:
|
|
150
|
+
category = "USDT-FUTURES" if market.quote == "USDT" else "USDC-FUTURES"
|
|
151
|
+
elif market.inverse:
|
|
152
|
+
category = "COIN-FUTURES"
|
|
153
|
+
|
|
154
|
+
res = await self._api_client.get_api_v3_market_tickers(
|
|
155
|
+
category=category,
|
|
156
|
+
symbol=symbol,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
ticker_response = res.data[0]
|
|
160
|
+
|
|
161
|
+
ticker = Ticker(
|
|
162
|
+
exchange=self._exchange_id,
|
|
163
|
+
symbol=symbol,
|
|
164
|
+
last_price=float(ticker_response.lastPrice),
|
|
165
|
+
timestamp=self._clock.timestamp_ms(),
|
|
166
|
+
volume=float(ticker_response.volume24h),
|
|
167
|
+
volumeCcy=float(ticker_response.turnover24h),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return ticker
|
|
171
|
+
|
|
172
|
+
async def request_all_tickers(
|
|
173
|
+
self,
|
|
174
|
+
) -> Dict[str, Ticker]:
|
|
175
|
+
"""Request 24hr ticker data for multiple symbols"""
|
|
176
|
+
all_tickers: Dict[str, Ticker] = {}
|
|
177
|
+
for category in ["SPOT", "USDT-FUTURES", "COIN-FUTURES", "USDC-FUTURES"]:
|
|
178
|
+
res = await self._api_client.get_api_v3_market_tickers(
|
|
179
|
+
category=category,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
for ticker_res in res.data:
|
|
183
|
+
sym_id = ticker_res.symbol
|
|
184
|
+
symbol = self._market_id.get(
|
|
185
|
+
f"{sym_id}_{self._inst_type_suffix(ticker_res.category)}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if not symbol:
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
all_tickers[symbol] = Ticker(
|
|
192
|
+
exchange=self._exchange_id,
|
|
193
|
+
symbol=symbol,
|
|
194
|
+
last_price=float(ticker_res.lastPrice),
|
|
195
|
+
timestamp=self._clock.timestamp_ms(),
|
|
196
|
+
volume=float(ticker_res.volume24h),
|
|
197
|
+
volumeCcy=float(ticker_res.turnover24h),
|
|
198
|
+
)
|
|
199
|
+
return all_tickers
|
|
200
|
+
|
|
201
|
+
async def request_index_klines(
|
|
202
|
+
self,
|
|
203
|
+
symbol: str,
|
|
204
|
+
interval: KlineInterval,
|
|
205
|
+
limit: int | None = None,
|
|
206
|
+
start_time: int | None = None,
|
|
207
|
+
end_time: int | None = None,
|
|
208
|
+
) -> KlineList:
|
|
209
|
+
"""Request index klines"""
|
|
210
|
+
raise NotImplementedError
|
|
211
|
+
|
|
212
|
+
def _ws_msg_handler(self, raw: bytes):
|
|
213
|
+
"""Handle incoming WebSocket messages"""
|
|
214
|
+
# Process the message based on its type
|
|
215
|
+
# if raw == b"pong":
|
|
216
|
+
# self._ws_client._transport.notify_user_specific_pong_received()
|
|
217
|
+
# self._log.debug(f"Pong received: `{raw.decode()}`")
|
|
218
|
+
# return
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
msg = self._ws_msg_general_decoder.decode(raw)
|
|
222
|
+
if msg.is_event_data:
|
|
223
|
+
self._handle_event_data(msg)
|
|
224
|
+
elif msg.arg is not None:
|
|
225
|
+
if msg.arg.topic == "books1":
|
|
226
|
+
self._handle_books1_data(raw, msg.arg)
|
|
227
|
+
elif msg.arg.topic == "publicTrade":
|
|
228
|
+
self._handle_trade_data(raw, msg.arg)
|
|
229
|
+
elif msg.arg.topic == "kline":
|
|
230
|
+
self._handle_candle_data(raw, msg.arg)
|
|
231
|
+
|
|
232
|
+
# print(f"Received WebSocket message: {raw}")
|
|
233
|
+
except msgspec.DecodeError as e:
|
|
234
|
+
self._log.error(f"Error decoding message: {str(raw)} {e}")
|
|
235
|
+
|
|
236
|
+
def _handle_event_data(self, msg: BitgetWsUtaGeneralMsg):
|
|
237
|
+
if msg.event == "subscribe" and msg.arg is not None:
|
|
238
|
+
arg = msg.arg
|
|
239
|
+
self._log.debug(f"Subscribed to {arg.message}")
|
|
240
|
+
elif msg.event == "error":
|
|
241
|
+
code = msg.code
|
|
242
|
+
error_msg = msg.msg
|
|
243
|
+
self._log.error(f"Subscribed error code={code} {error_msg}")
|
|
244
|
+
|
|
245
|
+
def _handle_candle_data(self, raw: bytes, arg: BitgetWsUtaArgMsg):
|
|
246
|
+
msg = self._ws_candle_decoder.decode(raw)
|
|
247
|
+
self._log.debug(f"Received kline data: {str(msg)}")
|
|
248
|
+
sym_id = f"{arg.symbol}_{self._uta_inst_type_suffix(arg.instType)}"
|
|
249
|
+
symbol = self._market_id[sym_id]
|
|
250
|
+
interval = BitgetEnumParser.parse_kline_interval(
|
|
251
|
+
BitgetKlineInterval(arg.interval)
|
|
252
|
+
)
|
|
253
|
+
for data in msg.data:
|
|
254
|
+
kline = Kline(
|
|
255
|
+
exchange=self._exchange_id,
|
|
256
|
+
symbol=symbol,
|
|
257
|
+
open=float(data.open),
|
|
258
|
+
high=float(data.high),
|
|
259
|
+
low=float(data.low),
|
|
260
|
+
close=float(data.close),
|
|
261
|
+
volume=float(data.volume),
|
|
262
|
+
quote_volume=float(data.turnover),
|
|
263
|
+
start=int(data.start),
|
|
264
|
+
timestamp=self._clock.timestamp_ms(),
|
|
265
|
+
interval=interval,
|
|
266
|
+
confirm=False, # NOTE: need to handle confirm yourself
|
|
267
|
+
)
|
|
268
|
+
self._msgbus.publish(topic="kline", msg=kline)
|
|
269
|
+
# self._log.debug(f"Kline update: {str(kline)}")
|
|
270
|
+
|
|
271
|
+
def _handle_trade_data(self, raw: bytes, arg: BitgetWsUtaArgMsg):
|
|
272
|
+
msg = self._ws_trade_decoder.decode(raw)
|
|
273
|
+
sym_id = f"{arg.symbol}_{self._uta_inst_type_suffix(arg.instType)}"
|
|
274
|
+
for data in msg.data:
|
|
275
|
+
trade = Trade(
|
|
276
|
+
exchange=self._exchange_id,
|
|
277
|
+
symbol=self._market_id[sym_id],
|
|
278
|
+
price=float(data.p),
|
|
279
|
+
size=float(data.v),
|
|
280
|
+
timestamp=int(data.T),
|
|
281
|
+
side=BitgetEnumParser.parse_order_side(data.S),
|
|
282
|
+
)
|
|
283
|
+
self._msgbus.publish(topic="trade", msg=trade)
|
|
284
|
+
# self._log.debug(f"Trade update: {str(trade)}")
|
|
285
|
+
|
|
286
|
+
def _handle_books1_data(self, raw: bytes, arg: BitgetWsUtaArgMsg):
|
|
287
|
+
msg = self._ws_books1_decoder.decode(raw)
|
|
288
|
+
sym_id = f"{arg.symbol}_{self._uta_inst_type_suffix(arg.instType)}"
|
|
289
|
+
symbol = self._market_id[sym_id]
|
|
290
|
+
for data in msg.data:
|
|
291
|
+
bids = data.b[0]
|
|
292
|
+
asks = data.a[0]
|
|
293
|
+
bookl1 = BookL1(
|
|
294
|
+
exchange=self._exchange_id,
|
|
295
|
+
symbol=symbol,
|
|
296
|
+
bid=float(bids.px),
|
|
297
|
+
bid_size=float(bids.sz),
|
|
298
|
+
ask=float(asks.px),
|
|
299
|
+
ask_size=float(asks.sz),
|
|
300
|
+
timestamp=int(data.ts),
|
|
301
|
+
)
|
|
302
|
+
self._msgbus.publish(topic="bookl1", msg=bookl1)
|
|
303
|
+
# self._log.debug(f"BookL1 update: {str(bookl1)}")
|
|
304
|
+
|
|
305
|
+
def subscribe_bookl1(self, symbol: str | List[str]):
|
|
306
|
+
symbol = symbol if isinstance(symbol, list) else [symbol]
|
|
307
|
+
symbols_by_inst_type = {}
|
|
308
|
+
|
|
309
|
+
for sym in symbol:
|
|
310
|
+
market = self._market.get(sym)
|
|
311
|
+
if not market:
|
|
312
|
+
raise ValueError(f"Symbol {sym} not found in market data.")
|
|
313
|
+
inst_type = self._get_inst_type(market)
|
|
314
|
+
if inst_type not in symbols_by_inst_type:
|
|
315
|
+
symbols_by_inst_type[inst_type] = []
|
|
316
|
+
symbols_by_inst_type[inst_type].append(market.id)
|
|
317
|
+
|
|
318
|
+
for inst_type, symbols in symbols_by_inst_type.items():
|
|
319
|
+
self._ws_client.subscribe_depth_v3(symbols, inst_type, "books1")
|
|
320
|
+
|
|
321
|
+
def unsubscribe_bookl1(self, symbol: str | List[str]):
|
|
322
|
+
symbol = symbol if isinstance(symbol, list) else [symbol]
|
|
323
|
+
symbols_by_inst_type = {}
|
|
324
|
+
|
|
325
|
+
for sym in symbol:
|
|
326
|
+
market = self._market.get(sym)
|
|
327
|
+
if not market:
|
|
328
|
+
raise ValueError(f"Symbol {sym} not found in market data.")
|
|
329
|
+
inst_type = self._get_inst_type(market)
|
|
330
|
+
if inst_type not in symbols_by_inst_type:
|
|
331
|
+
symbols_by_inst_type[inst_type] = []
|
|
332
|
+
symbols_by_inst_type[inst_type].append(market.id)
|
|
333
|
+
|
|
334
|
+
for inst_type, symbols in symbols_by_inst_type.items():
|
|
335
|
+
self._ws_client.unsubscribe_depth_v3(symbols, inst_type, "books1")
|
|
336
|
+
|
|
337
|
+
def subscribe_trade(self, symbol):
|
|
338
|
+
symbol = symbol if isinstance(symbol, list) else [symbol]
|
|
339
|
+
symbols_by_inst_type = {}
|
|
340
|
+
|
|
341
|
+
for sym in symbol:
|
|
342
|
+
market = self._market.get(sym)
|
|
343
|
+
if not market:
|
|
344
|
+
raise ValueError(f"Symbol {sym} not found in market data.")
|
|
345
|
+
inst_type = self._get_inst_type(market)
|
|
346
|
+
if inst_type not in symbols_by_inst_type:
|
|
347
|
+
symbols_by_inst_type[inst_type] = []
|
|
348
|
+
symbols_by_inst_type[inst_type].append(market.id)
|
|
349
|
+
|
|
350
|
+
for inst_type, symbols in symbols_by_inst_type.items():
|
|
351
|
+
self._ws_client.subscribe_trades_v3(symbols, inst_type)
|
|
352
|
+
|
|
353
|
+
def unsubscribe_trade(self, symbol):
|
|
354
|
+
symbol = symbol if isinstance(symbol, list) else [symbol]
|
|
355
|
+
symbols_by_inst_type = {}
|
|
356
|
+
|
|
357
|
+
for sym in symbol:
|
|
358
|
+
market = self._market.get(sym)
|
|
359
|
+
if not market:
|
|
360
|
+
raise ValueError(f"Symbol {sym} not found in market data.")
|
|
361
|
+
inst_type = self._get_inst_type(market)
|
|
362
|
+
if inst_type not in symbols_by_inst_type:
|
|
363
|
+
symbols_by_inst_type[inst_type] = []
|
|
364
|
+
symbols_by_inst_type[inst_type].append(market.id)
|
|
365
|
+
|
|
366
|
+
for inst_type, symbols in symbols_by_inst_type.items():
|
|
367
|
+
self._ws_client.unsubscribe_trades_v3(symbols, inst_type)
|
|
368
|
+
|
|
369
|
+
def subscribe_kline(self, symbol: str | List[str], interval: KlineInterval):
|
|
370
|
+
"""Subscribe to the kline data"""
|
|
371
|
+
symbol = symbol if isinstance(symbol, list) else [symbol]
|
|
372
|
+
bitget_interval = BitgetEnumParser.to_bitget_kline_interval(interval)
|
|
373
|
+
symbols_by_inst_type = {}
|
|
374
|
+
|
|
375
|
+
for sym in symbol:
|
|
376
|
+
market = self._market.get(sym)
|
|
377
|
+
if not market:
|
|
378
|
+
raise ValueError(f"Symbol {sym} not found in market data.")
|
|
379
|
+
inst_type = self._get_inst_type(market)
|
|
380
|
+
if inst_type not in symbols_by_inst_type:
|
|
381
|
+
symbols_by_inst_type[inst_type] = []
|
|
382
|
+
symbols_by_inst_type[inst_type].append(market.id)
|
|
383
|
+
|
|
384
|
+
for inst_type, symbols in symbols_by_inst_type.items():
|
|
385
|
+
self._ws_client.subscribe_candlestick_v3(
|
|
386
|
+
symbols, inst_type, bitget_interval
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
def unsubscribe_kline(self, symbol: str | List[str], interval: KlineInterval):
|
|
390
|
+
"""Unsubscribe from the kline data"""
|
|
391
|
+
symbol = symbol if isinstance(symbol, list) else [symbol]
|
|
392
|
+
bitget_interval = BitgetEnumParser.to_bitget_kline_interval(interval)
|
|
393
|
+
symbols_by_inst_type = {}
|
|
394
|
+
|
|
395
|
+
for sym in symbol:
|
|
396
|
+
market = self._market.get(sym)
|
|
397
|
+
if not market:
|
|
398
|
+
raise ValueError(f"Symbol {sym} not found in market data.")
|
|
399
|
+
inst_type = self._get_inst_type(market)
|
|
400
|
+
if inst_type not in symbols_by_inst_type:
|
|
401
|
+
symbols_by_inst_type[inst_type] = []
|
|
402
|
+
symbols_by_inst_type[inst_type].append(market.id)
|
|
403
|
+
|
|
404
|
+
for inst_type, symbols in symbols_by_inst_type.items():
|
|
405
|
+
self._ws_client.unsubscribe_candlestick_v3(
|
|
406
|
+
symbols, inst_type, bitget_interval
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def subscribe_bookl2(self, symbol: str | List[str], level: BookLevel):
|
|
410
|
+
"""Subscribe to the bookl2 data"""
|
|
411
|
+
raise NotImplementedError
|
|
412
|
+
|
|
413
|
+
def unsubscribe_bookl2(self, symbol: str | List[str], level: BookLevel):
|
|
414
|
+
"""Unsubscribe from the bookl2 data"""
|
|
415
|
+
raise NotImplementedError
|
|
416
|
+
|
|
417
|
+
def subscribe_funding_rate(self, symbol: str | List[str]):
|
|
418
|
+
"""Subscribe to the funding rate data"""
|
|
419
|
+
raise NotImplementedError
|
|
420
|
+
|
|
421
|
+
def unsubscribe_funding_rate(self, symbol: str | List[str]):
|
|
422
|
+
"""Unsubscribe from the funding rate data"""
|
|
423
|
+
raise NotImplementedError
|
|
424
|
+
|
|
425
|
+
def subscribe_index_price(self, symbol: str | List[str]):
|
|
426
|
+
"""Subscribe to the index price data"""
|
|
427
|
+
raise NotImplementedError
|
|
428
|
+
|
|
429
|
+
def unsubscribe_index_price(self, symbol: str | List[str]):
|
|
430
|
+
"""Unsubscribe from the index price data"""
|
|
431
|
+
raise NotImplementedError
|
|
432
|
+
|
|
433
|
+
def subscribe_mark_price(self, symbol: str | List[str]):
|
|
434
|
+
"""Subscribe to the mark price data"""
|
|
435
|
+
raise NotImplementedError
|
|
436
|
+
|
|
437
|
+
def unsubscribe_mark_price(self, symbol: str | List[str]):
|
|
438
|
+
"""Unsubscribe from the mark price data"""
|
|
439
|
+
raise NotImplementedError
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class BitgetPrivateConnector(PrivateConnector):
|
|
443
|
+
_account_type: BitgetAccountType
|
|
444
|
+
_market: Dict[str, BitgetMarket]
|
|
445
|
+
_api_client: BitgetApiClient
|
|
446
|
+
_oms: BitgetOrderManagementSystem
|
|
447
|
+
|
|
448
|
+
def __init__(
|
|
449
|
+
self,
|
|
450
|
+
account_type: BitgetAccountType,
|
|
451
|
+
exchange: BitgetExchangeManager,
|
|
452
|
+
cache: AsyncCache,
|
|
453
|
+
registry: OrderRegistry,
|
|
454
|
+
clock: LiveClock,
|
|
455
|
+
msgbus: MessageBus,
|
|
456
|
+
task_manager: TaskManager,
|
|
457
|
+
order_query_config: OrderQueryConfig,
|
|
458
|
+
enable_rate_limit: bool = True,
|
|
459
|
+
max_subscriptions_per_client: int | None = None,
|
|
460
|
+
max_clients: int | None = None,
|
|
461
|
+
**kwargs,
|
|
462
|
+
):
|
|
463
|
+
if not exchange.api_key or not exchange.secret or not exchange.passphrase:
|
|
464
|
+
raise ValueError(
|
|
465
|
+
"ApiKey, Secret and Passphrase must be provided for private connector."
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
max_slippage = kwargs.pop("max_slippage", 0.02)
|
|
469
|
+
|
|
470
|
+
api_client = BitgetApiClient(
|
|
471
|
+
clock=clock,
|
|
472
|
+
api_key=exchange.api_key,
|
|
473
|
+
secret=exchange.secret,
|
|
474
|
+
passphrase=exchange.passphrase,
|
|
475
|
+
testnet=account_type.is_testnet,
|
|
476
|
+
enable_rate_limit=enable_rate_limit,
|
|
477
|
+
**kwargs,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
oms = BitgetOrderManagementSystem(
|
|
481
|
+
account_type=account_type,
|
|
482
|
+
api_key=exchange.api_key,
|
|
483
|
+
secret=exchange.secret,
|
|
484
|
+
passphrase=exchange.passphrase,
|
|
485
|
+
market=exchange.market,
|
|
486
|
+
market_id=exchange.market_id,
|
|
487
|
+
registry=registry,
|
|
488
|
+
cache=cache,
|
|
489
|
+
api_client=api_client,
|
|
490
|
+
exchange_id=exchange.exchange_id,
|
|
491
|
+
clock=clock,
|
|
492
|
+
msgbus=msgbus,
|
|
493
|
+
task_manager=task_manager,
|
|
494
|
+
max_slippage=max_slippage,
|
|
495
|
+
enable_rate_limit=enable_rate_limit,
|
|
496
|
+
max_subscriptions_per_client=max_subscriptions_per_client,
|
|
497
|
+
max_clients=max_clients,
|
|
498
|
+
order_query_config=order_query_config,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
super().__init__(
|
|
502
|
+
account_type=account_type,
|
|
503
|
+
market=exchange.market,
|
|
504
|
+
api_client=api_client,
|
|
505
|
+
task_manager=task_manager,
|
|
506
|
+
oms=oms,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
async def connect(self):
|
|
510
|
+
if self._account_type.is_uta:
|
|
511
|
+
self._oms._ws_client.subscribe_v3_order()
|
|
512
|
+
self._oms._ws_client.subscribe_v3_position()
|
|
513
|
+
self._oms._ws_client.subscribe_v3_account()
|
|
514
|
+
elif self._account_type.is_future:
|
|
515
|
+
self._oms._ws_client.subscribe_orders(
|
|
516
|
+
inst_types=["USDT-FUTURES", "USDC-FUTURES", "COIN-FUTURES"]
|
|
517
|
+
)
|
|
518
|
+
self._oms._ws_client.subscribe_positions(
|
|
519
|
+
inst_types=["USDT-FUTURES", "USDC-FUTURES", "COIN-FUTURES"]
|
|
520
|
+
)
|
|
521
|
+
self._oms._ws_client.subscribe_account(
|
|
522
|
+
inst_types=["USDT-FUTURES", "USDC-FUTURES", "COIN-FUTURES"]
|
|
523
|
+
)
|
|
524
|
+
elif self._account_type.is_spot:
|
|
525
|
+
self._oms._ws_client.subscribe_orders(inst_types=["SPOT"])
|
|
526
|
+
self._oms._ws_client.subscribe_account(inst_types=["SPOT"])
|
|
527
|
+
await self._oms._ws_client.connect()
|
|
528
|
+
await self._oms._ws_api_client.connect()
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
# async def main():
|
|
532
|
+
# from walrasquant.constants import settings
|
|
533
|
+
|
|
534
|
+
# API_KEY = settings.BITGET.DEMO1.API_KEY
|
|
535
|
+
# SECRET = settings.BITGET.DEMO1.SECRET
|
|
536
|
+
# PASSPHRASE = settings.BITGET.DEMO1.PASSPHRASE
|
|
537
|
+
# exchange = BitgetExchangeManager(
|
|
538
|
+
# config={
|
|
539
|
+
# "apiKey": API_KEY,
|
|
540
|
+
# "secret": SECRET,
|
|
541
|
+
# "password": PASSPHRASE,
|
|
542
|
+
# }
|
|
543
|
+
# )
|
|
544
|
+
# logguard, msgbus, clock = setup_nexus_core("test-001", level_stdout="DEBUG")
|
|
545
|
+
# task_manager = TaskManager(
|
|
546
|
+
# loop=asyncio.get_event_loop(),
|
|
547
|
+
# )
|
|
548
|
+
# private_connector = BitgetPrivateConnector(
|
|
549
|
+
# exchange=exchange,
|
|
550
|
+
# account_type=BitgetAccountType.UTA_DEMO,
|
|
551
|
+
# cache=AsyncCache(
|
|
552
|
+
# strategy_id="bitget_test",
|
|
553
|
+
# user_id="test_user",
|
|
554
|
+
# msgbus=msgbus,
|
|
555
|
+
# clock=clock,
|
|
556
|
+
# task_manager=task_manager,
|
|
557
|
+
# ),
|
|
558
|
+
# msgbus=msgbus,
|
|
559
|
+
# clock=clock,
|
|
560
|
+
# task_manager=task_manager,
|
|
561
|
+
# enable_rate_limit=True,
|
|
562
|
+
# )
|
|
563
|
+
# await private_connector.connect()
|
|
564
|
+
|
|
565
|
+
# public_connector = BitgetPublicConnector(
|
|
566
|
+
# account_type=BitgetAccountType.UTA,
|
|
567
|
+
# exchange=exchange,
|
|
568
|
+
# msgbus=msgbus,
|
|
569
|
+
# clock=clock,
|
|
570
|
+
# task_manager=task_manager,
|
|
571
|
+
# )
|
|
572
|
+
# await public_connector.subscribe_kline("BTCUSDT-PERP.BITGET", interval=KlineInterval.MINUTE_1)
|
|
573
|
+
|
|
574
|
+
# await task_manager.wait()
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
# if __name__ == "__main__":
|
|
578
|
+
# asyncio.run(main())
|