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,108 @@
|
|
|
1
|
+
import ccxt
|
|
2
|
+
import msgspec
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
from walrasquant.base import ExchangeManager
|
|
5
|
+
from walrasquant.exchange.bybit.schema import BybitMarket
|
|
6
|
+
from walrasquant.exchange.bybit.constants import BybitAccountType
|
|
7
|
+
from walrasquant.constants import AccountType, ConfigType
|
|
8
|
+
from walrasquant.schema import InstrumentId
|
|
9
|
+
from walrasquant.error import EngineBuildError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BybitExchangeManager(ExchangeManager):
|
|
13
|
+
api: ccxt.bybit
|
|
14
|
+
market: Dict[str, BybitMarket]
|
|
15
|
+
market_id: Dict[str, str]
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: ConfigType | None = None):
|
|
18
|
+
resolved_config: ConfigType = {
|
|
19
|
+
"apiKey": config.get("apiKey", "") if config else "",
|
|
20
|
+
"secret": config.get("secret", "") if config else "",
|
|
21
|
+
"exchange_id": config.get("exchange_id", "bybit") if config else "bybit",
|
|
22
|
+
"sandbox": config.get("sandbox", False) if config else False,
|
|
23
|
+
}
|
|
24
|
+
if config and "password" in config:
|
|
25
|
+
resolved_config["password"] = config["password"]
|
|
26
|
+
super().__init__(resolved_config)
|
|
27
|
+
|
|
28
|
+
def load_markets(self, reload: bool = False) -> None:
|
|
29
|
+
market = self.api.load_markets(reload=reload)
|
|
30
|
+
for symbol, mkt in market.items():
|
|
31
|
+
try:
|
|
32
|
+
mkt_json = msgspec.json.encode(mkt)
|
|
33
|
+
mkt = msgspec.json.decode(mkt_json, type=BybitMarket)
|
|
34
|
+
if mkt.spot or mkt.linear or mkt.inverse or mkt.future:
|
|
35
|
+
symbol = self._parse_symbol(mkt, exchange_suffix="BYBIT")
|
|
36
|
+
mkt.symbol = symbol
|
|
37
|
+
self.market[symbol] = mkt
|
|
38
|
+
if mkt.type.value == "spot":
|
|
39
|
+
self.market_id[f"{mkt.id}_spot"] = symbol
|
|
40
|
+
elif mkt.option:
|
|
41
|
+
self.market_id[f"{mkt.id}_option"] = symbol
|
|
42
|
+
elif mkt.linear:
|
|
43
|
+
self.market_id[f"{mkt.id}_linear"] = symbol
|
|
44
|
+
elif mkt.inverse:
|
|
45
|
+
self.market_id[f"{mkt.id}_inverse"] = symbol
|
|
46
|
+
|
|
47
|
+
except msgspec.ValidationError as ve:
|
|
48
|
+
self._log.warning(f"Symbol Format Error: {ve}, {symbol}, {mkt}")
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
def validate_public_connector_config(
|
|
52
|
+
self, account_type: AccountType, basic_config: Any
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Validate public connector configuration for Bybit exchange"""
|
|
55
|
+
if not isinstance(account_type, BybitAccountType):
|
|
56
|
+
raise EngineBuildError(
|
|
57
|
+
f"Expected BybitAccountType, got {type(account_type)}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
account_type == BybitAccountType.UNIFIED
|
|
62
|
+
or account_type == BybitAccountType.UNIFIED_TESTNET
|
|
63
|
+
):
|
|
64
|
+
raise EngineBuildError(
|
|
65
|
+
f"{account_type} is not supported for public connector."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if basic_config.testnet != account_type.is_testnet:
|
|
69
|
+
raise EngineBuildError(
|
|
70
|
+
f"The `testnet` setting of Bybit is not consistent with the public connector's account type `{account_type}`."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def validate_public_connector_limits(
|
|
74
|
+
self, existing_connectors: Dict[AccountType, Any]
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Validate public connector limits for Bybit exchange"""
|
|
77
|
+
# Bybit has no specific connector limits
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def instrument_id_to_account_type(self, instrument_id: InstrumentId) -> AccountType:
|
|
81
|
+
"""Convert an instrument ID to the appropriate account type for Bybit exchange"""
|
|
82
|
+
if instrument_id.is_spot:
|
|
83
|
+
return (
|
|
84
|
+
BybitAccountType.SPOT_TESTNET
|
|
85
|
+
if self.is_testnet
|
|
86
|
+
else BybitAccountType.SPOT
|
|
87
|
+
)
|
|
88
|
+
elif instrument_id.is_linear:
|
|
89
|
+
return (
|
|
90
|
+
BybitAccountType.LINEAR_TESTNET
|
|
91
|
+
if self.is_testnet
|
|
92
|
+
else BybitAccountType.LINEAR
|
|
93
|
+
)
|
|
94
|
+
elif instrument_id.is_inverse:
|
|
95
|
+
return (
|
|
96
|
+
BybitAccountType.INVERSE_TESTNET
|
|
97
|
+
if self.is_testnet
|
|
98
|
+
else BybitAccountType.INVERSE
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(f"Unsupported instrument type: {instrument_id.type}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
exchange = BybitExchangeManager()
|
|
106
|
+
exchange.load_markets()
|
|
107
|
+
# print(exchange.market)
|
|
108
|
+
# print(exchange.market_id)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bybit exchange factory implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
from walrasquant.constants import ExchangeType, AccountType, ConfigType
|
|
8
|
+
from walrasquant.config import (
|
|
9
|
+
PublicConnectorConfig,
|
|
10
|
+
PrivateConnectorConfig,
|
|
11
|
+
BasicConfig,
|
|
12
|
+
)
|
|
13
|
+
from walrasquant.exchange.base_factory import ExchangeFactory, BuildContext
|
|
14
|
+
from walrasquant.base import (
|
|
15
|
+
ExchangeManager,
|
|
16
|
+
PublicConnector,
|
|
17
|
+
PrivateConnector,
|
|
18
|
+
ExecutionManagementSystem,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from walrasquant.exchange.bybit.exchange import BybitExchangeManager
|
|
22
|
+
from walrasquant.exchange.bybit.connector import (
|
|
23
|
+
BybitPublicConnector,
|
|
24
|
+
BybitPrivateConnector,
|
|
25
|
+
)
|
|
26
|
+
from walrasquant.exchange.bybit.ems import BybitExecutionManagementSystem
|
|
27
|
+
from walrasquant.exchange.bybit.constants import BybitAccountType
|
|
28
|
+
from walrasquant.exchange.bybit.schema import BybitMarket
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BybitFactory(ExchangeFactory):
|
|
32
|
+
"""Factory for creating Bybit exchange components."""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def exchange_type(self) -> ExchangeType:
|
|
36
|
+
return ExchangeType.BYBIT
|
|
37
|
+
|
|
38
|
+
def create_manager(self, basic_config: BasicConfig) -> ExchangeManager:
|
|
39
|
+
"""Create BybitExchangeManager."""
|
|
40
|
+
ccxt_config: ConfigType = {
|
|
41
|
+
"apiKey": basic_config.api_key or "",
|
|
42
|
+
"secret": basic_config.secret or "",
|
|
43
|
+
"exchange_id": "bybit",
|
|
44
|
+
"sandbox": basic_config.testnet,
|
|
45
|
+
}
|
|
46
|
+
if basic_config.passphrase:
|
|
47
|
+
ccxt_config["password"] = basic_config.passphrase
|
|
48
|
+
|
|
49
|
+
return BybitExchangeManager(ccxt_config)
|
|
50
|
+
|
|
51
|
+
def create_public_connector(
|
|
52
|
+
self,
|
|
53
|
+
config: PublicConnectorConfig,
|
|
54
|
+
exchange: ExchangeManager,
|
|
55
|
+
context: BuildContext,
|
|
56
|
+
) -> PublicConnector:
|
|
57
|
+
"""Create BybitPublicConnector."""
|
|
58
|
+
if not isinstance(config.account_type, BybitAccountType):
|
|
59
|
+
raise TypeError("Bybit public connector requires BybitAccountType")
|
|
60
|
+
return BybitPublicConnector(
|
|
61
|
+
account_type=config.account_type,
|
|
62
|
+
exchange=cast(BybitExchangeManager, exchange),
|
|
63
|
+
msgbus=context.msgbus,
|
|
64
|
+
clock=context.clock,
|
|
65
|
+
task_manager=context.task_manager,
|
|
66
|
+
enable_rate_limit=config.enable_rate_limit,
|
|
67
|
+
custom_url=config.custom_url,
|
|
68
|
+
max_subscriptions_per_client=config.max_subscriptions_per_client,
|
|
69
|
+
max_clients=config.max_clients,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def create_private_connector(
|
|
73
|
+
self,
|
|
74
|
+
config: PrivateConnectorConfig,
|
|
75
|
+
exchange: ExchangeManager,
|
|
76
|
+
context: BuildContext,
|
|
77
|
+
account_type: AccountType | None = None,
|
|
78
|
+
) -> PrivateConnector:
|
|
79
|
+
"""Create BybitPrivateConnector."""
|
|
80
|
+
# Use provided account_type or determine from exchange testnet status
|
|
81
|
+
if account_type:
|
|
82
|
+
final_account_type = account_type
|
|
83
|
+
else:
|
|
84
|
+
final_account_type = self.get_private_account_type(exchange, config)
|
|
85
|
+
|
|
86
|
+
if not isinstance(final_account_type, BybitAccountType):
|
|
87
|
+
raise TypeError("Bybit private connector requires BybitAccountType")
|
|
88
|
+
return BybitPrivateConnector(
|
|
89
|
+
exchange=cast(BybitExchangeManager, exchange),
|
|
90
|
+
account_type=final_account_type,
|
|
91
|
+
cache=context.cache,
|
|
92
|
+
clock=context.clock,
|
|
93
|
+
msgbus=context.msgbus,
|
|
94
|
+
registry=context.registry,
|
|
95
|
+
enable_rate_limit=config.enable_rate_limit,
|
|
96
|
+
task_manager=context.task_manager,
|
|
97
|
+
max_retries=config.max_retries,
|
|
98
|
+
delay_initial_ms=config.delay_initial_ms,
|
|
99
|
+
delay_max_ms=config.delay_max_ms,
|
|
100
|
+
backoff_factor=config.backoff_factor,
|
|
101
|
+
max_subscriptions_per_client=config.max_subscriptions_per_client,
|
|
102
|
+
max_clients=config.max_clients,
|
|
103
|
+
order_query_config=context.order_query_config,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def create_ems(
|
|
107
|
+
self, exchange: ExchangeManager, context: BuildContext
|
|
108
|
+
) -> ExecutionManagementSystem:
|
|
109
|
+
"""Create BybitExecutionManagementSystem."""
|
|
110
|
+
return BybitExecutionManagementSystem(
|
|
111
|
+
market=cast(dict[str, BybitMarket], exchange.market),
|
|
112
|
+
cache=context.cache,
|
|
113
|
+
msgbus=context.msgbus,
|
|
114
|
+
clock=context.clock,
|
|
115
|
+
task_manager=context.task_manager,
|
|
116
|
+
registry=context.registry,
|
|
117
|
+
queue_maxsize=context.queue_maxsize,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def get_private_account_type(
|
|
121
|
+
self, exchange: ExchangeManager, config: PrivateConnectorConfig
|
|
122
|
+
) -> AccountType:
|
|
123
|
+
"""Determine account type based on testnet status."""
|
|
124
|
+
return (
|
|
125
|
+
BybitAccountType.UNIFIED_TESTNET
|
|
126
|
+
if exchange.is_testnet
|
|
127
|
+
else BybitAccountType.UNIFIED
|
|
128
|
+
)
|