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,66 @@
1
+ from typing import Any, Mapping
2
+
3
+
4
+ class OkxHttpError(Exception):
5
+ """
6
+ Define the class for all OKX http specific errors.
7
+ """
8
+
9
+ def __init__(
10
+ self, status_code: int, message: str, headers: Mapping[str, Any]
11
+ ) -> None:
12
+ """
13
+ Define the base class for all OKX http specific errors.
14
+ """
15
+ super().__init__(message)
16
+ self.status = status_code
17
+ self.message = message
18
+ self.headers = headers
19
+
20
+
21
+ class OkxRequestError(Exception):
22
+ """
23
+ The base class for all OKX specific errors.
24
+
25
+ References
26
+ ----------
27
+ https://www.okx.com/docs-v5/en/?python#error-code
28
+
29
+ """
30
+
31
+ def __init__(self, error_code: int, status_code: int | None, message: str | None):
32
+ super().__init__(message)
33
+ self.code = error_code
34
+ self.status_code = status_code
35
+ self.message = message
36
+
37
+ def __repr__(self) -> str:
38
+ return f"{type(self).__name__}(code={self.code}, message='{self.message}')"
39
+
40
+ __str__ = __repr__
41
+
42
+
43
+ def retry_check(exc: BaseException) -> bool:
44
+ if isinstance(exc, OkxRequestError):
45
+ return exc.code in [50001, 50013, 50026, 51054, 51149, 51412]
46
+ return False
47
+
48
+
49
+ class OkxRateLimitError(Exception):
50
+ """
51
+ Raised when OKX API rate limit is exceeded.
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ message: str,
57
+ retry_after: float = 0.0,
58
+ scope: str | None = None,
59
+ endpoint: str | None = None,
60
+ code: int | None = None,
61
+ ):
62
+ super().__init__(message)
63
+ self.retry_after = retry_after
64
+ self.scope = scope
65
+ self.endpoint = endpoint
66
+ self.code = code
@@ -0,0 +1,102 @@
1
+ from typing import Any, Dict, List
2
+ from walrasquant.base import ExchangeManager
3
+ import ccxt
4
+ import msgspec
5
+ from walrasquant.config import BasicConfig
6
+ from walrasquant.exchange.okx.schema import OkxMarket
7
+ from walrasquant.exchange.okx.constants import OkxAccountType
8
+ from walrasquant.constants import AccountType, ConfigType
9
+ from walrasquant.schema import InstrumentId
10
+ from walrasquant.error import EngineBuildError
11
+
12
+
13
+ class OkxExchangeManager(ExchangeManager):
14
+ api: ccxt.okx
15
+ market: Dict[str, OkxMarket] # symbol -> okx market
16
+ market_id: Dict[str, str] # symbol -> exchange symbol id
17
+
18
+ def __init__(self, config: ConfigType | None = None):
19
+ resolved_config: ConfigType = {
20
+ "apiKey": config.get("apiKey", "") if config else "",
21
+ "secret": config.get("secret", "") if config else "",
22
+ "exchange_id": config.get("exchange_id", "okx") if config else "okx",
23
+ "sandbox": config.get("sandbox", False) if config else False,
24
+ }
25
+ if config and "password" in config:
26
+ resolved_config["password"] = config["password"]
27
+ super().__init__(resolved_config)
28
+ self.passphrase = resolved_config.get("password")
29
+ self._public_conn_account_type: OkxAccountType | None = None
30
+
31
+ def load_markets(self, reload: bool = False) -> None:
32
+ market = self.api.load_markets(reload=reload)
33
+ for symbol, mkt in market.items():
34
+ try:
35
+ mkt_json = msgspec.json.encode(mkt)
36
+ mkt = msgspec.json.decode(mkt_json, type=OkxMarket)
37
+
38
+ if (
39
+ mkt.spot or mkt.linear or mkt.inverse or mkt.future
40
+ ) and not mkt.option:
41
+ symbol = self._parse_symbol(mkt, exchange_suffix="OKX")
42
+ mkt.symbol = symbol
43
+ self.market[symbol] = mkt
44
+ self.market_id[mkt.id] = (
45
+ symbol # since okx symbol id is identical, no need to distinguish spot, linear, inverse
46
+ )
47
+ except msgspec.ValidationError as ve:
48
+ self._log.warning(f"Symbol Format Error: {ve}, {symbol}, {mkt}")
49
+ continue
50
+
51
+ def option(
52
+ self,
53
+ base: str | None = None,
54
+ quote: str | None = None,
55
+ exclude: List[str] | None = None,
56
+ ) -> List[str]:
57
+ raise NotImplementedError("Option is not supported for OKX")
58
+
59
+ def validate_public_connector_config(
60
+ self, account_type: AccountType, basic_config: BasicConfig | None = None
61
+ ) -> None:
62
+ """Validate public connector configuration for OKX exchange"""
63
+ if basic_config is None:
64
+ raise EngineBuildError("basic_config is required for OKX")
65
+ if not isinstance(account_type, OkxAccountType):
66
+ raise EngineBuildError(f"Expected OkxAccountType, got {type(account_type)}")
67
+
68
+ if basic_config.testnet != account_type.is_testnet:
69
+ raise EngineBuildError(
70
+ f"The `testnet` setting of OKX 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 OKX exchange"""
77
+ okx_connectors = [
78
+ c
79
+ for c in existing_connectors.values()
80
+ if hasattr(c, "account_type") and isinstance(c.account_type, OkxAccountType)
81
+ ]
82
+ if len(okx_connectors) > 1:
83
+ raise EngineBuildError(
84
+ "Only one public connector is supported for OKX, please remove the extra public connector config."
85
+ )
86
+
87
+ def set_public_connector_account_type(self, account_type: OkxAccountType) -> None:
88
+ """Set the account type for public connector configuration"""
89
+ self._public_conn_account_type = account_type
90
+
91
+ def instrument_id_to_account_type(self, instrument_id: InstrumentId) -> AccountType:
92
+ """Convert an instrument ID to the appropriate account type for OKX exchange"""
93
+ if self._public_conn_account_type is None:
94
+ raise EngineBuildError(
95
+ "Public connector account type not set for OKX. Please add OKX in public_conn_config."
96
+ )
97
+ return self._public_conn_account_type
98
+
99
+
100
+ if __name__ == "__main__":
101
+ okx = OkxExchangeManager()
102
+ # print(okx.market)
@@ -0,0 +1,138 @@
1
+ """
2
+ OKX 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.okx.exchange import OkxExchangeManager
22
+ from walrasquant.exchange.okx.connector import OkxPublicConnector, OkxPrivateConnector
23
+ from walrasquant.exchange.okx.ems import OkxExecutionManagementSystem
24
+ from walrasquant.exchange.okx.constants import OkxAccountType
25
+ from walrasquant.exchange.okx.schema import OkxMarket
26
+
27
+
28
+ class OkxFactory(ExchangeFactory):
29
+ """Factory for creating OKX exchange components."""
30
+
31
+ @property
32
+ def exchange_type(self) -> ExchangeType:
33
+ return ExchangeType.OKX
34
+
35
+ def create_manager(self, basic_config: BasicConfig) -> ExchangeManager:
36
+ """Create OkxExchangeManager."""
37
+ ccxt_config: ConfigType = {
38
+ "apiKey": basic_config.api_key or "",
39
+ "secret": basic_config.secret or "",
40
+ "exchange_id": "okx",
41
+ "sandbox": basic_config.testnet,
42
+ }
43
+ if basic_config.passphrase:
44
+ ccxt_config["password"] = basic_config.passphrase
45
+
46
+ return OkxExchangeManager(ccxt_config)
47
+
48
+ def create_public_connector(
49
+ self,
50
+ config: PublicConnectorConfig,
51
+ exchange: ExchangeManager,
52
+ context: BuildContext,
53
+ ) -> PublicConnector:
54
+ """Create OkxPublicConnector."""
55
+ if not isinstance(config.account_type, OkxAccountType):
56
+ raise TypeError("OKX public connector requires OkxAccountType")
57
+ connector = OkxPublicConnector(
58
+ account_type=config.account_type,
59
+ exchange=cast(OkxExchangeManager, exchange),
60
+ msgbus=context.msgbus,
61
+ clock=context.clock,
62
+ task_manager=context.task_manager,
63
+ enable_rate_limit=config.enable_rate_limit,
64
+ custom_url=config.custom_url,
65
+ max_subscriptions_per_client=config.max_subscriptions_per_client,
66
+ max_clients=config.max_clients,
67
+ )
68
+
69
+ # OKX needs post-creation setup
70
+ self.setup_public_connector(exchange, connector, config.account_type)
71
+ return connector
72
+
73
+ def create_private_connector(
74
+ self,
75
+ config: PrivateConnectorConfig,
76
+ exchange: ExchangeManager,
77
+ context: BuildContext,
78
+ account_type: AccountType | None = None,
79
+ ) -> PrivateConnector:
80
+ """Create OkxPrivateConnector."""
81
+ # Use provided account_type or determine from exchange testnet status
82
+ if account_type:
83
+ final_account_type = account_type
84
+ else:
85
+ final_account_type = self.get_private_account_type(exchange, config)
86
+
87
+ if not isinstance(final_account_type, OkxAccountType):
88
+ raise TypeError("OKX private connector requires OkxAccountType")
89
+ return OkxPrivateConnector(
90
+ exchange=cast(OkxExchangeManager, exchange),
91
+ account_type=final_account_type,
92
+ cache=context.cache,
93
+ clock=context.clock,
94
+ msgbus=context.msgbus,
95
+ registry=context.registry,
96
+ enable_rate_limit=config.enable_rate_limit,
97
+ task_manager=context.task_manager,
98
+ max_retries=config.max_retries,
99
+ delay_initial_ms=config.delay_initial_ms,
100
+ delay_max_ms=config.delay_max_ms,
101
+ backoff_factor=config.backoff_factor,
102
+ max_subscriptions_per_client=config.max_subscriptions_per_client,
103
+ max_clients=config.max_clients,
104
+ order_query_config=context.order_query_config,
105
+ )
106
+
107
+ def create_ems(
108
+ self, exchange: ExchangeManager, context: BuildContext
109
+ ) -> ExecutionManagementSystem:
110
+ """Create OkxExecutionManagementSystem."""
111
+ return OkxExecutionManagementSystem(
112
+ market=cast(dict[str, OkxMarket], exchange.market),
113
+ cache=context.cache,
114
+ msgbus=context.msgbus,
115
+ clock=context.clock,
116
+ task_manager=context.task_manager,
117
+ registry=context.registry,
118
+ queue_maxsize=context.queue_maxsize,
119
+ )
120
+
121
+ def get_private_account_type(
122
+ self, exchange: ExchangeManager, config: PrivateConnectorConfig
123
+ ) -> AccountType:
124
+ """Determine account type based on testnet status."""
125
+ return OkxAccountType.DEMO if exchange.is_testnet else OkxAccountType.LIVE
126
+
127
+ def setup_public_connector(
128
+ self,
129
+ exchange: ExchangeManager,
130
+ connector: PublicConnector,
131
+ account_type: AccountType,
132
+ ) -> None:
133
+ """Setup public connector account type."""
134
+ if not isinstance(account_type, OkxAccountType):
135
+ raise TypeError("OKX public connector requires OkxAccountType")
136
+ cast(OkxExchangeManager, exchange).set_public_connector_account_type(
137
+ account_type
138
+ )