cfquant 0.2.0__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.
- LTtx/__init__.py +2 -0
- LTtx/tx/Config.txt +8 -0
- LTtx/tx/LTtx_server.py +1177 -0
- LTtx/tx/__init__.py +5 -0
- LTtx/tx/tx.py +1935 -0
- cfquant/__init__.py +26 -0
- cfquant/account_routing.py +119 -0
- cfquant/channels.py +76 -0
- cfquant/cli.py +266 -0
- cfquant/client.py +365 -0
- cfquant/config.py +86 -0
- cfquant/hub.py +304 -0
- cfquant/logging_i18n.py +348 -0
- cfquant/normal_bridge.py +676 -0
- cfquant/pipe_bridge.py +249 -0
- cfquant/pipe_client.py +223 -0
- cfquant/pipe_hub.py +602 -0
- cfquant/pipe_transport.py +493 -0
- cfquant/protocol.py +238 -0
- cfquant/qmt_bridge.py +1081 -0
- cfquant/runtime_report.py +334 -0
- cfquant/tx.py +1898 -0
- cfquant/tx_trade_bridge.py +1905 -0
- cfquant/version.py +5 -0
- cfquant/xtconstant.py +262 -0
- cfquant/xtdata.py +462 -0
- cfquant/xttrader.py +955 -0
- cfquant/xttype.py +586 -0
- cfquant-0.2.0.dist-info/LICENSE +21 -0
- cfquant-0.2.0.dist-info/METADATA +144 -0
- cfquant-0.2.0.dist-info/RECORD +90 -0
- cfquant-0.2.0.dist-info/WHEEL +5 -0
- cfquant-0.2.0.dist-info/entry_points.txt +4 -0
- cfquant-0.2.0.dist-info/top_level.txt +6 -0
- cfquant_pipe_hub.py +45 -0
- cfquant_web_server.py +12137 -0
- qmt_scripts/CFQUANT.py +507 -0
- qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +830 -0
- qmt_scripts/CFQUANT_LITE.py +4928 -0
- qmt_scripts/CFQUANT_TRADE_LOWLAT.py +382 -0
- qmt_scripts/__init__.py +1 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SH.py +866 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SZ.py +866 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SH.py +5020 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SZ.py +5020 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SH.py +436 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SZ.py +436 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/readme.md +95 -0
- web_dashboard/__init__.py +1 -0
- web_dashboard/app.js +9847 -0
- web_dashboard/avatars/amber-pulse.svg +15 -0
- web_dashboard/avatars/copper-grid.svg +15 -0
- web_dashboard/avatars/market-blue.svg +15 -0
- web_dashboard/avatars/rose-circuit.svg +15 -0
- web_dashboard/avatars/signal-green.svg +15 -0
- web_dashboard/avatars/slate-wave.svg +15 -0
- web_dashboard/avatars/teal-orbit.svg +16 -0
- web_dashboard/avatars/violet-node.svg +15 -0
- web_dashboard/favicon.svg +5 -0
- web_dashboard/image/qmt_deploy/image-10.png +0 -0
- web_dashboard/image/qmt_deploy/image-11.png +0 -0
- web_dashboard/image/qmt_deploy/image-12.png +0 -0
- web_dashboard/image/qmt_deploy/image-13.png +0 -0
- web_dashboard/image/qmt_deploy/image-14.png +0 -0
- web_dashboard/image/qmt_deploy/image-15.png +0 -0
- web_dashboard/image/qmt_deploy/image-16.png +0 -0
- web_dashboard/image/qmt_deploy/image-17.png +0 -0
- web_dashboard/image/qmt_deploy/image-18.png +0 -0
- web_dashboard/image/qmt_deploy/image-19.png +0 -0
- web_dashboard/image/qmt_deploy/image-20.png +0 -0
- web_dashboard/image/qmt_deploy/image-21.png +0 -0
- web_dashboard/image/qmt_deploy/image-4.png +0 -0
- web_dashboard/image/qmt_deploy/image-5.png +0 -0
- web_dashboard/image/qmt_deploy/image-6.png +0 -0
- web_dashboard/image/qmt_deploy/image-7.png +0 -0
- web_dashboard/image/qmt_deploy/image-8.png +0 -0
- web_dashboard/image/qmt_deploy/image-9.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-1.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-2.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-3.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479325179.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479745262.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480178700.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480428759.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783489807217.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783490503491.png +0 -0
- web_dashboard/index.html +2317 -0
- web_dashboard/styles.css +7817 -0
- web_dashboard/vendor/mermaid.min.js +2029 -0
cfquant/xttrader.py
ADDED
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import itertools
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
import atexit
|
|
7
|
+
|
|
8
|
+
from .client import create_rpc_client
|
|
9
|
+
from .config import get_config
|
|
10
|
+
from .channels import channels_for_bridge, normalize_bridge_id
|
|
11
|
+
from .protocol import new_id
|
|
12
|
+
from . import xtconstant
|
|
13
|
+
from .xttype import (
|
|
14
|
+
XtAsset,
|
|
15
|
+
XtAccountStatus,
|
|
16
|
+
XtBankTransferResponse,
|
|
17
|
+
XtCancelOrderResponse,
|
|
18
|
+
XtCancelError,
|
|
19
|
+
XtOrder,
|
|
20
|
+
XtOrderError,
|
|
21
|
+
XtOrderResponse,
|
|
22
|
+
XtPosition,
|
|
23
|
+
XtSmtAppointmentResponse,
|
|
24
|
+
XtTrade,
|
|
25
|
+
filter_cancelable_orders,
|
|
26
|
+
to_objects,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_trade_client = None
|
|
31
|
+
_trade_client_lock = threading.Lock()
|
|
32
|
+
_account_bridge_cache = {}
|
|
33
|
+
_account_bridge_lock = threading.RLock()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_trade_client():
|
|
37
|
+
global _trade_client
|
|
38
|
+
|
|
39
|
+
with _trade_client_lock:
|
|
40
|
+
if _trade_client is None:
|
|
41
|
+
_trade_client = _new_trade_client()
|
|
42
|
+
return _trade_client
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def close_trade_client():
|
|
46
|
+
global _trade_client
|
|
47
|
+
|
|
48
|
+
with _trade_client_lock:
|
|
49
|
+
client = _trade_client
|
|
50
|
+
_trade_client = None
|
|
51
|
+
if client is not None:
|
|
52
|
+
try:
|
|
53
|
+
client.close()
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _trade_request(action, params=None, timeout=None):
|
|
59
|
+
return get_trade_client().request(action, params or {}, timeout=timeout)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _first_response_item(result):
|
|
63
|
+
if result is None:
|
|
64
|
+
return None
|
|
65
|
+
if isinstance(result, list):
|
|
66
|
+
return result[0] if result else None
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _truthy_param(value):
|
|
71
|
+
if isinstance(value, str):
|
|
72
|
+
return value.strip().lower() in ("1", "true", "yes", "y", "on")
|
|
73
|
+
return bool(value)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _account_type_value(account_type):
|
|
77
|
+
if account_type is None or account_type == "":
|
|
78
|
+
return xtconstant.SECURITY_ACCOUNT
|
|
79
|
+
if isinstance(account_type, str):
|
|
80
|
+
text = account_type.strip().upper()
|
|
81
|
+
if not text:
|
|
82
|
+
return xtconstant.SECURITY_ACCOUNT
|
|
83
|
+
if text.isdigit():
|
|
84
|
+
return int(text)
|
|
85
|
+
aliases = {
|
|
86
|
+
"SECURITY": xtconstant.SECURITY_ACCOUNT,
|
|
87
|
+
"SECURITY_ACCOUNT": xtconstant.SECURITY_ACCOUNT,
|
|
88
|
+
"STOCK_ACCOUNT": xtconstant.SECURITY_ACCOUNT,
|
|
89
|
+
"MARGIN": xtconstant.CREDIT_ACCOUNT,
|
|
90
|
+
"CREDIT_ACCOUNT": xtconstant.CREDIT_ACCOUNT,
|
|
91
|
+
}
|
|
92
|
+
if text in aliases:
|
|
93
|
+
return aliases[text]
|
|
94
|
+
for int_type, str_type in xtconstant.ACCOUNT_TYPE_DICT.items():
|
|
95
|
+
if text == str(str_type).upper():
|
|
96
|
+
return int_type
|
|
97
|
+
return account_type
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _attach_account_fields(value, account):
|
|
101
|
+
if value is None:
|
|
102
|
+
return None
|
|
103
|
+
if isinstance(value, list):
|
|
104
|
+
return [_attach_account_fields(item, account) for item in value]
|
|
105
|
+
if not hasattr(value, "__dict__"):
|
|
106
|
+
return value
|
|
107
|
+
payload = _account_payload(account)
|
|
108
|
+
account_id = str(payload.get("account_id") or "").strip()
|
|
109
|
+
if account_id:
|
|
110
|
+
setattr(value, "account_id", account_id)
|
|
111
|
+
elif getattr(value, "account_id", None) in (None, ""):
|
|
112
|
+
setattr(value, "account_id", "")
|
|
113
|
+
setattr(value, "account_type", _account_type_value(payload.get("account_type", xtconstant.SECURITY_ACCOUNT)))
|
|
114
|
+
return value
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _new_trade_client(client_id=None, bridge_id=None):
|
|
118
|
+
cfg = get_config()
|
|
119
|
+
bridge_id = normalize_bridge_id(bridge_id or cfg.get("bridge_id"))
|
|
120
|
+
trade_channel = channels_for_bridge(bridge_id)["trade"]
|
|
121
|
+
return create_rpc_client(
|
|
122
|
+
request_channel=trade_channel,
|
|
123
|
+
timeout=cfg["timeout"],
|
|
124
|
+
client_id=client_id or new_id("trade_client"),
|
|
125
|
+
bridge_id=bridge_id,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
atexit.register(close_trade_client)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class XtQuantTraderCallback(object):
|
|
133
|
+
def on_connected(self):
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
def on_disconnected(self):
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
def on_account_status(self, status):
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
def on_stock_asset(self, asset):
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
def on_stock_order(self, order):
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
def on_stock_trade(self, trade):
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
def on_stock_position(self, position):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
def on_order_error(self, order_error):
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
def on_cancel_error(self, cancel_error):
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
def on_order_stock_async_response(self, response):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
def on_cancel_order_stock_async_response(self, response):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def on_bank_transfer_async_response(self, response):
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
def on_ctp_internal_transfer_async_response(self, response):
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
def on_smt_appointment_async_response(self, response):
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class XtQuantTrader(object):
|
|
177
|
+
_seq = itertools.count(1)
|
|
178
|
+
_event_types = {
|
|
179
|
+
"on_connected": None,
|
|
180
|
+
"on_disconnected": None,
|
|
181
|
+
"on_account_status": XtAccountStatus,
|
|
182
|
+
"on_stock_asset": XtAsset,
|
|
183
|
+
"on_stock_order": XtOrder,
|
|
184
|
+
"on_stock_trade": XtTrade,
|
|
185
|
+
"on_stock_position": XtPosition,
|
|
186
|
+
"on_order_error": XtOrderError,
|
|
187
|
+
"on_cancel_error": XtCancelError,
|
|
188
|
+
"on_order_stock_async_response": XtOrderResponse,
|
|
189
|
+
"on_cancel_order_stock_async_response": XtCancelOrderResponse,
|
|
190
|
+
"on_bank_transfer_async_response": XtBankTransferResponse,
|
|
191
|
+
"on_ctp_internal_transfer_async_response": XtBankTransferResponse,
|
|
192
|
+
"on_smt_appointment_async_response": XtSmtAppointmentResponse,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
def __init__(self, path="", session_id=0, callback=None, account=None):
|
|
196
|
+
self.path = path
|
|
197
|
+
self.session_id = session_id
|
|
198
|
+
self.callback = callback or XtQuantTraderCallback()
|
|
199
|
+
self.account = account
|
|
200
|
+
self.account_id = _account_id(account)
|
|
201
|
+
self.bridge_id = normalize_bridge_id(_bridge_id_from_account(account) or get_config().get("bridge_id"))
|
|
202
|
+
self.client_id = new_id("trade_client_%s" % _safe_client_part(self.account_id))
|
|
203
|
+
self._client = None
|
|
204
|
+
self._clients = {}
|
|
205
|
+
self.connected = False
|
|
206
|
+
self._registered_events = set()
|
|
207
|
+
self._subscribed_accounts = {}
|
|
208
|
+
self.timeout = 0
|
|
209
|
+
self.relaxed_response_order_enabled = False
|
|
210
|
+
|
|
211
|
+
def start(self):
|
|
212
|
+
self._get_client().start()
|
|
213
|
+
self._register_trader_events()
|
|
214
|
+
if self.account is not None and _subscription_key(self.account) not in self._subscribed_accounts:
|
|
215
|
+
self.subscribe(self.account)
|
|
216
|
+
self.connected = True
|
|
217
|
+
|
|
218
|
+
def stop(self):
|
|
219
|
+
was_connected = self.connected
|
|
220
|
+
self.connected = False
|
|
221
|
+
for payload in list(self._subscribed_accounts.values()):
|
|
222
|
+
try:
|
|
223
|
+
self._trade_request("xttrader.unsubscribe", {
|
|
224
|
+
"account": dict(payload),
|
|
225
|
+
}, timeout=2)
|
|
226
|
+
except Exception:
|
|
227
|
+
pass
|
|
228
|
+
self._subscribed_accounts.clear()
|
|
229
|
+
clients = list(self._clients.values())
|
|
230
|
+
self._client = None
|
|
231
|
+
self._clients.clear()
|
|
232
|
+
for client in clients:
|
|
233
|
+
try:
|
|
234
|
+
client.close()
|
|
235
|
+
except Exception:
|
|
236
|
+
pass
|
|
237
|
+
if was_connected:
|
|
238
|
+
self._emit_noarg_callback("on_disconnected")
|
|
239
|
+
|
|
240
|
+
def connect(self):
|
|
241
|
+
try:
|
|
242
|
+
self.start()
|
|
243
|
+
self._trade_request("cfquant.ping", timeout=3)
|
|
244
|
+
self.connected = True
|
|
245
|
+
self._emit_noarg_callback("on_connected")
|
|
246
|
+
return 0
|
|
247
|
+
except Exception:
|
|
248
|
+
self.connected = False
|
|
249
|
+
return -1
|
|
250
|
+
|
|
251
|
+
def disconnect(self):
|
|
252
|
+
self.stop()
|
|
253
|
+
return 0
|
|
254
|
+
|
|
255
|
+
def register_callback(self, callback):
|
|
256
|
+
self.callback = callback
|
|
257
|
+
self._register_trader_events()
|
|
258
|
+
|
|
259
|
+
def subscribe(self, account):
|
|
260
|
+
account = self._resolve_account(account)
|
|
261
|
+
payload = _account_payload(account)
|
|
262
|
+
result = self._trade_request("xttrader.subscribe", {
|
|
263
|
+
"account": payload,
|
|
264
|
+
})
|
|
265
|
+
self._subscribed_accounts[_subscription_key_from_payload(payload)] = payload
|
|
266
|
+
return result
|
|
267
|
+
|
|
268
|
+
def unsubscribe(self, account):
|
|
269
|
+
account = self._resolve_account(account)
|
|
270
|
+
payload = _account_payload(account)
|
|
271
|
+
result = self._trade_request("xttrader.unsubscribe", {
|
|
272
|
+
"account": payload,
|
|
273
|
+
})
|
|
274
|
+
self._subscribed_accounts.pop(_subscription_key_from_payload(payload), None)
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
def set_timeout(self, timeout=0):
|
|
278
|
+
self.timeout = timeout
|
|
279
|
+
if timeout:
|
|
280
|
+
for client in self._clients.values():
|
|
281
|
+
client.timeout = float(timeout)
|
|
282
|
+
|
|
283
|
+
def set_relaxed_response_order_enabled(self, enabled):
|
|
284
|
+
self.relaxed_response_order_enabled = bool(enabled)
|
|
285
|
+
|
|
286
|
+
def sleep(self, time):
|
|
287
|
+
import time as _time
|
|
288
|
+
|
|
289
|
+
_time.sleep(time)
|
|
290
|
+
|
|
291
|
+
def common_op_sync_with_seq(self, seq, callable):
|
|
292
|
+
func = callable[0]
|
|
293
|
+
args = callable[1:]
|
|
294
|
+
return func(*args)
|
|
295
|
+
|
|
296
|
+
def common_op_async_with_seq(self, seq, callable, callback):
|
|
297
|
+
result = self.common_op_sync_with_seq(seq, callable)
|
|
298
|
+
if callable_callback(callback):
|
|
299
|
+
callback(result)
|
|
300
|
+
return seq
|
|
301
|
+
|
|
302
|
+
def order_stock(
|
|
303
|
+
self,
|
|
304
|
+
account,
|
|
305
|
+
stock_code,
|
|
306
|
+
order_type,
|
|
307
|
+
order_volume,
|
|
308
|
+
price_type,
|
|
309
|
+
price,
|
|
310
|
+
strategy_name="",
|
|
311
|
+
order_remark="",
|
|
312
|
+
):
|
|
313
|
+
result = self._trade_request("xttrader.order_stock", {
|
|
314
|
+
"account": _account_payload(account),
|
|
315
|
+
"stock_code": stock_code,
|
|
316
|
+
"order_type": order_type,
|
|
317
|
+
"order_volume": order_volume,
|
|
318
|
+
"price_type": price_type,
|
|
319
|
+
"price": price,
|
|
320
|
+
"strategy_name": strategy_name,
|
|
321
|
+
"order_remark": order_remark,
|
|
322
|
+
})
|
|
323
|
+
if isinstance(result, dict):
|
|
324
|
+
return result.get("order_id", -1)
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
def order_stock_async(self, account, stock_code, order_type, order_volume, price_type, price, strategy_name="", order_remark=""):
|
|
328
|
+
seq = next(self._seq)
|
|
329
|
+
self._trade_request("xttrader.order_stock_async", {
|
|
330
|
+
"account": _account_payload(account),
|
|
331
|
+
"stock_code": stock_code,
|
|
332
|
+
"order_type": order_type,
|
|
333
|
+
"order_volume": order_volume,
|
|
334
|
+
"price_type": price_type,
|
|
335
|
+
"price": price,
|
|
336
|
+
"strategy_name": strategy_name,
|
|
337
|
+
"order_remark": order_remark,
|
|
338
|
+
"seq": seq,
|
|
339
|
+
})
|
|
340
|
+
return seq
|
|
341
|
+
|
|
342
|
+
def cancel_order_stock(self, account, order_id):
|
|
343
|
+
result = self._trade_request("xttrader.cancel_order_stock", {
|
|
344
|
+
"account": _account_payload(account),
|
|
345
|
+
"order_id": order_id,
|
|
346
|
+
})
|
|
347
|
+
if isinstance(result, dict):
|
|
348
|
+
return result.get("cancel_result", -1)
|
|
349
|
+
return result
|
|
350
|
+
|
|
351
|
+
def cancel_order_stock_async(self, account, order_id):
|
|
352
|
+
seq = next(self._seq)
|
|
353
|
+
self._trade_request("xttrader.cancel_order_stock_async", {
|
|
354
|
+
"account": _account_payload(account),
|
|
355
|
+
"order_id": order_id,
|
|
356
|
+
"seq": seq,
|
|
357
|
+
})
|
|
358
|
+
return seq
|
|
359
|
+
|
|
360
|
+
def cancel_order_stock_sysid(self, account, market, sysid):
|
|
361
|
+
result = self._trade_request("xttrader.cancel_order_stock_sysid", {
|
|
362
|
+
"account": _account_payload(account),
|
|
363
|
+
"market": market,
|
|
364
|
+
"sysid": sysid,
|
|
365
|
+
})
|
|
366
|
+
if isinstance(result, dict):
|
|
367
|
+
return result.get("cancel_result", -1)
|
|
368
|
+
return result
|
|
369
|
+
|
|
370
|
+
def cancel_order_stock_sysid_async(self, account, market, sysid):
|
|
371
|
+
seq = next(self._seq)
|
|
372
|
+
self._trade_request("xttrader.cancel_order_stock_sysid_async", {
|
|
373
|
+
"account": _account_payload(account),
|
|
374
|
+
"market": market,
|
|
375
|
+
"sysid": sysid,
|
|
376
|
+
"seq": seq,
|
|
377
|
+
})
|
|
378
|
+
return seq
|
|
379
|
+
|
|
380
|
+
def query_stock_asset(self, account):
|
|
381
|
+
result = self._trade_request("xttrader.query_stock_asset", {
|
|
382
|
+
"account": _account_payload(account),
|
|
383
|
+
})
|
|
384
|
+
return _attach_account_fields(XtAsset.from_any(_first_response_item(result)), account)
|
|
385
|
+
|
|
386
|
+
def query_stock_asset_async(self, account, callback):
|
|
387
|
+
result = self.query_stock_asset(account)
|
|
388
|
+
if callable_callback(callback):
|
|
389
|
+
callback(result)
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
def query_stock_orders(self, account, cancelable_only=False):
|
|
393
|
+
cancelable_only = _truthy_param(cancelable_only)
|
|
394
|
+
result = self._trade_request("xttrader.query_stock_orders", {
|
|
395
|
+
"account": _account_payload(account),
|
|
396
|
+
"cancelable_only": cancelable_only,
|
|
397
|
+
})
|
|
398
|
+
orders = to_objects(result, XtOrder)
|
|
399
|
+
if cancelable_only:
|
|
400
|
+
orders = filter_cancelable_orders(orders)
|
|
401
|
+
return _attach_account_fields(orders, account)
|
|
402
|
+
|
|
403
|
+
def query_stock_orders_async(self, account, callback, cancelable_only=False):
|
|
404
|
+
seq = next(self._seq)
|
|
405
|
+
result = self.query_stock_orders(account, cancelable_only=cancelable_only)
|
|
406
|
+
if callable_callback(callback):
|
|
407
|
+
callback(result)
|
|
408
|
+
return seq
|
|
409
|
+
|
|
410
|
+
def query_stock_order(self, account, order_id):
|
|
411
|
+
orders = self.query_stock_orders(account) or []
|
|
412
|
+
target_order_id = str(order_id)
|
|
413
|
+
for order in orders:
|
|
414
|
+
if str(getattr(order, "order_id", "")) == target_order_id:
|
|
415
|
+
return order
|
|
416
|
+
if str(getattr(order, "m_strOrderSysID", "")) == target_order_id:
|
|
417
|
+
return order
|
|
418
|
+
if str(getattr(order, "order_sysid", "")) == target_order_id:
|
|
419
|
+
return order
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
def query_stock_trades(self, account):
|
|
423
|
+
result = self._trade_request("xttrader.query_stock_trades", {
|
|
424
|
+
"account": _account_payload(account),
|
|
425
|
+
})
|
|
426
|
+
return _attach_account_fields(to_objects(result, XtTrade), account)
|
|
427
|
+
|
|
428
|
+
def query_stock_trades_async(self, account, callback):
|
|
429
|
+
seq = next(self._seq)
|
|
430
|
+
result = self.query_stock_trades(account)
|
|
431
|
+
if callable_callback(callback):
|
|
432
|
+
callback(result)
|
|
433
|
+
return seq
|
|
434
|
+
|
|
435
|
+
def query_stock_positions(self, account):
|
|
436
|
+
result = self._trade_request("xttrader.query_stock_positions", {
|
|
437
|
+
"account": _account_payload(account),
|
|
438
|
+
})
|
|
439
|
+
return _attach_account_fields(to_objects(result, XtPosition), account)
|
|
440
|
+
|
|
441
|
+
def query_stock_positions_async(self, account, callback):
|
|
442
|
+
seq = next(self._seq)
|
|
443
|
+
result = self.query_stock_positions(account)
|
|
444
|
+
if callable_callback(callback):
|
|
445
|
+
callback(result)
|
|
446
|
+
return seq
|
|
447
|
+
|
|
448
|
+
def query_stock_position(self, account, stock_code):
|
|
449
|
+
positions = self.query_stock_positions(account) or []
|
|
450
|
+
for position in positions:
|
|
451
|
+
if getattr(position, "stock_code", None) == stock_code:
|
|
452
|
+
return position
|
|
453
|
+
qmt_code = "%s.%s" % (getattr(position, "m_strInstrumentID", ""), getattr(position, "m_strExchangeID", ""))
|
|
454
|
+
if qmt_code == stock_code:
|
|
455
|
+
return position
|
|
456
|
+
return None
|
|
457
|
+
|
|
458
|
+
def query_account_info(self):
|
|
459
|
+
return self._compat_request("query_account_info")
|
|
460
|
+
|
|
461
|
+
def query_account_infos(self):
|
|
462
|
+
return self._compat_request("query_account_infos")
|
|
463
|
+
|
|
464
|
+
def query_account_infos_async(self, callback):
|
|
465
|
+
return self._async_compat_request("query_account_infos", {}, callback)
|
|
466
|
+
|
|
467
|
+
def query_account_status(self):
|
|
468
|
+
return self._compat_request("query_account_status")
|
|
469
|
+
|
|
470
|
+
def query_account_status_async(self, callback):
|
|
471
|
+
return self._async_compat_request("query_account_status", {}, callback)
|
|
472
|
+
|
|
473
|
+
def query_com_fund(self, account):
|
|
474
|
+
return self._compat_account_request("query_com_fund", account)
|
|
475
|
+
|
|
476
|
+
def query_com_position(self, account):
|
|
477
|
+
return self._compat_account_request("query_com_position", account)
|
|
478
|
+
|
|
479
|
+
def query_position_statistics(self, account):
|
|
480
|
+
return self._compat_account_request("query_position_statistics", account)
|
|
481
|
+
|
|
482
|
+
def query_secu_account(self, account):
|
|
483
|
+
return self._compat_account_request("query_secu_account", account)
|
|
484
|
+
|
|
485
|
+
def query_credit_detail(self, account):
|
|
486
|
+
return self._compat_account_request("query_credit_detail", account)
|
|
487
|
+
|
|
488
|
+
def query_credit_detail_async(self, account, callback):
|
|
489
|
+
return self._async_compat_request("query_credit_detail", self._account_params(account), callback)
|
|
490
|
+
|
|
491
|
+
def query_credit_subjects(self, account):
|
|
492
|
+
return self._compat_account_request("query_credit_subjects", account)
|
|
493
|
+
|
|
494
|
+
def query_credit_subjects_async(self, account, callback):
|
|
495
|
+
return self._async_compat_request("query_credit_subjects", self._account_params(account), callback)
|
|
496
|
+
|
|
497
|
+
def query_credit_slo_code(self, account):
|
|
498
|
+
return self._compat_account_request("query_credit_slo_code", account)
|
|
499
|
+
|
|
500
|
+
def query_credit_slo_code_async(self, account, callback):
|
|
501
|
+
return self._async_compat_request("query_credit_slo_code", self._account_params(account), callback)
|
|
502
|
+
|
|
503
|
+
def query_credit_assure(self, account):
|
|
504
|
+
return self._compat_account_request("query_credit_assure", account)
|
|
505
|
+
|
|
506
|
+
def query_credit_assure_async(self, account, callback):
|
|
507
|
+
return self._async_compat_request("query_credit_assure", self._account_params(account), callback)
|
|
508
|
+
|
|
509
|
+
def query_stk_compacts(self, account):
|
|
510
|
+
return self._compat_account_request("query_stk_compacts", account)
|
|
511
|
+
|
|
512
|
+
def query_stk_compacts_async(self, account, callback):
|
|
513
|
+
return self._async_compat_request("query_stk_compacts", self._account_params(account), callback)
|
|
514
|
+
|
|
515
|
+
def query_ipo_data(self):
|
|
516
|
+
return self._compat_request("query_ipo_data")
|
|
517
|
+
|
|
518
|
+
def query_ipo_data_async(self, callback):
|
|
519
|
+
return self._async_compat_request("query_ipo_data", {}, callback)
|
|
520
|
+
|
|
521
|
+
def query_new_purchase_limit(self, account):
|
|
522
|
+
return self._compat_account_request("query_new_purchase_limit", account)
|
|
523
|
+
|
|
524
|
+
def query_new_purchase_limit_async(self, account, callback):
|
|
525
|
+
return self._async_compat_request("query_new_purchase_limit", self._account_params(account), callback)
|
|
526
|
+
|
|
527
|
+
def query_bank_info(self, account):
|
|
528
|
+
return self._compat_account_request("query_bank_info", account)
|
|
529
|
+
|
|
530
|
+
def query_bank_amount(self, account, bank_no, bank_account, bank_pwd):
|
|
531
|
+
return self._compat_account_request("query_bank_amount", account, [bank_no, bank_account, bank_pwd])
|
|
532
|
+
|
|
533
|
+
def query_bank_transfer_stream(self, account, start_date, end_date, bank_no="", bank_account=""):
|
|
534
|
+
return self._compat_account_request("query_bank_transfer_stream", account, [start_date, end_date, bank_no, bank_account])
|
|
535
|
+
|
|
536
|
+
def bank_transfer_in(self, account, bank_no, bank_account, balance, bank_pwd="", fund_pwd=""):
|
|
537
|
+
return self._compat_account_request("bank_transfer_in", account, [bank_no, bank_account, balance, bank_pwd, fund_pwd])
|
|
538
|
+
|
|
539
|
+
def bank_transfer_in_async(self, account, bank_no, bank_account, balance, bank_pwd="", fund_pwd=""):
|
|
540
|
+
return self._async_compat_request(
|
|
541
|
+
"bank_transfer_in",
|
|
542
|
+
self._account_params(account, [bank_no, bank_account, balance, bank_pwd, fund_pwd]),
|
|
543
|
+
self._object_callback("on_bank_transfer_async_response", XtBankTransferResponse),
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
def bank_transfer_out(self, account, bank_no, bank_account, balance, bank_pwd="", fund_pwd=""):
|
|
547
|
+
return self._compat_account_request("bank_transfer_out", account, [bank_no, bank_account, balance, bank_pwd, fund_pwd])
|
|
548
|
+
|
|
549
|
+
def bank_transfer_out_async(self, account, bank_no, bank_account, balance, bank_pwd="", fund_pwd=""):
|
|
550
|
+
return self._async_compat_request(
|
|
551
|
+
"bank_transfer_out",
|
|
552
|
+
self._account_params(account, [bank_no, bank_account, balance, bank_pwd, fund_pwd]),
|
|
553
|
+
self._object_callback("on_bank_transfer_async_response", XtBankTransferResponse),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
def fund_transfer(self, account, transfer_direction, price):
|
|
557
|
+
return self._compat_account_request("fund_transfer", account, [transfer_direction, price])
|
|
558
|
+
|
|
559
|
+
def secu_transfer(self, account, transfer_direction, stock_code, volume, transfer_type):
|
|
560
|
+
return self._compat_account_request("secu_transfer", account, [transfer_direction, stock_code, volume, transfer_type])
|
|
561
|
+
|
|
562
|
+
def ctp_transfer_future_to_option(self, opt_account_id, ft_account_id, balance):
|
|
563
|
+
return self._compat_request("ctp_transfer_future_to_option", {"args": [opt_account_id, ft_account_id, balance]})
|
|
564
|
+
|
|
565
|
+
def ctp_transfer_future_to_option_async(self, opt_account_id, ft_account_id, balance):
|
|
566
|
+
return self._async_compat_request(
|
|
567
|
+
"ctp_transfer_future_to_option",
|
|
568
|
+
{"args": [opt_account_id, ft_account_id, balance]},
|
|
569
|
+
self._object_callback("on_ctp_internal_transfer_async_response", XtBankTransferResponse),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
def ctp_transfer_option_to_future(self, opt_account_id, ft_account_id, balance):
|
|
573
|
+
return self._compat_request("ctp_transfer_option_to_future", {"args": [opt_account_id, ft_account_id, balance]})
|
|
574
|
+
|
|
575
|
+
def ctp_transfer_option_to_future_async(self, opt_account_id, ft_account_id, balance):
|
|
576
|
+
return self._async_compat_request(
|
|
577
|
+
"ctp_transfer_option_to_future",
|
|
578
|
+
{"args": [opt_account_id, ft_account_id, balance]},
|
|
579
|
+
self._object_callback("on_ctp_internal_transfer_async_response", XtBankTransferResponse),
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
def query_data(self, account, result_path, data_type, start_time=None, end_time=None, user_param={}):
|
|
583
|
+
return self._compat_account_request("query_data", account, [result_path, data_type, start_time, end_time, user_param])
|
|
584
|
+
|
|
585
|
+
def export_data(self, account, result_path, data_type, start_time=None, end_time=None, user_param={}):
|
|
586
|
+
return self._compat_account_request("export_data", account, [result_path, data_type, start_time, end_time, user_param])
|
|
587
|
+
|
|
588
|
+
def sync_transaction_from_external(self, operation, data_type, account, deal_list):
|
|
589
|
+
return self._compat_account_request("sync_transaction_from_external", account, [operation, data_type, deal_list])
|
|
590
|
+
|
|
591
|
+
def smt_query_compact(self, account):
|
|
592
|
+
return self._compat_account_request("smt_query_compact", account)
|
|
593
|
+
|
|
594
|
+
def smt_query_order(self, account):
|
|
595
|
+
return self._compat_account_request("smt_query_order", account)
|
|
596
|
+
|
|
597
|
+
def smt_query_quoter(self, account):
|
|
598
|
+
return self._compat_account_request("smt_query_quoter", account)
|
|
599
|
+
|
|
600
|
+
def smt_appointment_order_async(self, account, order_code, date, amount, apply_rate):
|
|
601
|
+
return self._async_compat_request(
|
|
602
|
+
"smt_appointment_order",
|
|
603
|
+
self._account_params(account, [order_code, date, amount, apply_rate]),
|
|
604
|
+
self._object_callback("on_smt_appointment_async_response", XtSmtAppointmentResponse),
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
def smt_appointment_cancel_async(self, account, apply_id):
|
|
608
|
+
return self._async_compat_request(
|
|
609
|
+
"smt_appointment_cancel",
|
|
610
|
+
self._account_params(account, [apply_id]),
|
|
611
|
+
self._object_callback("on_smt_appointment_async_response", XtSmtAppointmentResponse),
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
def smt_negotiate_order_async(self, account, src_group_id, order_code, date, amount, apply_rate, dict_param={}):
|
|
615
|
+
return self._async_compat_request(
|
|
616
|
+
"smt_negotiate_order",
|
|
617
|
+
self._account_params(account, [src_group_id, order_code, date, amount, apply_rate, dict_param]),
|
|
618
|
+
self._object_callback("on_smt_appointment_async_response", XtSmtAppointmentResponse),
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
def smt_compact_return_async(self, account, src_group_id, cash_compact_id, order_code, occur_amount):
|
|
622
|
+
return self._async_compat_request(
|
|
623
|
+
"smt_compact_return",
|
|
624
|
+
self._account_params(account, [src_group_id, cash_compact_id, order_code, occur_amount]),
|
|
625
|
+
self._object_callback("on_smt_appointment_async_response", XtSmtAppointmentResponse),
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
def smt_compact_renewal_async(self, account, cash_compact_id, order_code, defer_days, defer_num, apply_rate):
|
|
629
|
+
return self._async_compat_request(
|
|
630
|
+
"smt_compact_renewal",
|
|
631
|
+
self._account_params(account, [cash_compact_id, order_code, defer_days, defer_num, apply_rate]),
|
|
632
|
+
self._object_callback("on_smt_appointment_async_response", XtSmtAppointmentResponse),
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
def run_forever(self):
|
|
636
|
+
import time
|
|
637
|
+
|
|
638
|
+
self.start()
|
|
639
|
+
while True:
|
|
640
|
+
time.sleep(1)
|
|
641
|
+
|
|
642
|
+
def _register_trader_events(self, bridge_id=None):
|
|
643
|
+
bridge_id = normalize_bridge_id(bridge_id or self.bridge_id)
|
|
644
|
+
client = self._get_client(bridge_id)
|
|
645
|
+
for name in self._event_types:
|
|
646
|
+
event_name = "%s:trader:%s" % (bridge_id, name)
|
|
647
|
+
if event_name in self._registered_events:
|
|
648
|
+
continue
|
|
649
|
+
client.add_callback("trader:%s" % name, self._make_trader_handler(name))
|
|
650
|
+
self._registered_events.add(event_name)
|
|
651
|
+
|
|
652
|
+
def _make_trader_handler(self, name):
|
|
653
|
+
def handler(data):
|
|
654
|
+
data_account_id = _event_account_id(data)
|
|
655
|
+
if self.account_id and data_account_id and data_account_id != self.account_id:
|
|
656
|
+
return
|
|
657
|
+
cls = self._event_types.get(name)
|
|
658
|
+
if cls is not None:
|
|
659
|
+
data = cls.from_any(data)
|
|
660
|
+
func = getattr(self.callback, name, None)
|
|
661
|
+
if callable(func):
|
|
662
|
+
if name in ("on_connected", "on_disconnected"):
|
|
663
|
+
func()
|
|
664
|
+
else:
|
|
665
|
+
func(data)
|
|
666
|
+
|
|
667
|
+
return handler
|
|
668
|
+
|
|
669
|
+
def _emit_noarg_callback(self, name):
|
|
670
|
+
func = getattr(self.callback, name, None)
|
|
671
|
+
if callable(func):
|
|
672
|
+
try:
|
|
673
|
+
func()
|
|
674
|
+
except Exception:
|
|
675
|
+
pass
|
|
676
|
+
|
|
677
|
+
def _object_callback(self, name, cls):
|
|
678
|
+
func = getattr(self.callback, name, None)
|
|
679
|
+
if not callable(func):
|
|
680
|
+
return None
|
|
681
|
+
|
|
682
|
+
def handler(data):
|
|
683
|
+
func(cls.from_any(data))
|
|
684
|
+
|
|
685
|
+
return handler
|
|
686
|
+
|
|
687
|
+
def _get_client(self, bridge_id=None):
|
|
688
|
+
bridge_id = normalize_bridge_id(bridge_id or self.bridge_id)
|
|
689
|
+
client = self._clients.get(bridge_id)
|
|
690
|
+
if client is None:
|
|
691
|
+
client_id = self.client_id
|
|
692
|
+
if bridge_id != normalize_bridge_id(self.bridge_id):
|
|
693
|
+
client_id = "%s_%s" % (self.client_id, _safe_client_part(bridge_id))
|
|
694
|
+
client = _new_trade_client(client_id=client_id, bridge_id=bridge_id)
|
|
695
|
+
if self.timeout:
|
|
696
|
+
client.timeout = float(self.timeout)
|
|
697
|
+
self._clients[bridge_id] = client
|
|
698
|
+
if self._client is None:
|
|
699
|
+
self._client = client
|
|
700
|
+
return client
|
|
701
|
+
|
|
702
|
+
def _trade_request(self, action, params=None, timeout=None):
|
|
703
|
+
params = params or {}
|
|
704
|
+
bridge_id = _bridge_id_from_params(params) or self.bridge_id
|
|
705
|
+
self._register_trader_events(bridge_id)
|
|
706
|
+
return self._get_client(bridge_id).request(action, params, timeout=timeout)
|
|
707
|
+
|
|
708
|
+
def _resolve_account(self, account):
|
|
709
|
+
account = account or self.account
|
|
710
|
+
if account is None:
|
|
711
|
+
raise ValueError("account is required")
|
|
712
|
+
return account
|
|
713
|
+
|
|
714
|
+
def _compat_request(self, method, params=None):
|
|
715
|
+
return self._trade_request("xttrader.%s" % method, params or {})
|
|
716
|
+
|
|
717
|
+
def _compat_account_request(self, method, account, args=None, kwargs=None):
|
|
718
|
+
return self._compat_request(method, self._account_params(account, args=args, kwargs=kwargs))
|
|
719
|
+
|
|
720
|
+
def _account_params(self, account, args=None, kwargs=None):
|
|
721
|
+
return {
|
|
722
|
+
"account": _account_payload(account),
|
|
723
|
+
"args": list(args or []),
|
|
724
|
+
"kwargs": kwargs or {},
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
def _async_compat_request(self, method, params=None, callback=None):
|
|
728
|
+
seq = next(self._seq)
|
|
729
|
+
body = dict(params or {})
|
|
730
|
+
body["seq"] = seq
|
|
731
|
+
result = self._compat_request(method, body)
|
|
732
|
+
if callable_callback(callback):
|
|
733
|
+
callback(result)
|
|
734
|
+
return seq
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def _account_payload(account):
|
|
738
|
+
if isinstance(account, dict):
|
|
739
|
+
payload = {
|
|
740
|
+
"account_id": account.get("account_id") or account.get("m_strAccountID") or "",
|
|
741
|
+
"account_type": account.get("account_type", xtconstant.SECURITY_ACCOUNT),
|
|
742
|
+
}
|
|
743
|
+
else:
|
|
744
|
+
payload = {
|
|
745
|
+
"account_id": account.account_id,
|
|
746
|
+
"account_type": getattr(account, "account_type", xtconstant.SECURITY_ACCOUNT),
|
|
747
|
+
}
|
|
748
|
+
bridge_id = _bridge_id_from_account(account)
|
|
749
|
+
if bridge_id:
|
|
750
|
+
payload["bridge_id"] = bridge_id
|
|
751
|
+
return payload
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def _bridge_id_from_params(params):
|
|
755
|
+
if not isinstance(params, dict):
|
|
756
|
+
return ""
|
|
757
|
+
bridge_id = _bridge_id_value(params.get("bridge_id") or params.get("qmt_bridge_id"))
|
|
758
|
+
if bridge_id:
|
|
759
|
+
return bridge_id
|
|
760
|
+
if "account" in params:
|
|
761
|
+
return _bridge_id_from_account(params.get("account")) or _default_bridge_id()
|
|
762
|
+
return ""
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def _bridge_id_from_account(account):
|
|
766
|
+
if account is None:
|
|
767
|
+
return ""
|
|
768
|
+
if isinstance(account, dict):
|
|
769
|
+
bridge_id = _bridge_id_value(account.get("bridge_id") or account.get("qmt_bridge_id"))
|
|
770
|
+
account_id = str(account.get("account_id") or account.get("m_strAccountID") or "").strip()
|
|
771
|
+
account_type = account.get("account_type", xtconstant.SECURITY_ACCOUNT)
|
|
772
|
+
else:
|
|
773
|
+
bridge_id = _bridge_id_value(getattr(account, "bridge_id", "") or getattr(account, "qmt_bridge_id", ""))
|
|
774
|
+
account_id = _account_id(account)
|
|
775
|
+
account_type = getattr(account, "account_type", xtconstant.SECURITY_ACCOUNT)
|
|
776
|
+
if bridge_id:
|
|
777
|
+
return bridge_id
|
|
778
|
+
return _bridge_id_for_account(account_id, account_type)
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def _bridge_id_value(value):
|
|
782
|
+
value = str(value or "").strip()
|
|
783
|
+
if not value:
|
|
784
|
+
return ""
|
|
785
|
+
return normalize_bridge_id(value)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _subscription_key(account):
|
|
789
|
+
return _subscription_key_from_payload(_account_payload(account))
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def _subscription_key_from_payload(payload):
|
|
793
|
+
bridge_id = _bridge_id_from_account(payload) or _default_bridge_id()
|
|
794
|
+
return (
|
|
795
|
+
normalize_bridge_id(bridge_id),
|
|
796
|
+
_account_type_name(payload.get("account_type", xtconstant.SECURITY_ACCOUNT)),
|
|
797
|
+
str(payload.get("account_id") or "").strip(),
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def _default_bridge_id():
|
|
802
|
+
return normalize_bridge_id(get_config().get("bridge_id"))
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _bridge_id_for_account(account_id, account_type=xtconstant.SECURITY_ACCOUNT):
|
|
806
|
+
account_id = str(account_id or "").strip()
|
|
807
|
+
if not account_id:
|
|
808
|
+
return ""
|
|
809
|
+
account_type = _account_type_name(account_type)
|
|
810
|
+
pairs = _account_bridge_pairs()
|
|
811
|
+
for value in pairs.values():
|
|
812
|
+
if not isinstance(value, dict):
|
|
813
|
+
continue
|
|
814
|
+
if str(value.get("account_id") or "").strip() != account_id:
|
|
815
|
+
continue
|
|
816
|
+
if _account_type_name(value.get("account_type", xtconstant.SECURITY_ACCOUNT)) != account_type:
|
|
817
|
+
continue
|
|
818
|
+
return _bridge_id_value(value.get("bridge_id"))
|
|
819
|
+
legacy = pairs.get(account_id)
|
|
820
|
+
if isinstance(legacy, dict):
|
|
821
|
+
return _bridge_id_value(legacy.get("bridge_id"))
|
|
822
|
+
return legacy or ""
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _account_bridge_pairs():
|
|
826
|
+
paths = _account_bridge_config_paths()
|
|
827
|
+
stamp_parts = []
|
|
828
|
+
for path in paths:
|
|
829
|
+
try:
|
|
830
|
+
stat = os.stat(path)
|
|
831
|
+
stamp_parts.append((path, stat.st_mtime, stat.st_size))
|
|
832
|
+
except Exception:
|
|
833
|
+
stamp_parts.append((path, 0, 0))
|
|
834
|
+
stamp = tuple(stamp_parts)
|
|
835
|
+
with _account_bridge_lock:
|
|
836
|
+
cached = _account_bridge_cache.get("stamp")
|
|
837
|
+
if cached == stamp:
|
|
838
|
+
return dict(_account_bridge_cache.get("pairs") or {})
|
|
839
|
+
pairs = {}
|
|
840
|
+
for path, mtime, _size in stamp_parts:
|
|
841
|
+
if not mtime:
|
|
842
|
+
continue
|
|
843
|
+
try:
|
|
844
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
845
|
+
raw = json.load(f)
|
|
846
|
+
values = (raw.get("account_pairs") or {}) if isinstance(raw, dict) else {}
|
|
847
|
+
if not values and isinstance(raw, dict):
|
|
848
|
+
values = raw.get("account_configs") or {}
|
|
849
|
+
if isinstance(values, dict):
|
|
850
|
+
items = []
|
|
851
|
+
for key, item in values.items():
|
|
852
|
+
if isinstance(item, dict):
|
|
853
|
+
row = dict(item)
|
|
854
|
+
row.setdefault("account_key", key)
|
|
855
|
+
items.append(row)
|
|
856
|
+
else:
|
|
857
|
+
items.append({"account_key": key, "account_id": key, "bridge_id": item})
|
|
858
|
+
else:
|
|
859
|
+
items = values
|
|
860
|
+
for item in items:
|
|
861
|
+
if not isinstance(item, dict):
|
|
862
|
+
continue
|
|
863
|
+
account_id = str(item.get("account_id") or "").strip()
|
|
864
|
+
account_type = _account_type_name(item.get("account_type", xtconstant.SECURITY_ACCOUNT))
|
|
865
|
+
bridge_id = _bridge_id_value(item.get("bridge_id"))
|
|
866
|
+
if account_id and bridge_id:
|
|
867
|
+
account_key = str(item.get("account_key") or "").strip()
|
|
868
|
+
if not account_key:
|
|
869
|
+
account_key = "%s:%s:%s" % (bridge_id, account_type, account_id)
|
|
870
|
+
pairs[account_key] = {
|
|
871
|
+
"account_key": account_key,
|
|
872
|
+
"account_id": account_id,
|
|
873
|
+
"account_type": account_type,
|
|
874
|
+
"bridge_id": bridge_id,
|
|
875
|
+
}
|
|
876
|
+
pairs.setdefault(account_id, bridge_id)
|
|
877
|
+
except Exception:
|
|
878
|
+
pass
|
|
879
|
+
_account_bridge_cache["stamp"] = stamp
|
|
880
|
+
_account_bridge_cache["pairs"] = pairs
|
|
881
|
+
return dict(pairs)
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def _account_bridge_config_paths():
|
|
885
|
+
paths = []
|
|
886
|
+
env_path = os.environ.get("CFQUANT_WEB_CONFIG_FILE")
|
|
887
|
+
if env_path:
|
|
888
|
+
paths.append(os.path.abspath(env_path))
|
|
889
|
+
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
890
|
+
cwd = os.path.abspath(os.getcwd())
|
|
891
|
+
paths.extend([
|
|
892
|
+
os.path.join(project_dir, "runtime", "config", "cfquant_web_config.json"),
|
|
893
|
+
os.path.join(cwd, "runtime", "config", "cfquant_web_config.json"),
|
|
894
|
+
os.path.join(project_dir, "cfquant_web_config.json"),
|
|
895
|
+
os.path.join(cwd, "cfquant_web_config.json"),
|
|
896
|
+
])
|
|
897
|
+
result = []
|
|
898
|
+
seen = set()
|
|
899
|
+
for path in paths:
|
|
900
|
+
key = os.path.normcase(path)
|
|
901
|
+
if key in seen:
|
|
902
|
+
continue
|
|
903
|
+
seen.add(key)
|
|
904
|
+
result.append(path)
|
|
905
|
+
return result
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def _account_id(account):
|
|
909
|
+
if account is None:
|
|
910
|
+
return ""
|
|
911
|
+
if isinstance(account, dict):
|
|
912
|
+
return str(account.get("account_id") or account.get("m_strAccountID") or "").strip()
|
|
913
|
+
return str(getattr(account, "account_id", "") or getattr(account, "m_strAccountID", "") or "").strip()
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def _account_type_name(account_type):
|
|
917
|
+
mapping = dict((value, name) for value, name in xtconstant.ACCOUNT_TYPE_DICT.items())
|
|
918
|
+
if isinstance(account_type, str):
|
|
919
|
+
text = account_type.strip().upper()
|
|
920
|
+
aliases = {
|
|
921
|
+
"2": "STOCK",
|
|
922
|
+
"SECURITY": "STOCK",
|
|
923
|
+
"SECURITY_ACCOUNT": "STOCK",
|
|
924
|
+
"STOCK_ACCOUNT": "STOCK",
|
|
925
|
+
"3": "CREDIT",
|
|
926
|
+
"CREDIT_ACCOUNT": "CREDIT",
|
|
927
|
+
"MARGIN": "CREDIT",
|
|
928
|
+
}
|
|
929
|
+
return aliases.get(text, text or "STOCK")
|
|
930
|
+
return mapping.get(account_type, "STOCK")
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def _safe_client_part(value):
|
|
934
|
+
text = str(value or "account").strip()
|
|
935
|
+
result = []
|
|
936
|
+
for char in text:
|
|
937
|
+
result.append(char if char.isalnum() else "_")
|
|
938
|
+
return "".join(result) or "account"
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def callable_callback(callback):
|
|
942
|
+
return callback is not None and callable(callback)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def _event_account_id(data):
|
|
946
|
+
if isinstance(data, dict):
|
|
947
|
+
for key in ("account_id", "m_strAccountID", "m_strAccountId", "m_strAccount", "m_accountID"):
|
|
948
|
+
value = data.get(key)
|
|
949
|
+
if value:
|
|
950
|
+
return str(value).strip()
|
|
951
|
+
for name in ("account_id", "m_strAccountID", "m_strAccountId", "m_strAccount", "m_accountID"):
|
|
952
|
+
value = getattr(data, name, None)
|
|
953
|
+
if value:
|
|
954
|
+
return str(value).strip()
|
|
955
|
+
return ""
|