walrasquant-lib 0.4.22__tar.gz → 0.4.24__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.22 → walrasquant_lib-0.4.24}/PKG-INFO +2 -2
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/pyproject.toml +2 -2
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/api_client.py +10 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/ems.py +52 -327
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/config.py +3 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/engine.py +12 -1
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/base_factory.py +1 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/connector.py +84 -51
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/ems.py +2 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/factory.py +1 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/schema.py +25 -10
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/ems.py +2 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/factory.py +1 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/connector.py +21 -12
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/ems.py +2 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/factory.py +1 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/oms.py +5 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/schema.py +12 -11
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/ems.py +2 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/factory.py +1 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/rest_api.py +3 -53
- walrasquant_lib-0.4.24/src/walrasquant/exchange/hyperliquid/signing.py +72 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/websockets.py +3 -53
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/connector.py +27 -18
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/ems.py +2 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/factory.py +1 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/schema.py +0 -2
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/schema.py +1 -1
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/README.md +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/aggregation.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/backends/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/backends/db.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/backends/db_memory.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/backends/db_postgresql.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/backends/db_sqlite.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/connector.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/exchange.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/oms.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/retry.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/sms.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/base/ws_client.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/constants.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/core/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/core/cache.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/core/clock.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/core/connection.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/core/entity.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/core/nautilius_core.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/core/registry.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/error.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/constants.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/error.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/exchange.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/oms.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/rest_api.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/binance/websockets.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/connector.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/constants.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/error.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/exchange.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/oms.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/rest_api.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/schema.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bitget/websockets.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/constants.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/error.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/exchange.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/rest_api.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/bybit/websockets.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/connector.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/constants.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/error.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/exchange.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/oms.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/hyperliquid/schema.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/constants.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/error.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/exchange.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/oms.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/rest_api.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/okx/websockets.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/exchange/registry.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/execution/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/execution/algorithm.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/execution/algorithms/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/execution/algorithms/twap.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/execution/config.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/execution/constants.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/execution/schema.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/indicator.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/push.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/strategy.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/tools/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/tools/pm2_wrapper.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/web/__init__.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/web/app.py +0 -0
- {walrasquant_lib-0.4.22 → walrasquant_lib-0.4.24}/src/walrasquant/web/server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: walrasquant-lib
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.24
|
|
4
4
|
Summary: fastest python trading bot
|
|
5
5
|
Author: River-Shi
|
|
6
6
|
Author-email: River-Shi <nachuan.shi.quant@gmail.com>
|
|
@@ -23,13 +23,13 @@ Requires-Dist: dynaconf[redis]>=3.2.12
|
|
|
23
23
|
Requires-Dist: aiohttp>=3.13.3
|
|
24
24
|
Requires-Dist: rich>=14.0.0
|
|
25
25
|
Requires-Dist: picows>=2.1.1
|
|
26
|
-
Requires-Dist: ccxt>=4.5.63
|
|
27
26
|
Requires-Dist: nexuscore-lib>=0.2.0
|
|
28
27
|
Requires-Dist: flashduty-lib>=0.2.0
|
|
29
28
|
Requires-Dist: nexuslog-lib>=0.5.0
|
|
30
29
|
Requires-Dist: msgspec>=0.21.1
|
|
31
30
|
Requires-Dist: pandas<3.0.0
|
|
32
31
|
Requires-Dist: numpy>=2.2.0
|
|
32
|
+
Requires-Dist: ccxt>=4.5.64
|
|
33
33
|
Requires-Python: >=3.11, <3.15
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "walrasquant-lib"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.24"
|
|
4
4
|
description = "fastest python trading bot"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "River-Shi",email = "nachuan.shi.quant@gmail.com"}
|
|
@@ -27,13 +27,13 @@ dependencies = [
|
|
|
27
27
|
"aiohttp>=3.13.3",
|
|
28
28
|
"rich>=14.0.0",
|
|
29
29
|
"picows>=2.1.1",
|
|
30
|
-
"ccxt>=4.5.63",
|
|
31
30
|
"nexuscore-lib>=0.2.0",
|
|
32
31
|
"flashduty-lib>=0.2.0",
|
|
33
32
|
"nexuslog-lib>=0.5.0",
|
|
34
33
|
"msgspec>=0.21.1",
|
|
35
34
|
"pandas<3.0.0",
|
|
36
35
|
"numpy>=2.2.0",
|
|
36
|
+
"ccxt>=4.5.64",
|
|
37
37
|
]
|
|
38
38
|
|
|
39
39
|
[project.scripts]
|
|
@@ -29,8 +29,18 @@ class ApiClient(ABC):
|
|
|
29
29
|
|
|
30
30
|
async def _init_session(self, base_url: str | None = None):
|
|
31
31
|
if self._session is None:
|
|
32
|
+
# The default TCPConnector drops idle connections after 15s and
|
|
33
|
+
# resolves DNS on a thread pool, so intermittent traders pay a
|
|
34
|
+
# fresh DNS + TCP + TLS handshake on the order path.
|
|
35
|
+
connector = aiohttp.TCPConnector(
|
|
36
|
+
limit=64,
|
|
37
|
+
ttl_dns_cache=300,
|
|
38
|
+
keepalive_timeout=60,
|
|
39
|
+
resolver=aiohttp.AsyncResolver(),
|
|
40
|
+
)
|
|
32
41
|
kwargs: Dict[str, Any] = {
|
|
33
42
|
"timeout": aiohttp.ClientTimeout(total=self._timeout),
|
|
43
|
+
"connector": connector,
|
|
34
44
|
}
|
|
35
45
|
if base_url:
|
|
36
46
|
kwargs["base_url"] = base_url
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import traceback
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import Any, Mapping, List, Dict, cast
|
|
4
|
+
from typing import Any, Mapping, List, Dict, cast, Tuple
|
|
5
5
|
from typing import Literal
|
|
6
6
|
from decimal import Decimal
|
|
7
7
|
import nexuslog as logging
|
|
@@ -39,7 +39,7 @@ from walrasquant.base.connector import PrivateConnector
|
|
|
39
39
|
_CREATE_SUBMIT_TYPES: frozenset = frozenset({SubmitType.CREATE, SubmitType.CREATE_WS})
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def _item_priority(item:
|
|
42
|
+
def _item_priority(item: Tuple[OrderSubmit, SubmitType]) -> int:
|
|
43
43
|
"""Return the scheduling priority for a queue item.
|
|
44
44
|
|
|
45
45
|
Priority scale (lower = higher priority):
|
|
@@ -89,9 +89,6 @@ class PriorityOrderQueue:
|
|
|
89
89
|
_, _, item = await self._queue.get()
|
|
90
90
|
return item
|
|
91
91
|
|
|
92
|
-
def task_done(self) -> None:
|
|
93
|
-
self._queue.task_done()
|
|
94
|
-
|
|
95
92
|
|
|
96
93
|
class ExecutionManagementSystem(ABC):
|
|
97
94
|
_BATCH_CHUNK_SIZE: int = 20
|
|
@@ -105,6 +102,7 @@ class ExecutionManagementSystem(ABC):
|
|
|
105
102
|
task_manager: TaskManager,
|
|
106
103
|
registry: OrderRegistry,
|
|
107
104
|
queue_maxsize: int = 100_000,
|
|
105
|
+
concurrency: int = 3,
|
|
108
106
|
):
|
|
109
107
|
self._log = logging.getLogger(name=type(self).__name__)
|
|
110
108
|
|
|
@@ -115,9 +113,10 @@ class ExecutionManagementSystem(ABC):
|
|
|
115
113
|
self._registry = registry
|
|
116
114
|
self._clock = clock
|
|
117
115
|
self._queue_maxsize = queue_maxsize
|
|
116
|
+
self._concurrency = concurrency
|
|
118
117
|
self._order_submit_queues: Dict[AccountType, PriorityOrderQueue] = {}
|
|
119
118
|
self._private_connectors: Dict[AccountType, PrivateConnector] = {}
|
|
120
|
-
self.
|
|
119
|
+
self._shutdown: bool = False
|
|
121
120
|
|
|
122
121
|
def _safe_put(self, queue: PriorityOrderQueue, item: Any) -> None:
|
|
123
122
|
# Order submits may originate off the event loop (e.g. threaded scheduled
|
|
@@ -138,15 +137,11 @@ class ExecutionManagementSystem(ABC):
|
|
|
138
137
|
|
|
139
138
|
def _do_put(self, queue: PriorityOrderQueue, item: Any) -> None:
|
|
140
139
|
# Always runs on the loop thread: queue access + bookkeeping are serialized.
|
|
141
|
-
|
|
140
|
+
if self._shutdown:
|
|
141
|
+
self._log.warning(f"EMS is shutting down; order submit rejected: {item}")
|
|
142
|
+
return
|
|
142
143
|
priority = _item_priority(item)
|
|
143
144
|
|
|
144
|
-
if submit_type in _CREATE_SUBMIT_TYPES:
|
|
145
|
-
self._pending_create_oids.add(order_submit.oid)
|
|
146
|
-
elif submit_type in (SubmitType.CANCEL, SubmitType.CANCEL_WS):
|
|
147
|
-
if order_submit.oid in self._pending_create_oids:
|
|
148
|
-
priority = 6 # demote: paired create must execute first
|
|
149
|
-
|
|
150
145
|
if queue.maxsize == 0 or queue.qsize() < queue.maxsize:
|
|
151
146
|
try:
|
|
152
147
|
queue.put_nowait(item, priority=priority)
|
|
@@ -323,7 +318,6 @@ class ExecutionManagementSystem(ABC):
|
|
|
323
318
|
Cancel an order
|
|
324
319
|
"""
|
|
325
320
|
# self._cache.mark_cancel_intent(order_submit.oid)
|
|
326
|
-
self._pending_create_oids.discard(order_submit.oid)
|
|
327
321
|
await self._private_connectors[account_type]._oms.cancel_order(
|
|
328
322
|
oid=order_submit.oid,
|
|
329
323
|
symbol=order_submit.symbol,
|
|
@@ -337,7 +331,6 @@ class ExecutionManagementSystem(ABC):
|
|
|
337
331
|
Cancel an order
|
|
338
332
|
"""
|
|
339
333
|
# self._cache.mark_cancel_intent(order_submit.oid)
|
|
340
|
-
self._pending_create_oids.discard(order_submit.oid)
|
|
341
334
|
await self._private_connectors[account_type]._oms.cancel_order_ws(
|
|
342
335
|
oid=order_submit.oid,
|
|
343
336
|
symbol=order_submit.symbol,
|
|
@@ -350,7 +343,6 @@ class ExecutionManagementSystem(ABC):
|
|
|
350
343
|
"""
|
|
351
344
|
Create an order
|
|
352
345
|
"""
|
|
353
|
-
self._pending_create_oids.discard(order_submit.oid)
|
|
354
346
|
self._registry.register_order(order_submit.oid)
|
|
355
347
|
self._cache.add_inflight_order(order_submit.symbol, order_submit.oid)
|
|
356
348
|
await self._private_connectors[account_type]._oms.create_order(
|
|
@@ -371,7 +363,6 @@ class ExecutionManagementSystem(ABC):
|
|
|
371
363
|
"""
|
|
372
364
|
Create an order
|
|
373
365
|
"""
|
|
374
|
-
self._pending_create_oids.discard(order_submit.oid)
|
|
375
366
|
self._registry.register_order(order_submit.oid)
|
|
376
367
|
self._cache.add_inflight_order(order_submit.symbol, order_submit.oid)
|
|
377
368
|
await self._private_connectors[account_type]._oms.create_order_ws(
|
|
@@ -404,293 +395,6 @@ class ExecutionManagementSystem(ABC):
|
|
|
404
395
|
"""
|
|
405
396
|
pass
|
|
406
397
|
|
|
407
|
-
# async def _auto_maker(self, order_submit: OrderSubmit, account_type: AccountType):
|
|
408
|
-
# """
|
|
409
|
-
# Auto maker order: always place the order at the best price
|
|
410
|
-
# """
|
|
411
|
-
# pass
|
|
412
|
-
|
|
413
|
-
# def _calculate_twap_orders(
|
|
414
|
-
# self,
|
|
415
|
-
# symbol: str,
|
|
416
|
-
# total_amount: Decimal,
|
|
417
|
-
# duration: float,
|
|
418
|
-
# wait: float,
|
|
419
|
-
# min_order_amount: Decimal,
|
|
420
|
-
# reduce_only: bool = False,
|
|
421
|
-
# ) -> Tuple[List[Decimal], float]:
|
|
422
|
-
# """
|
|
423
|
-
# Calculate the amount list and wait time for the twap order
|
|
424
|
-
|
|
425
|
-
# eg:
|
|
426
|
-
# amount_list = [10, 10, 10]
|
|
427
|
-
# wait = 10
|
|
428
|
-
# """
|
|
429
|
-
# amount_list = []
|
|
430
|
-
# if total_amount == 0 or total_amount < min_order_amount:
|
|
431
|
-
# if reduce_only and total_amount > 0:
|
|
432
|
-
# self._log.info(
|
|
433
|
-
# f"TWAP ORDER: {symbol} Total amount is less than min order amount: {total_amount} < {min_order_amount}, reduce_only: {reduce_only}"
|
|
434
|
-
# )
|
|
435
|
-
# return [total_amount], 0
|
|
436
|
-
# self._log.info(
|
|
437
|
-
# f"TWAP ORDER: {symbol} Total amount is less than min order amount: {total_amount} < {min_order_amount}"
|
|
438
|
-
# )
|
|
439
|
-
# return [], 0
|
|
440
|
-
|
|
441
|
-
# interval = duration // wait
|
|
442
|
-
# base_amount = float(total_amount) / interval
|
|
443
|
-
|
|
444
|
-
# base_amount = max(
|
|
445
|
-
# min_order_amount, self._amount_to_precision(symbol, base_amount)
|
|
446
|
-
# )
|
|
447
|
-
|
|
448
|
-
# interval = int(total_amount // base_amount)
|
|
449
|
-
# remaining = total_amount - interval * base_amount
|
|
450
|
-
|
|
451
|
-
# amount_list = [base_amount] * interval
|
|
452
|
-
|
|
453
|
-
# if remaining >= min_order_amount or (reduce_only and remaining > 0):
|
|
454
|
-
# amount_list.append(remaining)
|
|
455
|
-
# else:
|
|
456
|
-
# amount_list[-1] += remaining
|
|
457
|
-
|
|
458
|
-
# wait = duration / len(amount_list)
|
|
459
|
-
|
|
460
|
-
# self._log.info(f"TWAP ORDER: {symbol} Amount list: {amount_list}, Wait: {wait}")
|
|
461
|
-
|
|
462
|
-
# return amount_list, wait
|
|
463
|
-
|
|
464
|
-
# def _cal_limit_order_price(
|
|
465
|
-
# self, symbol: str, side: OrderSide, market: BaseMarket
|
|
466
|
-
# ) -> Decimal:
|
|
467
|
-
# """
|
|
468
|
-
# Calculate the limit order price
|
|
469
|
-
# """
|
|
470
|
-
# basis_point = market.precision.price
|
|
471
|
-
# book = self._cache.bookl1(symbol)
|
|
472
|
-
|
|
473
|
-
# if side.is_buy:
|
|
474
|
-
# # if the spread is greater than the basis point
|
|
475
|
-
# if book.spread > basis_point:
|
|
476
|
-
# price = book.ask - basis_point
|
|
477
|
-
# else:
|
|
478
|
-
# price = book.bid
|
|
479
|
-
# elif side.is_sell:
|
|
480
|
-
# # if the spread is greater than the basis point
|
|
481
|
-
# if book.spread > basis_point:
|
|
482
|
-
# price = book.bid + basis_point
|
|
483
|
-
# else:
|
|
484
|
-
# price = book.ask
|
|
485
|
-
# price = self._price_to_precision(symbol, price)
|
|
486
|
-
# self._log.debug(
|
|
487
|
-
# f"CALCULATE LIMIT ORDER PRICE: symbol: {symbol}, side: {side}, price: {price}, ask: {book.ask}, bid: {book.bid}"
|
|
488
|
-
# )
|
|
489
|
-
# return price
|
|
490
|
-
|
|
491
|
-
# NOTE: TWAP order related code is commented out for now
|
|
492
|
-
# async def _twap_order(
|
|
493
|
-
# self, order_submit: TWAPOrderSubmit, account_type: AccountType
|
|
494
|
-
# ):
|
|
495
|
-
# """
|
|
496
|
-
# Execute the twap order
|
|
497
|
-
# """
|
|
498
|
-
# symbol = order_submit.symbol
|
|
499
|
-
# instrument_id = order_submit.instrument_id
|
|
500
|
-
# side = order_submit.side
|
|
501
|
-
# market = self._market[symbol]
|
|
502
|
-
# position_side = order_submit.position_side
|
|
503
|
-
# kwargs = order_submit.kwargs
|
|
504
|
-
# twap_uuid = order_submit.uuid
|
|
505
|
-
# check_interval = order_submit.check_interval
|
|
506
|
-
# reduce_only = order_submit.kwargs.get("reduce_only", False)
|
|
507
|
-
|
|
508
|
-
# algo_order = AlgoOrder(
|
|
509
|
-
# symbol=symbol,
|
|
510
|
-
# uuid=twap_uuid,
|
|
511
|
-
# side=side,
|
|
512
|
-
# amount=order_submit.amount,
|
|
513
|
-
# duration=order_submit.duration,
|
|
514
|
-
# wait=order_submit.wait,
|
|
515
|
-
# status=AlgoOrderStatus.RUNNING,
|
|
516
|
-
# exchange=instrument_id.exchange,
|
|
517
|
-
# timestamp=self._clock.timestamp_ms(),
|
|
518
|
-
# position_side=position_side,
|
|
519
|
-
# )
|
|
520
|
-
|
|
521
|
-
# self._cache._order_initialized(algo_order)
|
|
522
|
-
|
|
523
|
-
# min_order_amount: Decimal = self._get_min_order_amount(symbol, market)
|
|
524
|
-
# amount_list, wait = self._calculate_twap_orders(
|
|
525
|
-
# symbol=symbol,
|
|
526
|
-
# total_amount=order_submit.amount,
|
|
527
|
-
# duration=order_submit.duration,
|
|
528
|
-
# wait=order_submit.wait,
|
|
529
|
-
# min_order_amount=min_order_amount,
|
|
530
|
-
# reduce_only=reduce_only,
|
|
531
|
-
# )
|
|
532
|
-
|
|
533
|
-
# order_id = None
|
|
534
|
-
# elapsed_time = 0
|
|
535
|
-
|
|
536
|
-
# try:
|
|
537
|
-
# while amount_list:
|
|
538
|
-
# if order_id:
|
|
539
|
-
# order = self._cache.get_order(order_id)
|
|
540
|
-
|
|
541
|
-
# is_opened = order.bind_optional(
|
|
542
|
-
# lambda order: order.is_opened
|
|
543
|
-
# ).value_or(False)
|
|
544
|
-
# on_flight = order.bind_optional(
|
|
545
|
-
# lambda order: order.on_flight
|
|
546
|
-
# ).value_or(False)
|
|
547
|
-
# is_closed = order.bind_optional(
|
|
548
|
-
# lambda order: order.is_closed
|
|
549
|
-
# ).value_or(False)
|
|
550
|
-
|
|
551
|
-
# # 检查现价单是否已成交,不然的话立刻下市价单成交 或者 把remaining amount加到下一个市价单上
|
|
552
|
-
# if is_opened and not on_flight:
|
|
553
|
-
# await self._cancel_order(
|
|
554
|
-
# order_submit=CancelOrderSubmit(
|
|
555
|
-
# symbol=symbol,
|
|
556
|
-
# instrument_id=instrument_id,
|
|
557
|
-
# submit_type=SubmitType.CANCEL,
|
|
558
|
-
# uuid=order_id,
|
|
559
|
-
# ),
|
|
560
|
-
# account_type=account_type,
|
|
561
|
-
# )
|
|
562
|
-
# elif is_closed:
|
|
563
|
-
# order_id = None
|
|
564
|
-
# remaining = order.unwrap().remaining
|
|
565
|
-
# if remaining >= min_order_amount or (
|
|
566
|
-
# reduce_only and remaining > 0
|
|
567
|
-
# ):
|
|
568
|
-
# order = await self._create_order(
|
|
569
|
-
# order_submit=CreateOrderSubmit(
|
|
570
|
-
# symbol=symbol,
|
|
571
|
-
# instrument_id=instrument_id,
|
|
572
|
-
# submit_type=SubmitType.CREATE,
|
|
573
|
-
# side=side,
|
|
574
|
-
# type=OrderType.MARKET,
|
|
575
|
-
# amount=remaining,
|
|
576
|
-
# position_side=position_side,
|
|
577
|
-
# time_in_force=TimeInForce.IOC,
|
|
578
|
-
# kwargs=kwargs,
|
|
579
|
-
# ),
|
|
580
|
-
# account_type=account_type,
|
|
581
|
-
# )
|
|
582
|
-
# if order.success:
|
|
583
|
-
# algo_order.orders.append(order.uuid)
|
|
584
|
-
# self._cache._order_status_update(algo_order)
|
|
585
|
-
# else:
|
|
586
|
-
# algo_order.status = AlgoOrderStatus.FAILED
|
|
587
|
-
# self._cache._order_status_update(algo_order)
|
|
588
|
-
# self._log.error(
|
|
589
|
-
# f"TWAP ORDER FAILED: symbol: {symbol}, side: {side}"
|
|
590
|
-
# )
|
|
591
|
-
# break
|
|
592
|
-
# else:
|
|
593
|
-
# if amount_list:
|
|
594
|
-
# amount_list[-1] += remaining
|
|
595
|
-
# await asyncio.sleep(check_interval)
|
|
596
|
-
# elapsed_time += check_interval
|
|
597
|
-
# else:
|
|
598
|
-
# price = self._cal_limit_order_price(
|
|
599
|
-
# symbol=symbol,
|
|
600
|
-
# side=side,
|
|
601
|
-
# market=market,
|
|
602
|
-
# )
|
|
603
|
-
# amount = amount_list.pop()
|
|
604
|
-
# if amount_list:
|
|
605
|
-
# order_submit = CreateOrderSubmit(
|
|
606
|
-
# symbol=symbol,
|
|
607
|
-
# instrument_id=instrument_id,
|
|
608
|
-
# submit_type=SubmitType.CREATE,
|
|
609
|
-
# type=OrderType.LIMIT,
|
|
610
|
-
# side=side,
|
|
611
|
-
# amount=amount,
|
|
612
|
-
# price=price,
|
|
613
|
-
# time_in_force=TimeInForce.GTC,
|
|
614
|
-
# position_side=position_side,
|
|
615
|
-
# kwargs=kwargs,
|
|
616
|
-
# )
|
|
617
|
-
# else:
|
|
618
|
-
# order_submit = CreateOrderSubmit(
|
|
619
|
-
# symbol=symbol,
|
|
620
|
-
# instrument_id=instrument_id,
|
|
621
|
-
# submit_type=SubmitType.CREATE,
|
|
622
|
-
# type=OrderType.MARKET,
|
|
623
|
-
# side=side,
|
|
624
|
-
# amount=amount,
|
|
625
|
-
# time_in_force=TimeInForce.IOC,
|
|
626
|
-
# position_side=position_side,
|
|
627
|
-
# kwargs=kwargs,
|
|
628
|
-
# )
|
|
629
|
-
# order = await self._create_order(order_submit, account_type)
|
|
630
|
-
# if order.success:
|
|
631
|
-
# order_id = order.uuid
|
|
632
|
-
# algo_order.orders.append(order_id)
|
|
633
|
-
# self._cache._order_status_update(algo_order)
|
|
634
|
-
# await asyncio.sleep(wait - elapsed_time)
|
|
635
|
-
# elapsed_time = 0
|
|
636
|
-
# else:
|
|
637
|
-
# algo_order.status = AlgoOrderStatus.FAILED
|
|
638
|
-
# self._cache._order_status_update(algo_order)
|
|
639
|
-
|
|
640
|
-
# self._log.error(
|
|
641
|
-
# f"TWAP ORDER FAILED: symbol: {symbol}, side: {side}, uuid: {twap_uuid}"
|
|
642
|
-
# )
|
|
643
|
-
# break
|
|
644
|
-
|
|
645
|
-
# algo_order.status = AlgoOrderStatus.FINISHED
|
|
646
|
-
# self._cache._order_status_update(algo_order)
|
|
647
|
-
|
|
648
|
-
# self._log.info(
|
|
649
|
-
# f"TWAP ORDER FINISHED: symbol: {symbol}, side: {side}, uuid: {twap_uuid}"
|
|
650
|
-
# )
|
|
651
|
-
# except asyncio.CancelledError:
|
|
652
|
-
# algo_order.status = AlgoOrderStatus.CANCELING
|
|
653
|
-
# self._cache._order_status_update(algo_order)
|
|
654
|
-
|
|
655
|
-
# open_orders = self._cache.get_open_orders(symbol=symbol)
|
|
656
|
-
# for uuid in open_orders.copy():
|
|
657
|
-
# await self._cancel_order(
|
|
658
|
-
# order_submit=CancelOrderSubmit(
|
|
659
|
-
# symbol=symbol,
|
|
660
|
-
# instrument_id=instrument_id,
|
|
661
|
-
# submit_type=SubmitType.CANCEL,
|
|
662
|
-
# uuid=uuid,
|
|
663
|
-
# ),
|
|
664
|
-
# account_type=account_type,
|
|
665
|
-
# )
|
|
666
|
-
|
|
667
|
-
# algo_order.status = AlgoOrderStatus.CANCELED
|
|
668
|
-
# self._cache._order_status_update(algo_order)
|
|
669
|
-
|
|
670
|
-
# self._log.info(
|
|
671
|
-
# f"TWAP ORDER CANCELLED: symbol: {symbol}, side: {side}, uuid: {twap_uuid}"
|
|
672
|
-
# )
|
|
673
|
-
|
|
674
|
-
# async def _create_twap_order(
|
|
675
|
-
# self, order_submit: TWAPOrderSubmit, account_type: AccountType
|
|
676
|
-
# ):
|
|
677
|
-
# """
|
|
678
|
-
# Create a twap order
|
|
679
|
-
# """
|
|
680
|
-
# uuid = order_submit.uuid
|
|
681
|
-
# self._task_manager.create_task(
|
|
682
|
-
# self._twap_order(order_submit, account_type), name=uuid
|
|
683
|
-
# )
|
|
684
|
-
|
|
685
|
-
# async def _cancel_twap_order(
|
|
686
|
-
# self, order_submit: CancelTWAPOrderSubmit, account_type: AccountType
|
|
687
|
-
# ):
|
|
688
|
-
# """
|
|
689
|
-
# Cancel a twap order
|
|
690
|
-
# """
|
|
691
|
-
# uuid = order_submit.uuid
|
|
692
|
-
# self._task_manager.cancel_task(uuid)
|
|
693
|
-
|
|
694
398
|
async def _create_tp_sl_order(
|
|
695
399
|
self, order_submit: TakeProfitAndStopLossOrderSubmit, account_type: AccountType
|
|
696
400
|
):
|
|
@@ -737,32 +441,47 @@ class ExecutionManagementSystem(ABC):
|
|
|
737
441
|
}
|
|
738
442
|
|
|
739
443
|
self._log.debug(f"Handling orders for account type: {account_type}")
|
|
444
|
+
# Each dequeued item runs as its own task so one in-flight REST round
|
|
445
|
+
# trip does not block the rest of the queue. The semaphore throttles
|
|
446
|
+
# the dequeue itself: excess items stay in the priority queue (where
|
|
447
|
+
# cancels can still jump ahead) instead of piling up as tasks parked
|
|
448
|
+
# on a FIFO semaphore. Rate-limit quota compliance stays with the
|
|
449
|
+
# per-exchange throttled-py limiters.
|
|
450
|
+
semaphore = asyncio.Semaphore(self._concurrency)
|
|
740
451
|
while True:
|
|
741
452
|
queue_item = await queue.get()
|
|
742
|
-
|
|
743
|
-
submit_type
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
self.
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
f"account_type={account_type}, submit_type={submit_type}, "
|
|
753
|
-
f"order_submit={order_submit}, queue_item={queue_item}\n"
|
|
754
|
-
f"Traceback: {traceback.format_exc()}"
|
|
453
|
+
await semaphore.acquire()
|
|
454
|
+
(order_submit, submit_type) = queue_item
|
|
455
|
+
self._log.debug(f"[ORDER SUBMIT]: {order_submit}")
|
|
456
|
+
self._task_manager.create_task(
|
|
457
|
+
self._run_submit(
|
|
458
|
+
submit_handlers[submit_type],
|
|
459
|
+
order_submit,
|
|
460
|
+
submit_type,
|
|
461
|
+
account_type,
|
|
462
|
+
semaphore,
|
|
755
463
|
)
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
async def _run_submit(
|
|
467
|
+
self,
|
|
468
|
+
handler: Any,
|
|
469
|
+
order_submit: Any,
|
|
470
|
+
submit_type: SubmitType,
|
|
471
|
+
account_type: AccountType,
|
|
472
|
+
semaphore: asyncio.Semaphore,
|
|
473
|
+
) -> None:
|
|
474
|
+
try:
|
|
475
|
+
await handler(order_submit, account_type)
|
|
476
|
+
except Exception:
|
|
477
|
+
self._log.error(
|
|
478
|
+
"Order submit handler failed, "
|
|
479
|
+
f"account_type={account_type}, submit_type={submit_type}, "
|
|
480
|
+
f"order_submit={order_submit}\n"
|
|
481
|
+
f"Traceback: {traceback.format_exc()}"
|
|
482
|
+
)
|
|
483
|
+
finally:
|
|
484
|
+
semaphore.release()
|
|
766
485
|
|
|
767
486
|
async def _create_batch_orders(
|
|
768
487
|
self, batch_orders: List[BatchOrderSubmit], account_type: AccountType
|
|
@@ -782,6 +501,12 @@ class ExecutionManagementSystem(ABC):
|
|
|
782
501
|
orders=orders
|
|
783
502
|
)
|
|
784
503
|
|
|
504
|
+
def shutdown(self) -> None:
|
|
505
|
+
# Stop accepting new order submits; the engine calls this before the
|
|
506
|
+
# final cancel-all sweep so late strategy callbacks cannot slip new
|
|
507
|
+
# orders past it.
|
|
508
|
+
self._shutdown = True
|
|
509
|
+
|
|
785
510
|
async def start(self):
|
|
786
511
|
"""
|
|
787
512
|
Start the order submit
|
|
@@ -135,6 +135,9 @@ class WebConfig:
|
|
|
135
135
|
class QueueConfig:
|
|
136
136
|
ems_maxsize: int = 100_000
|
|
137
137
|
sms_maxsize: int = 100_000
|
|
138
|
+
# Max concurrent in-flight order submits per account; 1 restores strictly
|
|
139
|
+
# serial submission.
|
|
140
|
+
ems_concurrency: int = 7
|
|
138
141
|
|
|
139
142
|
|
|
140
143
|
@dataclass
|
|
@@ -310,12 +310,13 @@ class Engine:
|
|
|
310
310
|
registry=self._registry,
|
|
311
311
|
order_query_config=self._config.order_query_config,
|
|
312
312
|
queue_maxsize=self._config.queue_config.ems_maxsize,
|
|
313
|
+
ems_concurrency=self._config.queue_config.ems_concurrency,
|
|
313
314
|
)
|
|
314
315
|
|
|
315
316
|
for exchange_id, exchange in self._exchanges.items():
|
|
316
317
|
# Check if there's a custom EMS for this exchange
|
|
317
318
|
if exchange_id in self._custom_ems:
|
|
318
|
-
|
|
319
|
+
ems = self._custom_ems[exchange_id](
|
|
319
320
|
market=exchange.market,
|
|
320
321
|
cache=self._cache,
|
|
321
322
|
msgbus=self._msgbus,
|
|
@@ -324,6 +325,10 @@ class Engine:
|
|
|
324
325
|
registry=self._registry,
|
|
325
326
|
queue_maxsize=self._config.queue_config.ems_maxsize,
|
|
326
327
|
)
|
|
328
|
+
# Set directly instead of via __init__: custom EMS classes
|
|
329
|
+
# may predate the concurrency parameter.
|
|
330
|
+
ems._concurrency = self._config.queue_config.ems_concurrency
|
|
331
|
+
self._ems[exchange_id] = ems
|
|
327
332
|
self._ems[exchange_id]._build(self._private_connectors)
|
|
328
333
|
continue
|
|
329
334
|
|
|
@@ -673,6 +678,12 @@ class Engine:
|
|
|
673
678
|
# Suppress expected shutdown exceptions
|
|
674
679
|
pass
|
|
675
680
|
|
|
681
|
+
# Refuse any further order submits: late WS callbacks can still fire
|
|
682
|
+
# until the connectors are torn down below, and a new order slipping in
|
|
683
|
+
# after the cancel-all sweep would survive it.
|
|
684
|
+
for ems in self._ems.values():
|
|
685
|
+
ems.shutdown()
|
|
686
|
+
|
|
676
687
|
# Cancel all open orders if configured
|
|
677
688
|
if self._config.exit_after_cancel:
|
|
678
689
|
await self._cancel_all_open_orders()
|