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.
Files changed (105) hide show
  1. walrasquant/__init__.py +7 -0
  2. walrasquant/aggregation.py +449 -0
  3. walrasquant/backends/__init__.py +5 -0
  4. walrasquant/backends/db.py +109 -0
  5. walrasquant/backends/db_memory.py +61 -0
  6. walrasquant/backends/db_postgresql.py +321 -0
  7. walrasquant/backends/db_sqlite.py +310 -0
  8. walrasquant/base/__init__.py +24 -0
  9. walrasquant/base/api_client.py +46 -0
  10. walrasquant/base/connector.py +863 -0
  11. walrasquant/base/ems.py +794 -0
  12. walrasquant/base/exchange.py +213 -0
  13. walrasquant/base/oms.py +428 -0
  14. walrasquant/base/retry.py +220 -0
  15. walrasquant/base/sms.py +545 -0
  16. walrasquant/base/ws_client.py +408 -0
  17. walrasquant/config.py +284 -0
  18. walrasquant/constants.py +413 -0
  19. walrasquant/core/__init__.py +0 -0
  20. walrasquant/core/cache.py +688 -0
  21. walrasquant/core/clock.py +59 -0
  22. walrasquant/core/connection.py +41 -0
  23. walrasquant/core/entity.py +504 -0
  24. walrasquant/core/nautilius_core.py +103 -0
  25. walrasquant/core/registry.py +41 -0
  26. walrasquant/engine.py +745 -0
  27. walrasquant/error.py +34 -0
  28. walrasquant/exchange/__init__.py +13 -0
  29. walrasquant/exchange/base_factory.py +172 -0
  30. walrasquant/exchange/binance/__init__.py +30 -0
  31. walrasquant/exchange/binance/connector.py +1093 -0
  32. walrasquant/exchange/binance/constants.py +934 -0
  33. walrasquant/exchange/binance/ems.py +140 -0
  34. walrasquant/exchange/binance/error.py +48 -0
  35. walrasquant/exchange/binance/exchange.py +144 -0
  36. walrasquant/exchange/binance/factory.py +115 -0
  37. walrasquant/exchange/binance/oms.py +1807 -0
  38. walrasquant/exchange/binance/rest_api.py +1653 -0
  39. walrasquant/exchange/binance/schema.py +1063 -0
  40. walrasquant/exchange/binance/websockets.py +389 -0
  41. walrasquant/exchange/bitget/__init__.py +28 -0
  42. walrasquant/exchange/bitget/connector.py +578 -0
  43. walrasquant/exchange/bitget/constants.py +392 -0
  44. walrasquant/exchange/bitget/ems.py +202 -0
  45. walrasquant/exchange/bitget/error.py +36 -0
  46. walrasquant/exchange/bitget/exchange.py +128 -0
  47. walrasquant/exchange/bitget/factory.py +135 -0
  48. walrasquant/exchange/bitget/oms.py +1619 -0
  49. walrasquant/exchange/bitget/rest_api.py +610 -0
  50. walrasquant/exchange/bitget/schema.py +885 -0
  51. walrasquant/exchange/bitget/websockets.py +753 -0
  52. walrasquant/exchange/bybit/__init__.py +32 -0
  53. walrasquant/exchange/bybit/connector.py +819 -0
  54. walrasquant/exchange/bybit/constants.py +479 -0
  55. walrasquant/exchange/bybit/ems.py +93 -0
  56. walrasquant/exchange/bybit/error.py +36 -0
  57. walrasquant/exchange/bybit/exchange.py +108 -0
  58. walrasquant/exchange/bybit/factory.py +128 -0
  59. walrasquant/exchange/bybit/oms.py +1195 -0
  60. walrasquant/exchange/bybit/rest_api.py +570 -0
  61. walrasquant/exchange/bybit/schema.py +867 -0
  62. walrasquant/exchange/bybit/websockets.py +307 -0
  63. walrasquant/exchange/hyperliquid/__init__.py +28 -0
  64. walrasquant/exchange/hyperliquid/connector.py +370 -0
  65. walrasquant/exchange/hyperliquid/constants.py +371 -0
  66. walrasquant/exchange/hyperliquid/ems.py +156 -0
  67. walrasquant/exchange/hyperliquid/error.py +48 -0
  68. walrasquant/exchange/hyperliquid/exchange.py +120 -0
  69. walrasquant/exchange/hyperliquid/factory.py +135 -0
  70. walrasquant/exchange/hyperliquid/oms.py +1081 -0
  71. walrasquant/exchange/hyperliquid/rest_api.py +348 -0
  72. walrasquant/exchange/hyperliquid/schema.py +583 -0
  73. walrasquant/exchange/hyperliquid/websockets.py +592 -0
  74. walrasquant/exchange/okx/__init__.py +25 -0
  75. walrasquant/exchange/okx/connector.py +931 -0
  76. walrasquant/exchange/okx/constants.py +518 -0
  77. walrasquant/exchange/okx/ems.py +144 -0
  78. walrasquant/exchange/okx/error.py +66 -0
  79. walrasquant/exchange/okx/exchange.py +102 -0
  80. walrasquant/exchange/okx/factory.py +138 -0
  81. walrasquant/exchange/okx/oms.py +1199 -0
  82. walrasquant/exchange/okx/rest_api.py +799 -0
  83. walrasquant/exchange/okx/schema.py +1449 -0
  84. walrasquant/exchange/okx/websockets.py +420 -0
  85. walrasquant/exchange/registry.py +201 -0
  86. walrasquant/execution/__init__.py +24 -0
  87. walrasquant/execution/algorithm.py +968 -0
  88. walrasquant/execution/algorithms/__init__.py +3 -0
  89. walrasquant/execution/algorithms/twap.py +392 -0
  90. walrasquant/execution/config.py +34 -0
  91. walrasquant/execution/constants.py +27 -0
  92. walrasquant/execution/schema.py +62 -0
  93. walrasquant/indicator.py +382 -0
  94. walrasquant/push.py +77 -0
  95. walrasquant/schema.py +755 -0
  96. walrasquant/strategy.py +1805 -0
  97. walrasquant/tools/__init__.py +0 -0
  98. walrasquant/tools/pm2_wrapper.py +1016 -0
  99. walrasquant/web/__init__.py +26 -0
  100. walrasquant/web/app.py +157 -0
  101. walrasquant/web/server.py +92 -0
  102. walrasquant_lib-0.4.20.dist-info/METADATA +162 -0
  103. walrasquant_lib-0.4.20.dist-info/RECORD +105 -0
  104. walrasquant_lib-0.4.20.dist-info/WHEEL +4 -0
  105. walrasquant_lib-0.4.20.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,140 @@
