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.
Files changed (90) hide show
  1. LTtx/__init__.py +2 -0
  2. LTtx/tx/Config.txt +8 -0
  3. LTtx/tx/LTtx_server.py +1177 -0
  4. LTtx/tx/__init__.py +5 -0
  5. LTtx/tx/tx.py +1935 -0
  6. cfquant/__init__.py +26 -0
  7. cfquant/account_routing.py +119 -0
  8. cfquant/channels.py +76 -0
  9. cfquant/cli.py +266 -0
  10. cfquant/client.py +365 -0
  11. cfquant/config.py +86 -0
  12. cfquant/hub.py +304 -0
  13. cfquant/logging_i18n.py +348 -0
  14. cfquant/normal_bridge.py +676 -0
  15. cfquant/pipe_bridge.py +249 -0
  16. cfquant/pipe_client.py +223 -0
  17. cfquant/pipe_hub.py +602 -0
  18. cfquant/pipe_transport.py +493 -0
  19. cfquant/protocol.py +238 -0
  20. cfquant/qmt_bridge.py +1081 -0
  21. cfquant/runtime_report.py +334 -0
  22. cfquant/tx.py +1898 -0
  23. cfquant/tx_trade_bridge.py +1905 -0
  24. cfquant/version.py +5 -0
  25. cfquant/xtconstant.py +262 -0
  26. cfquant/xtdata.py +462 -0
  27. cfquant/xttrader.py +955 -0
  28. cfquant/xttype.py +586 -0
  29. cfquant-0.2.0.dist-info/LICENSE +21 -0
  30. cfquant-0.2.0.dist-info/METADATA +144 -0
  31. cfquant-0.2.0.dist-info/RECORD +90 -0
  32. cfquant-0.2.0.dist-info/WHEEL +5 -0
  33. cfquant-0.2.0.dist-info/entry_points.txt +4 -0
  34. cfquant-0.2.0.dist-info/top_level.txt +6 -0
  35. cfquant_pipe_hub.py +45 -0
  36. cfquant_web_server.py +12137 -0
  37. qmt_scripts/CFQUANT.py +507 -0
  38. qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +830 -0
  39. qmt_scripts/CFQUANT_LITE.py +4928 -0
  40. qmt_scripts/CFQUANT_TRADE_LOWLAT.py +382 -0
  41. qmt_scripts/__init__.py +1 -0
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. web_dashboard/__init__.py +1 -0
  50. web_dashboard/app.js +9847 -0
  51. web_dashboard/avatars/amber-pulse.svg +15 -0
  52. web_dashboard/avatars/copper-grid.svg +15 -0
  53. web_dashboard/avatars/market-blue.svg +15 -0
  54. web_dashboard/avatars/rose-circuit.svg +15 -0
  55. web_dashboard/avatars/signal-green.svg +15 -0
  56. web_dashboard/avatars/slate-wave.svg +15 -0
  57. web_dashboard/avatars/teal-orbit.svg +16 -0
  58. web_dashboard/avatars/violet-node.svg +15 -0
  59. web_dashboard/favicon.svg +5 -0
  60. web_dashboard/image/qmt_deploy/image-10.png +0 -0
  61. web_dashboard/image/qmt_deploy/image-11.png +0 -0
  62. web_dashboard/image/qmt_deploy/image-12.png +0 -0
  63. web_dashboard/image/qmt_deploy/image-13.png +0 -0
  64. web_dashboard/image/qmt_deploy/image-14.png +0 -0
  65. web_dashboard/image/qmt_deploy/image-15.png +0 -0
  66. web_dashboard/image/qmt_deploy/image-16.png +0 -0
  67. web_dashboard/image/qmt_deploy/image-17.png +0 -0
  68. web_dashboard/image/qmt_deploy/image-18.png +0 -0
  69. web_dashboard/image/qmt_deploy/image-19.png +0 -0
  70. web_dashboard/image/qmt_deploy/image-20.png +0 -0
  71. web_dashboard/image/qmt_deploy/image-21.png +0 -0
  72. web_dashboard/image/qmt_deploy/image-4.png +0 -0
  73. web_dashboard/image/qmt_deploy/image-5.png +0 -0
  74. web_dashboard/image/qmt_deploy/image-6.png +0 -0
  75. web_dashboard/image/qmt_deploy/image-7.png +0 -0
  76. web_dashboard/image/qmt_deploy/image-8.png +0 -0
  77. web_dashboard/image/qmt_deploy/image-9.png +0 -0
  78. 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
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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
  88. web_dashboard/index.html +2317 -0
  89. web_dashboard/styles.css +7817 -0
  90. 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 ""