walrasquant-lib 0.4.28__tar.gz → 0.5.0__tar.gz
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_lib-0.4.28 → walrasquant_lib-0.5.0}/PKG-INFO +1 -1
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/pyproject.toml +1 -1
- walrasquant_lib-0.5.0/src/walrasquant/base/connector.py +448 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/ems.py +75 -119
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/exchange.py +3 -3
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/oms.py +129 -43
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/sms.py +7 -6
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/ws_client.py +13 -3
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/constants.py +25 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/core/cache.py +55 -13
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/core/nautilius_core.py +1 -1
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/engine.py +10 -8
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/ems.py +10 -53
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/exchange.py +9 -39
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/oms.py +332 -178
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/rest_api.py +1 -1
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/schema.py +71 -1
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/connector.py +4 -4
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/ems.py +14 -72
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/exchange.py +10 -8
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/oms.py +261 -360
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/schema.py +45 -2
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/constants.py +0 -1
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/ems.py +2 -32
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/exchange.py +9 -7
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/oms.py +283 -173
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/rest_api.py +0 -1
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/schema.py +17 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/deribit/connector.py +13 -2
- walrasquant_lib-0.5.0/src/walrasquant/exchange/deribit/constants.py +213 -0
- walrasquant_lib-0.5.0/src/walrasquant/exchange/deribit/ems.py +26 -0
- walrasquant_lib-0.5.0/src/walrasquant/exchange/deribit/error.py +19 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/deribit/exchange.py +1 -2
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/deribit/factory.py +1 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/deribit/oms.py +177 -21
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/deribit/rest_api.py +83 -11
- walrasquant_lib-0.5.0/src/walrasquant/exchange/deribit/schema.py +68 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/deribit/websockets.py +59 -14
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/constants.py +1 -5
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/ems.py +8 -32
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/exchange.py +1 -2
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/oms.py +293 -182
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/schema.py +8 -0
- walrasquant_lib-0.5.0/src/walrasquant/exchange/okx/ems.py +63 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/exchange.py +29 -5
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/oms.py +329 -206
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/schema.py +58 -1
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/execution/algorithm.py +25 -40
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/schema.py +114 -91
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/strategy.py +23 -47
- walrasquant_lib-0.4.28/src/walrasquant/base/connector.py +0 -873
- walrasquant_lib-0.4.28/src/walrasquant/exchange/deribit/constants.py +0 -112
- walrasquant_lib-0.4.28/src/walrasquant/exchange/deribit/ems.py +0 -66
- walrasquant_lib-0.4.28/src/walrasquant/exchange/deribit/schema.py +0 -41
- walrasquant_lib-0.4.28/src/walrasquant/exchange/okx/ems.py +0 -146
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/README.md +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/aggregation.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/backends/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/backends/db.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/backends/db_memory.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/backends/db_postgresql.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/backends/db_sqlite.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/api_client.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/base/retry.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/config.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/core/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/core/clock.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/core/connection.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/core/entity.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/core/registry.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/error.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/base_factory.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/connector.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/constants.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/error.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/factory.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/binance/websockets.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/constants.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/error.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/factory.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/rest_api.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bitget/websockets.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/connector.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/error.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/factory.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/bybit/websockets.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/deribit/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/connector.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/error.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/factory.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/rest_api.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/signing.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/hyperliquid/websockets.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/connector.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/constants.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/error.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/factory.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/rest_api.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/okx/websockets.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/exchange/registry.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/execution/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/execution/algorithms/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/execution/algorithms/twap.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/execution/config.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/execution/constants.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/execution/schema.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/indicator.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/push.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/tools/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/tools/pm2_wrapper.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/web/__init__.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/web/app.py +0 -0
- {walrasquant_lib-0.4.28 → walrasquant_lib-0.5.0}/src/walrasquant/web/server.py +0 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, List, Literal, Mapping
|
|
3
|
+
import inspect
|
|
4
|
+
import nexuslog as logging
|
|
5
|
+
|
|
6
|
+
from walrasquant.base.oms import OrderManagementSystem
|
|
7
|
+
from walrasquant.base.ws_client import WSClient
|
|
8
|
+
from walrasquant.base.api_client import ApiClient
|
|
9
|
+
from walrasquant.aggregation import (
|
|
10
|
+
KlineAggregator,
|
|
11
|
+
TimeKlineAggregator,
|
|
12
|
+
VolumeKlineAggregator,
|
|
13
|
+
)
|
|
14
|
+
from walrasquant.schema import (
|
|
15
|
+
BaseMarket,
|
|
16
|
+
KlineList,
|
|
17
|
+
Ticker,
|
|
18
|
+
Trade,
|
|
19
|
+
)
|
|
20
|
+
from walrasquant.constants import ExchangeType, AccountType
|
|
21
|
+
from walrasquant.core.entity import TaskManager
|
|
22
|
+
from walrasquant.constants import (
|
|
23
|
+
KlineInterval,
|
|
24
|
+
BookLevel,
|
|
25
|
+
)
|
|
26
|
+
from walrasquant.core.nautilius_core import LiveClock, MessageBus
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ApiProxy:
|
|
30
|
+
def __init__(self, api_client: ApiClient, task_manager: TaskManager):
|
|
31
|
+
self._api_client = api_client
|
|
32
|
+
self._task_manager = task_manager
|
|
33
|
+
|
|
34
|
+
def __getattr__(self, name):
|
|
35
|
+
if not hasattr(self._api_client, name):
|
|
36
|
+
raise AttributeError(f"ApiClient has no attribute '{name}'")
|
|
37
|
+
|
|
38
|
+
api_method = getattr(self._api_client, name)
|
|
39
|
+
if not callable(api_method):
|
|
40
|
+
return api_method
|
|
41
|
+
|
|
42
|
+
# check whether the method is async
|
|
43
|
+
if not inspect.iscoroutinefunction(api_method):
|
|
44
|
+
return api_method
|
|
45
|
+
|
|
46
|
+
def sync_wrapper(*args, **kwargs):
|
|
47
|
+
return self._task_manager.run_sync(api_method(*args, **kwargs))
|
|
48
|
+
|
|
49
|
+
return sync_wrapper
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FireApiProxy:
|
|
53
|
+
"""Fire-and-forget proxy: submits async REST calls as background tasks, no return value."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, api_client: ApiClient, task_manager: TaskManager):
|
|
56
|
+
self._api_client = api_client
|
|
57
|
+
self._task_manager = task_manager
|
|
58
|
+
|
|
59
|
+
def __getattr__(self, name):
|
|
60
|
+
if not hasattr(self._api_client, name):
|
|
61
|
+
raise AttributeError(f"ApiClient has no attribute '{name}'")
|
|
62
|
+
|
|
63
|
+
api_method = getattr(self._api_client, name)
|
|
64
|
+
if not inspect.iscoroutinefunction(api_method):
|
|
65
|
+
return api_method
|
|
66
|
+
|
|
67
|
+
def fire_wrapper(*args, **kwargs):
|
|
68
|
+
self._task_manager.create_task(api_method(*args, **kwargs))
|
|
69
|
+
|
|
70
|
+
return fire_wrapper
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PublicConnector(ABC):
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
account_type: AccountType,
|
|
77
|
+
market: Mapping[str, BaseMarket],
|
|
78
|
+
market_id: Dict[str, str],
|
|
79
|
+
exchange_id: ExchangeType,
|
|
80
|
+
ws_client: WSClient,
|
|
81
|
+
msgbus: MessageBus,
|
|
82
|
+
clock: LiveClock,
|
|
83
|
+
api_client: ApiClient,
|
|
84
|
+
task_manager: TaskManager,
|
|
85
|
+
):
|
|
86
|
+
self._log = logging.getLogger(name=type(self).__name__)
|
|
87
|
+
self._account_type = account_type
|
|
88
|
+
self._market = market
|
|
89
|
+
self._market_id = market_id
|
|
90
|
+
self._exchange_id = exchange_id
|
|
91
|
+
self._ws_client = ws_client
|
|
92
|
+
self._msgbus = msgbus
|
|
93
|
+
self._api_client = api_client
|
|
94
|
+
self._clock = clock
|
|
95
|
+
self._task_manager = task_manager
|
|
96
|
+
|
|
97
|
+
# Aggregator management - key: symbol, value: list of aggregators
|
|
98
|
+
self._aggregators: Dict[str, List[KlineAggregator]] = {}
|
|
99
|
+
self._msgbus.subscribe(
|
|
100
|
+
topic="trade", handler=self._handle_trade_for_aggregators
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def account_type(self):
|
|
105
|
+
return self._account_type
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
async def request_klines(
|
|
109
|
+
self,
|
|
110
|
+
symbol: str,
|
|
111
|
+
interval: KlineInterval,
|
|
112
|
+
limit: int | None = None,
|
|
113
|
+
start_time: int | None = None,
|
|
114
|
+
end_time: int | None = None,
|
|
115
|
+
) -> KlineList:
|
|
116
|
+
"""Request klines"""
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
async def request_ticker(
|
|
121
|
+
self,
|
|
122
|
+
symbol: str,
|
|
123
|
+
) -> Ticker:
|
|
124
|
+
"""Request 24hr ticker data"""
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
async def request_all_tickers(
|
|
129
|
+
self,
|
|
130
|
+
) -> Dict[str, Ticker]:
|
|
131
|
+
"""Request 24hr ticker data for multiple symbols"""
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
@abstractmethod
|
|
135
|
+
async def request_index_klines(
|
|
136
|
+
self,
|
|
137
|
+
symbol: str,
|
|
138
|
+
interval: KlineInterval,
|
|
139
|
+
limit: int | None = None,
|
|
140
|
+
start_time: int | None = None,
|
|
141
|
+
end_time: int | None = None,
|
|
142
|
+
) -> KlineList:
|
|
143
|
+
"""Request index klines"""
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def subscribe_trade(self, symbol: str | List[str]):
|
|
148
|
+
"""Subscribe to the trade data"""
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def unsubscribe_trade(self, symbol: str | List[str]):
|
|
153
|
+
"""Unsubscribe from the trade data"""
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def subscribe_bookl1(self, symbol: str | List[str]):
|
|
158
|
+
"""Subscribe to the bookl1 data"""
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
@abstractmethod
|
|
162
|
+
def unsubscribe_bookl1(self, symbol: str | List[str]):
|
|
163
|
+
"""Unsubscribe from the bookl1 data"""
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
@abstractmethod
|
|
167
|
+
def subscribe_kline(
|
|
168
|
+
self,
|
|
169
|
+
symbol: str | List[str],
|
|
170
|
+
interval: KlineInterval,
|
|
171
|
+
):
|
|
172
|
+
"""Subscribe to the kline data
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
symbol: Symbol(s) to subscribe to
|
|
176
|
+
interval: Kline interval
|
|
177
|
+
use_aggregator: If True, use TimeKlineAggregator instead of exchange native klines
|
|
178
|
+
"""
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
@abstractmethod
|
|
182
|
+
def unsubscribe_kline(
|
|
183
|
+
self,
|
|
184
|
+
symbol: str | List[str],
|
|
185
|
+
interval: KlineInterval,
|
|
186
|
+
):
|
|
187
|
+
"""Unsubscribe from the kline data"""
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
def subscribe_kline_aggregator(
|
|
191
|
+
self,
|
|
192
|
+
symbol: str,
|
|
193
|
+
interval: KlineInterval,
|
|
194
|
+
build_with_no_updates: bool,
|
|
195
|
+
):
|
|
196
|
+
"""Subscribe to time-based kline data using TimeKlineAggregator
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
symbol: Symbol to subscribe to
|
|
200
|
+
interval: Kline interval
|
|
201
|
+
"""
|
|
202
|
+
# Ensure trade subscription for the symbol
|
|
203
|
+
self.subscribe_trade(symbol)
|
|
204
|
+
|
|
205
|
+
# Create and register time kline aggregator
|
|
206
|
+
aggregator = self._create_time_kline_aggregator(
|
|
207
|
+
symbol, interval, build_with_no_updates
|
|
208
|
+
)
|
|
209
|
+
self._add_aggregator(symbol, aggregator)
|
|
210
|
+
|
|
211
|
+
self._log.info(
|
|
212
|
+
"Time kline aggregator created for %s with interval %s", symbol, interval
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def unsubscribe_kline_aggregator(
|
|
216
|
+
self,
|
|
217
|
+
symbol: str,
|
|
218
|
+
interval: KlineInterval,
|
|
219
|
+
):
|
|
220
|
+
"""Unsubscribe from time-based kline data using TimeKlineAggregator
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
symbol: Symbol to unsubscribe from
|
|
224
|
+
interval: Kline interval
|
|
225
|
+
"""
|
|
226
|
+
aggregators = self._aggregators.get(symbol, [])
|
|
227
|
+
# Create a copy to avoid "list changed size during iteration" error
|
|
228
|
+
for aggregator in aggregators.copy():
|
|
229
|
+
if (
|
|
230
|
+
isinstance(aggregator, TimeKlineAggregator)
|
|
231
|
+
and aggregator.interval == interval
|
|
232
|
+
):
|
|
233
|
+
aggregator.stop()
|
|
234
|
+
aggregators.remove(aggregator)
|
|
235
|
+
self._log.info(
|
|
236
|
+
"Time kline aggregator stopped for %s with interval %s",
|
|
237
|
+
symbol,
|
|
238
|
+
interval,
|
|
239
|
+
)
|
|
240
|
+
if not aggregators:
|
|
241
|
+
self._aggregators.pop(symbol, None)
|
|
242
|
+
self.unsubscribe_trade(symbol)
|
|
243
|
+
|
|
244
|
+
def subscribe_volume_kline_aggregator(
|
|
245
|
+
self,
|
|
246
|
+
symbol: str,
|
|
247
|
+
volume_threshold: float,
|
|
248
|
+
volume_type: Literal["BUY", "SELL", "DEFAULT"],
|
|
249
|
+
):
|
|
250
|
+
"""Subscribe to volume-based kline data using VolumeKlineAggregator
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
symbol: Symbol to subscribe to
|
|
254
|
+
volume_threshold: Volume threshold for creating new klines
|
|
255
|
+
"""
|
|
256
|
+
# Ensure trade subscription for the symbol
|
|
257
|
+
self.subscribe_trade(symbol)
|
|
258
|
+
|
|
259
|
+
# Create and register volume kline aggregator
|
|
260
|
+
aggregator = self._create_volume_kline_aggregator(
|
|
261
|
+
symbol, volume_threshold, volume_type
|
|
262
|
+
)
|
|
263
|
+
self._add_aggregator(symbol, aggregator)
|
|
264
|
+
|
|
265
|
+
self._log.info(
|
|
266
|
+
"Volume kline aggregator created for %s with threshold %s and type %s",
|
|
267
|
+
symbol,
|
|
268
|
+
volume_threshold,
|
|
269
|
+
volume_type,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def unsubscribe_volume_kline_aggregator(
|
|
273
|
+
self, symbol: str, volume_threshold: float, volume_type: str
|
|
274
|
+
):
|
|
275
|
+
"""Unsubscribe from volume-based kline data using VolumeKlineAggregator
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
symbol: Symbol to unsubscribe from
|
|
279
|
+
volume_threshold: Volume threshold for the kline aggregator
|
|
280
|
+
"""
|
|
281
|
+
aggregators = self._aggregators.get(symbol, [])
|
|
282
|
+
# Create a copy to avoid "list changed size during iteration" error
|
|
283
|
+
for aggregator in aggregators.copy():
|
|
284
|
+
if (
|
|
285
|
+
isinstance(aggregator, VolumeKlineAggregator)
|
|
286
|
+
and aggregator.volume_threshold == volume_threshold
|
|
287
|
+
):
|
|
288
|
+
aggregators.remove(aggregator)
|
|
289
|
+
self._log.info(
|
|
290
|
+
"Volume kline aggregator stopped for %s with threshold %s and type %s",
|
|
291
|
+
symbol,
|
|
292
|
+
volume_threshold,
|
|
293
|
+
volume_type,
|
|
294
|
+
)
|
|
295
|
+
if not aggregators:
|
|
296
|
+
self._aggregators.pop(symbol, None)
|
|
297
|
+
self.unsubscribe_trade(symbol)
|
|
298
|
+
|
|
299
|
+
@abstractmethod
|
|
300
|
+
def subscribe_bookl2(self, symbol: str | List[str], level: BookLevel):
|
|
301
|
+
"""Subscribe to the bookl2 data"""
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
@abstractmethod
|
|
305
|
+
def unsubscribe_bookl2(self, symbol: str | List[str], level: BookLevel):
|
|
306
|
+
"""Unsubscribe from the bookl2 data"""
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
@abstractmethod
|
|
310
|
+
def subscribe_funding_rate(self, symbol: str | List[str]):
|
|
311
|
+
"""Subscribe to the funding rate data"""
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
@abstractmethod
|
|
315
|
+
def unsubscribe_funding_rate(self, symbol: str | List[str]):
|
|
316
|
+
"""Unsubscribe from the funding rate data"""
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
@abstractmethod
|
|
320
|
+
def subscribe_index_price(self, symbol: str | List[str]):
|
|
321
|
+
"""Subscribe to the index price data"""
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
@abstractmethod
|
|
325
|
+
def unsubscribe_index_price(self, symbol: str | List[str]):
|
|
326
|
+
"""Unsubscribe from the index price data"""
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
@abstractmethod
|
|
330
|
+
def subscribe_mark_price(self, symbol: str | List[str]):
|
|
331
|
+
"""Subscribe to the mark price data"""
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
@abstractmethod
|
|
335
|
+
def unsubscribe_mark_price(self, symbol: str | List[str]):
|
|
336
|
+
"""Unsubscribe from the mark price data"""
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
def _create_time_kline_aggregator(
|
|
340
|
+
self,
|
|
341
|
+
symbol: str,
|
|
342
|
+
interval: KlineInterval,
|
|
343
|
+
build_with_no_updates: bool,
|
|
344
|
+
) -> TimeKlineAggregator:
|
|
345
|
+
"""Create a time-based kline aggregator."""
|
|
346
|
+
return TimeKlineAggregator(
|
|
347
|
+
exchange=self._exchange_id,
|
|
348
|
+
symbol=symbol,
|
|
349
|
+
interval=interval,
|
|
350
|
+
msgbus=self._msgbus,
|
|
351
|
+
clock=self._clock,
|
|
352
|
+
build_with_no_updates=build_with_no_updates,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def _create_volume_kline_aggregator(
|
|
356
|
+
self,
|
|
357
|
+
symbol: str,
|
|
358
|
+
volume_threshold: float,
|
|
359
|
+
volume_type: Literal["BUY", "SELL", "DEFAULT"],
|
|
360
|
+
) -> VolumeKlineAggregator:
|
|
361
|
+
"""Create a volume-based kline aggregator."""
|
|
362
|
+
|
|
363
|
+
return VolumeKlineAggregator(
|
|
364
|
+
exchange=self._exchange_id,
|
|
365
|
+
symbol=symbol,
|
|
366
|
+
msgbus=self._msgbus,
|
|
367
|
+
volume_threshold=volume_threshold,
|
|
368
|
+
volume_type=volume_type,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def _add_aggregator(self, symbol: str, aggregator) -> None:
|
|
372
|
+
"""Add an aggregator for a symbol."""
|
|
373
|
+
if symbol not in self._aggregators:
|
|
374
|
+
self._aggregators[symbol] = []
|
|
375
|
+
self._aggregators[symbol].append(aggregator)
|
|
376
|
+
|
|
377
|
+
def _handle_trade_for_aggregators(self, trade: Trade) -> None:
|
|
378
|
+
"""Route trade data to all aggregators for this symbol."""
|
|
379
|
+
aggregators = self._aggregators.get(trade.symbol)
|
|
380
|
+
if aggregators:
|
|
381
|
+
for aggregator in aggregators:
|
|
382
|
+
aggregator.handle_trade(trade)
|
|
383
|
+
|
|
384
|
+
async def connect(self):
|
|
385
|
+
"""Connect to the exchange"""
|
|
386
|
+
await self._ws_client.connect()
|
|
387
|
+
|
|
388
|
+
async def wait_ready(self):
|
|
389
|
+
"""Wait for the initial WebSocket connection to be established"""
|
|
390
|
+
await self._ws_client.wait_ready()
|
|
391
|
+
|
|
392
|
+
async def disconnect(self):
|
|
393
|
+
"""Disconnect from the exchange"""
|
|
394
|
+
# Stop all aggregators
|
|
395
|
+
for aggregators in self._aggregators.values():
|
|
396
|
+
for aggregator in aggregators:
|
|
397
|
+
if hasattr(aggregator, "stop"):
|
|
398
|
+
aggregator.stop() # type: ignore
|
|
399
|
+
self._aggregators.clear()
|
|
400
|
+
|
|
401
|
+
# NOTE: no need to manually disconnect ws_client here
|
|
402
|
+
# self._ws_client.disconnect() # not needed to await
|
|
403
|
+
await self._api_client.close_session()
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class PrivateConnector(ABC):
|
|
407
|
+
def __init__(
|
|
408
|
+
self,
|
|
409
|
+
account_type: AccountType,
|
|
410
|
+
market: Mapping[str, BaseMarket],
|
|
411
|
+
api_client: ApiClient,
|
|
412
|
+
task_manager: TaskManager,
|
|
413
|
+
oms: OrderManagementSystem,
|
|
414
|
+
):
|
|
415
|
+
self._log = logging.getLogger(name=type(self).__name__)
|
|
416
|
+
self._account_type = account_type
|
|
417
|
+
self._market = market
|
|
418
|
+
self._api_client = api_client
|
|
419
|
+
self._oms = oms
|
|
420
|
+
self._task_manager = task_manager
|
|
421
|
+
self._api_proxy = ApiProxy(self._api_client, self._task_manager)
|
|
422
|
+
self._api_fire_proxy = FireApiProxy(self._api_client, self._task_manager)
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def account_type(self):
|
|
426
|
+
return self._account_type
|
|
427
|
+
|
|
428
|
+
@property
|
|
429
|
+
def api(self):
|
|
430
|
+
return self._api_proxy
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def api_fire(self):
|
|
434
|
+
return self._api_fire_proxy
|
|
435
|
+
|
|
436
|
+
@abstractmethod
|
|
437
|
+
async def connect(self):
|
|
438
|
+
"""Connect to the exchange"""
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
async def wait_ready(self):
|
|
442
|
+
"""Wait for the OMS WebSocket client(s) to be ready"""
|
|
443
|
+
await self._oms.wait_ready()
|
|
444
|
+
|
|
445
|
+
async def disconnect(self):
|
|
446
|
+
"""Disconnect from the exchange"""
|
|
447
|
+
# self._oms._ws_client.disconnect() # not needed to await
|
|
448
|
+
await self._api_client.close_session()
|