1
+ from decimal import Decimal
2
+ from typing import Dict, cast
3
+ from walrasquant.constants import AccountType
4
+ from walrasquant.schema import InstrumentId, BaseMarket
5
+ from walrasquant.core.cache import AsyncCache
6
+ from walrasquant.core.nautilius_core import MessageBus, LiveClock
7
+ from walrasquant.core.entity import TaskManager
8
+ from walrasquant.core.registry import OrderRegistry
9
+ from walrasquant.exchange.binance import BinanceAccountType
10
+ from walrasquant.exchange.binance.schema import BinanceMarket
11
+ from walrasquant.base import ExecutionManagementSystem
12
+ from walrasquant.base.ems import PriorityOrderQueue
13
+
14
+
15
+ class BinanceExecutionManagementSystem(ExecutionManagementSystem):
16
+ _market: Dict[str, BinanceMarket]
17
+ _BATCH_CHUNK_SIZE = 5
18
+
19
+ BINANCE_SPOT_PRIORITY = [
20
+ BinanceAccountType.ISOLATED_MARGIN,
21
+ BinanceAccountType.MARGIN,
22
+ BinanceAccountType.SPOT_TESTNET,
23
+ BinanceAccountType.SPOT,
24
+ ]
25
+
26
+ def __init__(
27
+ self,
28
+ market: Dict[str, BinanceMarket],
29
+ cache: AsyncCache,
30
+ msgbus: MessageBus,
31
+ clock: LiveClock,
32
+ task_manager: TaskManager,
33
+ registry: OrderRegistry,
34
+ queue_maxsize: int = 100_000,
35
+ ):
36
+ super().__init__(
37
+ market=market,
38
+ cache=cache,
39
+ msgbus=msgbus,
40
+ clock=clock,
41
+ task_manager=task_manager,
42
+ registry=registry,
43
+ queue_maxsize=queue_maxsize,
44
+ )
45
+ self._binance_spot_account_type: BinanceAccountType | None = None
46
+ self._binance_linear_account_type: BinanceAccountType | None = None
47
+ self._binance_inverse_account_type: BinanceAccountType | None = None
48
+ self._binance_pm_account_type: BinanceAccountType | None = None
49
+
50
+ def _set_account_type(self):
51
+ account_types = self._private_connectors.keys()
52
+
53
+ if BinanceAccountType.PORTFOLIO_MARGIN in account_types:
54
+ self._binance_pm_account_type = BinanceAccountType.PORTFOLIO_MARGIN
55
+ return
56
+
57
+ for account_type in self.BINANCE_SPOT_PRIORITY:
58
+ if account_type in account_types:
59
+ self._binance_spot_account_type = account_type
60
+ break
61
+
62
+ self._binance_linear_account_type = (
63
+ BinanceAccountType.USD_M_FUTURE_TESTNET
64
+ if BinanceAccountType.USD_M_FUTURE_TESTNET in account_types
65
+ else BinanceAccountType.USD_M_FUTURE
66
+ )
67
+
68
+ self._binance_inverse_account_type = (
69
+ BinanceAccountType.COIN_M_FUTURE_TESTNET
70
+ if BinanceAccountType.COIN_M_FUTURE_TESTNET in account_types
71
+ else BinanceAccountType.COIN_M_FUTURE
72
+ )
73
+
74
+ def _instrument_id_to_account_type(
75
+ self, instrument_id: InstrumentId
76
+ ) -> AccountType:
77
+ if self._binance_pm_account_type:
78
+ return self._binance_pm_account_type
79
+
80
+ if instrument_id.is_spot:
81
+ account_type = self._binance_spot_account_type
82
+ elif instrument_id.is_linear:
83
+ account_type = self._binance_linear_account_type
84
+ elif instrument_id.is_inverse:
85
+ account_type = self._binance_inverse_account_type
86
+ else:
87
+ raise ValueError(f"Unsupported instrument type: {instrument_id.type}")
88
+
89
+ if account_type is None:
90
+ raise ValueError(f"No Binance account type configured for {instrument_id}")
91
+ return account_type
92
+
93
+ def _build_order_submit_queues(self):
94
+ for account_type in self._private_connectors.keys():
95
+ if isinstance(account_type, BinanceAccountType):
96
+ self._order_submit_queues[account_type] = PriorityOrderQueue(
97
+ maxsize=self._queue_maxsize
98
+ )
99
+
100
+ def _get_min_order_amount(
101
+ self, symbol: str, market: BaseMarket, px: float
102
+ ) -> Decimal:
103
+ binance_market = cast(BinanceMarket, market)
104
+ # book = self._cache.bookl1(symbol)
105
+ cost_limits = binance_market.limits.cost
106
+ amount_limits = binance_market.limits.amount
107
+ cost_min = cost_limits.min if cost_limits is not None else 0.0
108
+ amount_min = amount_limits.min if amount_limits is not None else 0.0
109
+ cost_min = cost_min or 0.0
110
+ amount_min = amount_min or 0.0
111
+
112
+ min_order_amount = max(cost_min * 1.01 / px, amount_min)
113
+ min_order_amount = self._amount_to_precision(
114
+ symbol, min_order_amount, mode="ceil"
115
+ )
116
+ return min_order_amount
117
+
118
+ def _get_max_order_amount(
119
+ self, symbol: str, market: BaseMarket, is_market: bool, px: float
120
+ ) -> Decimal:
121
+ binance_market = cast(BinanceMarket, market)
122
+ cost = binance_market.limits.cost.max
123
+ _cost = (
124
+ self._amount_to_precision(symbol, cost / px, mode="floor")
125
+ if cost
126
+ else Decimal("Infinity")
127
+ )
128
+ amount = (
129
+ Decimal(str(binance_market.limits.amount.max))
130
+ if binance_market.limits.amount.max
131
+ else Decimal("Infinity")
132
+ )
133
+ if is_market:
134
+ mkt_max = (
135
+ Decimal(str(binance_market.limits.market.max))
136
+ if binance_market.limits.market and binance_market.limits.market.max
137
+ else Decimal("Infinity")
138
+ )
139
+ return min(_cost, amount, mkt_max)
140
+ return min(_cost, amount)
@@ -0,0 +1,48 @@
1
+ class BinanceClientError(Exception):
2
+ """
3
+ The base class for all Binance specific errors.
4
+ """
5
+
6
+ def __init__(self, code: int, message: str):
7
+ super().__init__(message)
8
+ self.code = code
9
+ self.message = message
10
+
11
+ def __repr__(self) -> str:
12
+ return f"{type(self).__name__}(code={self.code}, message='{self.message}')"
13
+
14
+ __str__ = __repr__
15
+
16
+
17
+ class BinanceServerError(Exception):
18
+ """
19
+ The base class for all Binance specific errors.
20
+ """
21
+
22
+ def __init__(self, code: int, message: str):
23
+ super().__init__(message)
24
+ self.code = code
25
+ self.message = message
26
+
27
+ def __repr__(self) -> str:
28
+ return f"{type(self).__name__}(code={self.code}, message='{self.message}')"
29
+
30
+ __str__ = __repr__
31
+
32
+
33
+ class BinanceRateLimitError(Exception):
34
+ """
35
+ Raised when Binance API rate limit is exceeded.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ message: str,
41
+ retry_after: float = 0.0,
42
+ api_type: str | None = None,
43
+ rate_limit_type: str | None = None,
44
+ ):
45
+ super().__init__(message)
46
+ self.retry_after = retry_after
47
+ self.api_type = api_type
48
+ self.rate_limit_type = rate_limit_type
@@ -0,0 +1,144 @@
1
+ import ccxt
2
+ import msgspec
3
+ from typing import Any, Dict, List
4
+ from walrasquant.base import ExchangeManager
5
+ from walrasquant.exchange.binance.schema import BinanceMarket
6
+ from walrasquant.exchange.binance.constants import BinanceAccountType
7
+ from walrasquant.schema import InstrumentId
8
+ from walrasquant.constants import AccountType, ConfigType
9
+ from walrasquant.error import EngineBuildError
10
+
11
+
12
+ class BinanceExchangeManager(ExchangeManager):
13
+ api: ccxt.binance
14
+ market: Dict[str, BinanceMarket]
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", "binance")
22
+ if config
23
+ else "binance",
24
+ "sandbox": config.get("sandbox", False) if config else False,
25
+ }
26
+ if config and "password" in config:
27
+ resolved_config["password"] = config["password"]
28
+ super().__init__(resolved_config)
29
+
30
+ def load_markets(self, reload: bool = False) -> None:
31
+ market = self.api.load_markets(reload=reload)
32
+ for symbol, mkt in market.items():
33
+ try:
34
+ mkt_json = msgspec.json.encode(mkt)
35
+ mkt = msgspec.json.decode(mkt_json, type=BinanceMarket)
36
+
37
+ if (
38
+ mkt.spot or mkt.linear or mkt.inverse or mkt.future
39
+ ) and not mkt.option:
40
+ symbol = self._parse_symbol(mkt, exchange_suffix="BINANCE")
41
+ mkt.symbol = symbol
42
+ self.market[symbol] = mkt
43
+ if mkt.type.value == "spot":
44
+ self.market_id[f"{mkt.id}_spot"] = symbol
45
+ elif mkt.linear:
46
+ self.market_id[f"{mkt.id}_linear"] = symbol
47
+ elif mkt.inverse:
48
+ self.market_id[f"{mkt.id}_inverse"] = symbol
49
+
50
+ except msgspec.ValidationError as ve:
51
+ self._log.warning(f"Symbol Format Error: {ve}, {symbol}, {mkt}")
52
+ continue
53
+
54
+ def option(
55
+ self,
56
+ base: str | None = None,
57
+ quote: str | None = None,
58
+ exclude: List[str] | None = None,
59
+ ) -> List[str]:
60
+ raise NotImplementedError("Option is not supported for Binance")
61
+
62
+ def validate_public_connector_config(
63
+ self, account_type: AccountType, basic_config: Any
64
+ ) -> None:
65
+ """Validate public connector configuration for Binance exchange"""
66
+ if not isinstance(account_type, BinanceAccountType):
67
+ raise EngineBuildError(
68
+ f"Expected BinanceAccountType, got {type(account_type)}"
69
+ )
70
+
71
+ if (
72
+ account_type.is_isolated_margin_or_margin
73
+ or account_type.is_portfolio_margin
74
+ ):
75
+ raise EngineBuildError(
76
+ f"{account_type} is not supported for public connector."
77
+ )
78
+
79
+ if basic_config.testnet != account_type.is_testnet:
80
+ raise EngineBuildError(
81
+ f"The `testnet` setting of Binance is not consistent with the public connector's account type `{account_type}`."
82
+ )
83
+
84
+ def validate_public_connector_limits(
85
+ self, existing_connectors: Dict[AccountType, Any]
86
+ ) -> None:
87
+ """Validate public connector limits for Binance exchange"""
88
+ # Binance has no specific connector limits
89
+ pass
90
+
91
+ def instrument_id_to_account_type(self, instrument_id: InstrumentId) -> AccountType:
92
+ """Convert an instrument ID to the appropriate account type for Binance exchange"""
93
+ if instrument_id.is_spot:
94
+ return (
95
+ BinanceAccountType.SPOT_TESTNET
96
+ if self.is_testnet
97
+ else BinanceAccountType.SPOT
98
+ )
99
+ elif instrument_id.is_linear:
100
+ return (
101
+ BinanceAccountType.USD_M_FUTURE_TESTNET
102
+ if self.is_testnet
103
+ else BinanceAccountType.USD_M_FUTURE
104
+ )
105
+ elif instrument_id.is_inverse:
106
+ return (
107
+ BinanceAccountType.COIN_M_FUTURE_TESTNET
108
+ if self.is_testnet
109
+ else BinanceAccountType.COIN_M_FUTURE
110
+ )
111
+ else:
112
+ raise ValueError(f"Unsupported instrument type: {instrument_id.type}")
113
+
114
+
115
+ def check():
116
+ bnc = BinanceExchangeManager()
117
+ market = bnc.market
118
+ market_id = bnc.market_id # noqa: F841
119
+
120
+ for symbol, mkt in market.items():
121
+ instrument_id = InstrumentId.from_str(symbol)
122
+ if mkt.subType:
123
+ assert instrument_id.type == mkt.subType
124
+ else:
125
+ assert instrument_id.type == mkt.type
126
+
127
+ if mkt.limits.market is None:
128
+ print(f"Market limits is None for {symbol} ({mkt.type})")
129
+
130
+ assert mkt.limits.amount is not None
131
+
132
+ if mkt.limits.cost.max is None:
133
+ print(f"Cost max limit is None for {symbol} ({mkt.type})")
134
+
135
+ if mkt.limits.amount.max is None:
136
+ print(f"Amount max limit is None for {symbol} ({mkt.type})")
137
+
138
+ assert mkt.limits.cost is not None
139
+
140
+ print("All checks passed")
141
+
142
+
143
+ if __name__ == "__main__":
144
+ check()
@@ -0,0 +1,115 @@
1
+ """
2
+ Binance 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.binance.exchange import BinanceExchangeManager
22
+ from walrasquant.exchange.binance.constants import BinanceAccountType
23
+ from walrasquant.exchange.binance.connector import (
24
+ BinancePublicConnector,
25
+ BinancePrivateConnector,
26
+ )
27
+ from walrasquant.exchange.binance.ems import BinanceExecutionManagementSystem
28
+ from walrasquant.exchange.binance.schema import BinanceMarket
29
+
30
+
31
+ class BinanceFactory(ExchangeFactory):
32
+ """Factory for creating Binance exchange components."""
33
+
34
+ @property
35
+ def exchange_type(self) -> ExchangeType:
36
+ return ExchangeType.BINANCE
37
+
38
+ def create_manager(self, basic_config: BasicConfig) -> ExchangeManager:
39
+ """Create BinanceExchangeManager."""
40
+ ccxt_config: ConfigType = {
41
+ "apiKey": basic_config.api_key or "",
42
+ "secret": basic_config.secret or "",
43
+ "exchange_id": "binance",
44
+ "sandbox": basic_config.testnet,
45
+ }
46
+ if basic_config.passphrase:
47
+ ccxt_config["password"] = basic_config.passphrase
48
+
49
+ return BinanceExchangeManager(ccxt_config)
50
+
51
+ def create_public_connector(
52
+ self,
53
+ config: PublicConnectorConfig,
54
+ exchange: ExchangeManager,
55
+ context: BuildContext,
56
+ ) -> PublicConnector:
57
+ """Create BinancePublicConnector."""
58
+ if not isinstance(config.account_type, BinanceAccountType):
59
+ raise TypeError("Binance public connector requires BinanceAccountType")
60
+ return BinancePublicConnector(
61
+ account_type=config.account_type,
62
+ exchange=cast(BinanceExchangeManager, 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 BinancePrivateConnector."""
80
+ # Use provided account_type or fall back to config
81
+ final_account_type = account_type or config.account_type
82
+ if not isinstance(final_account_type, BinanceAccountType):
83
+ raise TypeError("Binance private connector requires BinanceAccountType")
84
+
85
+ return BinancePrivateConnector(
86
+ exchange=cast(BinanceExchangeManager, exchange),
87
+ account_type=final_account_type,
88
+ cache=context.cache,
89
+ clock=context.clock,
90
+ msgbus=context.msgbus,
91
+ registry=context.registry,
92
+ enable_rate_limit=config.enable_rate_limit,
93
+ task_manager=context.task_manager,
94
+ max_retries=config.max_retries,
95
+ delay_initial_ms=config.delay_initial_ms,
96
+ delay_max_ms=config.delay_max_ms,
97
+ backoff_factor=config.backoff_factor,
98
+ max_subscriptions_per_client=config.max_subscriptions_per_client,
99
+ max_clients=config.max_clients,
100
+ order_query_config=context.order_query_config,
101
+ )
102
+
103
+ def create_ems(
104
+ self, exchange: ExchangeManager, context: BuildContext
105
+ ) -> ExecutionManagementSystem:
106
+ """Create BinanceExecutionManagementSystem."""
107
+ return BinanceExecutionManagementSystem(
108
+ market=cast(dict[str, BinanceMarket], exchange.market),
109
+ cache=context.cache,
110
+ msgbus=context.msgbus,
111
+ clock=context.clock,
112
+ task_manager=context.task_manager,
113
+ registry=context.registry,
114
+ queue_maxsize=context.queue_maxsize,
115
+ )