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
@@ -0,0 +1,830 @@
1
+ #coding:gbk
2
+ #! /usr/bin/python
3
+
4
+ import os
5
+ import sys
6
+ import importlib
7
+ import datetime as dt
8
+ import io
9
+ import json
10
+ import queue
11
+ import threading
12
+ import time
13
+
14
+
15
+ _normal_bridge = None
16
+ _trade_bridge = None
17
+ _trade_thread = None
18
+ _trade_request_queue = queue.Queue(maxsize=10000)
19
+ _trade_timer_key = None
20
+ _trade_loop_started_at = 0
21
+ _trade_loop_error = ""
22
+ _trade_recv_count = 0
23
+ _trade_dispatch_count = 0
24
+ _trade_direct_dispatch_count = 0
25
+ _trade_reroute_count = 0
26
+ _trade_queue_full_count = 0
27
+ _trade_last_recv_at = 0
28
+ _trade_last_dispatch_at = 0
29
+ DEFAULT_ACCOUNT_ID = str(os.environ.get("CFQUANT_ACCOUNT_ID") or "").strip()
30
+ DEFAULT_ACCOUNT_TYPE = str(os.environ.get("CFQUANT_ACCOUNT_TYPE") or "STOCK").strip().upper()
31
+ USER_BRIDGE_ID = "default"
32
+ BRIDGE_ID = os.environ.get("CFQUANT_BRIDGE_ID", USER_BRIDGE_ID)
33
+ PIPE_NAME = os.environ.get("CFQUANT_PIPE_NAME", r"\\.\pipe\cfquant_pipe_hub")
34
+ RUNTIME_CONFIG_PATH = ""
35
+ RUNTIME_CONFIG = {}
36
+ RUNTIME_CHANNELS = {}
37
+ QMT_MARKET = os.environ.get("CFQUANT_MARKET", "").strip().upper()
38
+ PIPE_CONNECT_TIMEOUT_MS = int(os.environ.get("CFQUANT_PIPE_CONNECT_TIMEOUT_MS", "3000"))
39
+ TRADE_LOOP_IN_THREAD = os.environ.get("CFQUANT_CTYPE_TRADE_THREAD", "1").strip().lower() in ("1", "true", "yes", "on")
40
+ NORMAL_PUMP_MAX_COUNT = int(os.environ.get("CFQUANT_CTYPE_NORMAL_PUMP_MAX_COUNT", "100"))
41
+ NORMAL_PUMP_MAX_MS = float(os.environ.get("CFQUANT_CTYPE_NORMAL_PUMP_MAX_MS", "0"))
42
+ TRADE_SLEEP_SECONDS = float(os.environ.get("CFQUANT_CTYPE_TRADE_SLEEP_SECONDS", "0.001"))
43
+ TRADE_PUMP_MAX_COUNT = int(os.environ.get("CFQUANT_CTYPE_TRADE_PUMP_MAX_COUNT", "100"))
44
+ TRADE_PUMP_MAX_MS = float(os.environ.get("CFQUANT_CTYPE_TRADE_PUMP_MAX_MS", "0"))
45
+ TRADE_TIMER_INTERVAL_MS = int(os.environ.get("CFQUANT_CTYPE_TRADE_TIMER_INTERVAL_MS", "20"))
46
+
47
+
48
+ def _entry_file_path():
49
+ path = globals().get("__file__") or ""
50
+ path = str(path or "").strip()
51
+ if path and not path.startswith("<"):
52
+ try:
53
+ return os.path.abspath(path)
54
+ except Exception:
55
+ return path
56
+ return ""
57
+
58
+
59
+ def _entry_base_dir():
60
+ entry_file = _entry_file_path()
61
+ if entry_file:
62
+ return os.path.dirname(entry_file)
63
+ for name in ("CFQUANT_QMT_SCRIPT_DIR", "CFQUANT_SCRIPT_DIR", "CFQUANT_ENTRY_DIR"):
64
+ path = str(os.environ.get(name) or "").strip()
65
+ if path and os.path.isdir(path):
66
+ return os.path.abspath(path)
67
+ try:
68
+ cwd = os.path.abspath(os.getcwd())
69
+ if (
70
+ os.path.isfile(os.path.join(cwd, "CFQUANT_CTYPE_ALL_LOWLAT.py"))
71
+ or os.path.isfile(os.path.join(cwd, "cfquant_bridge_config.json"))
72
+ or os.path.isdir(os.path.join(cwd, "cfquant"))
73
+ ):
74
+ return cwd
75
+ except Exception:
76
+ pass
77
+ for path in sys.path:
78
+ path = str(path or "").strip()
79
+ if path and os.path.isdir(path):
80
+ base = os.path.abspath(path)
81
+ if (
82
+ os.path.isfile(os.path.join(base, "CFQUANT_CTYPE_ALL_LOWLAT.py"))
83
+ or os.path.isfile(os.path.join(base, "cfquant_bridge_config.json"))
84
+ or os.path.isdir(os.path.join(base, "cfquant"))
85
+ ):
86
+ return base
87
+ try:
88
+ return os.path.abspath(os.getcwd())
89
+ except Exception:
90
+ return ""
91
+
92
+
93
+ def _runtime_log_path():
94
+ try:
95
+ base_dir = _entry_base_dir()
96
+ parent_dir = os.path.dirname(base_dir)
97
+ configured = os.environ.get("CFQUANT_QMT_LOG_DIR") or os.environ.get("CFQUANT_LOG_DIR")
98
+ if configured:
99
+ candidates = [configured]
100
+ elif os.path.basename(base_dir).lower() == "python":
101
+ candidates = [
102
+ os.path.join(parent_dir, "bin.x64", "log"),
103
+ os.path.join(parent_dir, "log"),
104
+ os.path.join(base_dir, "log"),
105
+ ]
106
+ else:
107
+ candidates = [
108
+ os.path.join(base_dir, "log"),
109
+ os.path.join(parent_dir, "bin.x64", "log"),
110
+ os.path.join(parent_dir, "log"),
111
+ ]
112
+ candidates.extend([
113
+ os.path.join(parent_dir, "bin.x64", "tx_log"),
114
+ os.path.join(base_dir, "tx_log"),
115
+ os.path.join(parent_dir, "tx_log"),
116
+ base_dir,
117
+ ])
118
+ for log_dir in candidates:
119
+ if not log_dir:
120
+ continue
121
+ try:
122
+ os.makedirs(log_dir, exist_ok=True)
123
+ return os.path.join(log_dir, "cfquant_ctype_bridge.log")
124
+ except Exception:
125
+ if os.path.isdir(log_dir):
126
+ return os.path.join(log_dir, "cfquant_ctype_bridge.log")
127
+ except Exception:
128
+ pass
129
+ return ""
130
+
131
+
132
+ def _write_runtime_log(message):
133
+ try:
134
+ path = _runtime_log_path()
135
+ if not path:
136
+ return
137
+ log_dir = os.path.dirname(path)
138
+ if log_dir and not os.path.isdir(log_dir):
139
+ os.makedirs(log_dir)
140
+ with open(path, "a") as f:
141
+ f.write("%s %s\n" % (dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), message))
142
+ except Exception:
143
+ pass
144
+
145
+
146
+ def _install_logging_compat():
147
+ """Keep this entry compatible with older cfquant packages in QMT."""
148
+ logging_module = importlib.import_module("cfquant.logging_i18n")
149
+
150
+ if not hasattr(logging_module, "normalize_log_language"):
151
+ def _normalize_log_language(value=None):
152
+ value = str(value or "").strip().lower()
153
+ return "en" if value in ("en", "english") else "zh"
154
+
155
+ logging_module.normalize_log_language = _normalize_log_language
156
+
157
+ if not hasattr(logging_module, "normalize_log_enabled"):
158
+ def _normalize_log_enabled(value=None):
159
+ if isinstance(value, bool):
160
+ return value
161
+ if value is None:
162
+ return True
163
+ text = str(value).strip().lower()
164
+ return text not in ("0", "false", "no", "off", "disable", "disabled", "closed", "close")
165
+
166
+ logging_module.normalize_log_enabled = _normalize_log_enabled
167
+
168
+ if not hasattr(logging_module, "_CFQUANT_COMPAT_LOG_LANGUAGE"):
169
+ logging_module._CFQUANT_COMPAT_LOG_LANGUAGE = ""
170
+ if not hasattr(logging_module, "_CFQUANT_COMPAT_LOG_ENABLED"):
171
+ logging_module._CFQUANT_COMPAT_LOG_ENABLED = None
172
+
173
+ if not hasattr(logging_module, "get_log_language"):
174
+ def _get_log_language():
175
+ if logging_module._CFQUANT_COMPAT_LOG_LANGUAGE:
176
+ return logging_module._CFQUANT_COMPAT_LOG_LANGUAGE
177
+ return logging_module.normalize_log_language(
178
+ os.environ.get("CFQUANT_QMT_LOG_LANGUAGE")
179
+ or os.environ.get("CFQUANT_LOG_LANGUAGE")
180
+ or "zh"
181
+ )
182
+
183
+ logging_module.get_log_language = _get_log_language
184
+
185
+ if not hasattr(logging_module, "set_log_language"):
186
+ def _set_log_language(value):
187
+ language = logging_module.normalize_log_language(value)
188
+ logging_module._CFQUANT_COMPAT_LOG_LANGUAGE = language
189
+ return language
190
+
191
+ logging_module.set_log_language = _set_log_language
192
+
193
+ if not hasattr(logging_module, "get_log_enabled"):
194
+ def _get_log_enabled():
195
+ if logging_module._CFQUANT_COMPAT_LOG_ENABLED is not None:
196
+ return bool(logging_module._CFQUANT_COMPAT_LOG_ENABLED)
197
+ return logging_module.normalize_log_enabled(
198
+ os.environ.get("CFQUANT_QMT_LOG_ENABLED")
199
+ or os.environ.get("CFQUANT_LOG_ENABLED")
200
+ or "1"
201
+ )
202
+
203
+ logging_module.get_log_enabled = _get_log_enabled
204
+
205
+ if not hasattr(logging_module, "set_log_enabled"):
206
+ def _set_log_enabled(value):
207
+ enabled = logging_module.normalize_log_enabled(value)
208
+ logging_module._CFQUANT_COMPAT_LOG_ENABLED = enabled
209
+ return enabled
210
+
211
+ logging_module.set_log_enabled = _set_log_enabled
212
+
213
+ if not hasattr(logging_module, "translate_log"):
214
+ def _translate_log(message, language=None):
215
+ return str(message)
216
+
217
+ logging_module.translate_log = _translate_log
218
+
219
+ return logging_module
220
+
221
+
222
+ def _ensure_path():
223
+ try:
224
+ base_dir = _entry_base_dir()
225
+ parent_dir = os.path.dirname(base_dir)
226
+ env_paths = [p for p in os.environ.get("CFQUANT_PYTHONPATH", "").split(os.pathsep) if p]
227
+ if os.path.basename(base_dir).lower() == "python":
228
+ candidates = env_paths + [os.path.join(parent_dir, "bin.x64"), base_dir, parent_dir]
229
+ else:
230
+ candidates = env_paths + [
231
+ base_dir,
232
+ os.path.join(base_dir, "bin.x64"),
233
+ parent_dir,
234
+ os.path.join(parent_dir, "bin.x64"),
235
+ os.path.join(parent_dir, "python"),
236
+ ]
237
+ ordered = []
238
+ seen = set()
239
+ for path in candidates:
240
+ if not path or not os.path.isdir(path):
241
+ continue
242
+ path = os.path.abspath(path)
243
+ key = os.path.normcase(path)
244
+ if key in seen:
245
+ continue
246
+ seen.add(key)
247
+ ordered.append(path)
248
+ if ordered:
249
+ sys.path[:] = ordered + [
250
+ path for path in sys.path
251
+ if os.path.normcase(os.path.abspath(path or os.curdir)) not in seen
252
+ ]
253
+ except Exception:
254
+ pass
255
+
256
+
257
+ def _runtime_config_paths():
258
+ try:
259
+ base_dir = _entry_base_dir()
260
+ parent_dir = os.path.dirname(base_dir)
261
+ candidates = []
262
+ env_path = os.environ.get("CFQUANT_BRIDGE_CONFIG_FILE")
263
+ if env_path:
264
+ candidates.append(env_path)
265
+ market = str(os.environ.get("CFQUANT_MARKET") or QMT_MARKET or "").strip().upper()
266
+ market_filenames = []
267
+ if market in ("SH", "SZ"):
268
+ market_filenames.append("cfquant_bridge_config_%s.json" % market)
269
+ if os.path.basename(base_dir).lower() == "python":
270
+ for filename in market_filenames:
271
+ candidates.append(os.path.join(parent_dir, "bin.x64", filename))
272
+ candidates.append(os.path.join(base_dir, filename))
273
+ candidates.append(os.path.join(parent_dir, "bin.x64", "cfquant_bridge_config.json"))
274
+ candidates.append(os.path.join(base_dir, "cfquant_bridge_config.json"))
275
+ else:
276
+ for filename in market_filenames:
277
+ candidates.append(os.path.join(base_dir, filename))
278
+ candidates.append(os.path.join(base_dir, "bin.x64", filename))
279
+ candidates.append(os.path.join(base_dir, "cfquant_bridge_config.json"))
280
+ candidates.append(os.path.join(base_dir, "bin.x64", "cfquant_bridge_config.json"))
281
+ for filename in market_filenames:
282
+ candidates.append(os.path.join(parent_dir, filename))
283
+ candidates.append(os.path.join(parent_dir, "cfquant_bridge_config.json"))
284
+ result = []
285
+ seen = set()
286
+ for path in candidates:
287
+ if not path:
288
+ continue
289
+ path = os.path.abspath(os.path.expandvars(os.path.expanduser(path)))
290
+ key = os.path.normcase(path)
291
+ if key in seen:
292
+ continue
293
+ seen.add(key)
294
+ result.append(path)
295
+ return result
296
+ except Exception:
297
+ return []
298
+
299
+
300
+ def _load_runtime_config():
301
+ for path in _runtime_config_paths():
302
+ if not os.path.isfile(path):
303
+ continue
304
+ for index, opener in enumerate((
305
+ lambda: io.open(path, "r", encoding="utf-8"),
306
+ lambda: open(path, "r"),
307
+ )):
308
+ try:
309
+ with opener() as f:
310
+ data = json.loads(f.read())
311
+ if isinstance(data, dict):
312
+ return path, data
313
+ except Exception as e:
314
+ if index == 1:
315
+ _write_runtime_log("cfquant ctypes runtime config read failed path=%s error=%s" % (path, e))
316
+ return "", {}
317
+
318
+
319
+ def _config_bool(value, default=True):
320
+ if isinstance(value, bool):
321
+ return value
322
+ if value is None:
323
+ return default
324
+ text = str(value).strip().lower()
325
+ if text in ("0", "false", "no", "off", "disable", "disabled", "closed", "close"):
326
+ return False
327
+ if text in ("1", "true", "yes", "on", "enable", "enabled", "open"):
328
+ return True
329
+ return default
330
+
331
+
332
+ def _env_allows_runtime_override(name, default_value=""):
333
+ value = str(os.environ.get(name) or "").strip()
334
+ if not value:
335
+ return True
336
+ if str(os.environ.get("%s_SOURCE" % name) or "").strip() == "cfquant_entry":
337
+ return True
338
+ return bool(default_value and value == default_value)
339
+
340
+
341
+ def _apply_runtime_config():
342
+ global BRIDGE_ID, PIPE_NAME, RUNTIME_CONFIG_PATH, RUNTIME_CONFIG, RUNTIME_CHANNELS, QMT_MARKET, DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_TYPE
343
+
344
+ path, data = _load_runtime_config()
345
+ RUNTIME_CONFIG_PATH = path
346
+ RUNTIME_CONFIG = data
347
+ if not data:
348
+ _write_runtime_log("cfquant ctypes runtime config not found")
349
+ return
350
+ if data.get("account_id") and not DEFAULT_ACCOUNT_ID:
351
+ DEFAULT_ACCOUNT_ID = str(data.get("account_id") or "").strip()
352
+ if data.get("account_type"):
353
+ DEFAULT_ACCOUNT_TYPE = str(data.get("account_type") or DEFAULT_ACCOUNT_TYPE or "STOCK").strip().upper()
354
+ if data.get("bridge_id") and _env_allows_runtime_override("CFQUANT_BRIDGE_ID", USER_BRIDGE_ID):
355
+ BRIDGE_ID = data.get("bridge_id")
356
+ if data.get("market"):
357
+ market = str(data.get("market") or "").strip().upper()
358
+ if market in ("SH", "SZ"):
359
+ QMT_MARKET = market
360
+ if not os.environ.get("CFQUANT_MARKET"):
361
+ os.environ["CFQUANT_MARKET"] = market
362
+ os.environ["CFQUANT_MARKET_SOURCE"] = "cfquant_entry"
363
+ if data.get("pipe_name") and _env_allows_runtime_override("CFQUANT_PIPE_NAME", r"\\.\pipe\cfquant_pipe_hub"):
364
+ PIPE_NAME = data.get("pipe_name")
365
+ channels = data.get("channels") or {}
366
+ if isinstance(channels, dict):
367
+ RUNTIME_CHANNELS = channels
368
+ if not os.environ.get("CFQUANT_QMT_LOG_LANGUAGE") and data.get("qmt_log_language"):
369
+ os.environ["CFQUANT_QMT_LOG_LANGUAGE"] = str(data.get("qmt_log_language") or "zh")
370
+ if not os.environ.get("CFQUANT_QMT_LOG_ENABLED") and "qmt_log_enabled" in data:
371
+ os.environ["CFQUANT_QMT_LOG_ENABLED"] = "1" if _config_bool(data.get("qmt_log_enabled"), True) else "0"
372
+ _write_runtime_log(
373
+ "cfquant ctypes runtime config loaded path=%s bridge_id=%s pipe=%s"
374
+ % (path, BRIDGE_ID, PIPE_NAME)
375
+ )
376
+
377
+
378
+ _ensure_path()
379
+ _apply_runtime_config()
380
+ _write_runtime_log("cfquant ctypes entry executing file=%s sys_path_head=%s" % (_entry_file_path() or "<string>", sys.path[:5]))
381
+
382
+ _logging_i18n = _install_logging_compat()
383
+ get_log_enabled = _logging_i18n.get_log_enabled
384
+ translate_log = _logging_i18n.translate_log
385
+
386
+ try:
387
+ import cfquant as _cfquant
388
+ from cfquant import __version__ as _ENTRY_VERSION
389
+ from cfquant.protocol import loads_message
390
+ _write_runtime_log(
391
+ "cfquant import ok version=%s module_file=%s"
392
+ % (_ENTRY_VERSION, getattr(_cfquant, "__file__", "<unknown>"))
393
+ )
394
+ except Exception as e:
395
+ _write_runtime_log("cfquant import failed:%s sys_path_head=%s" % (e, sys.path[:8]))
396
+ raise
397
+
398
+
399
+ def _print_log(message):
400
+ if not get_log_enabled():
401
+ return
402
+ translated = translate_log(message)
403
+ print(translated)
404
+ _write_runtime_log(translated)
405
+
406
+
407
+ def _load_bridge_starters():
408
+ import cfquant.pipe_transport as pipe_transport
409
+ import cfquant.pipe_bridge as pipe_bridge
410
+
411
+ try:
412
+ pipe_transport = importlib.reload(pipe_transport)
413
+ except Exception as e:
414
+ _print_log("pipe transport reload failed:%s" % e)
415
+ try:
416
+ pipe_bridge = importlib.reload(pipe_bridge)
417
+ except Exception as e:
418
+ _print_log("pipe bridge reload failed:%s" % e)
419
+ return pipe_bridge.start_pipe_normal_bridge, pipe_bridge.start_pipe_trade_bridge
420
+
421
+
422
+ start_pipe_normal_bridge, start_pipe_trade_bridge = _load_bridge_starters()
423
+
424
+ from cfquant.channels import channels_for_bridge, normalize_bridge_id
425
+
426
+ BRIDGE_ID = normalize_bridge_id(BRIDGE_ID)
427
+ QMT_MARKET = str(QMT_MARKET or os.environ.get("CFQUANT_MARKET") or RUNTIME_CONFIG.get("market") or "").strip().upper()
428
+ if QMT_MARKET not in ("SH", "SZ"):
429
+ QMT_MARKET = ""
430
+ if not os.environ.get("CFQUANT_BRIDGE_ID"):
431
+ os.environ["CFQUANT_BRIDGE_ID"] = BRIDGE_ID
432
+ os.environ["CFQUANT_BRIDGE_ID_SOURCE"] = "cfquant_entry"
433
+ if QMT_MARKET and not os.environ.get("CFQUANT_MARKET"):
434
+ os.environ["CFQUANT_MARKET"] = QMT_MARKET
435
+ os.environ["CFQUANT_MARKET_SOURCE"] = "cfquant_entry"
436
+ if PIPE_NAME and not os.environ.get("CFQUANT_PIPE_NAME"):
437
+ os.environ["CFQUANT_PIPE_NAME"] = PIPE_NAME
438
+ os.environ["CFQUANT_PIPE_NAME_SOURCE"] = "cfquant_entry"
439
+ BRIDGE_CHANNELS = channels_for_bridge(BRIDGE_ID)
440
+ for _channel_key in ("normal", "trade", "callback"):
441
+ _channel_value = RUNTIME_CHANNELS.get(_channel_key) or RUNTIME_CONFIG.get("%s_channel" % _channel_key)
442
+ if _channel_value:
443
+ BRIDGE_CHANNELS[_channel_key] = str(_channel_value).strip()
444
+
445
+ _normal_bridge = start_pipe_normal_bridge(
446
+ None,
447
+ pipe_name=PIPE_NAME,
448
+ request_channel=BRIDGE_CHANNELS["normal"],
449
+ request_channels=[BRIDGE_CHANNELS["normal"]],
450
+ callback_event_channel=BRIDGE_CHANNELS["callback"],
451
+ bridge_id=BRIDGE_ID,
452
+ account_id=DEFAULT_ACCOUNT_ID,
453
+ show=True,
454
+ schedule_timer=False,
455
+ pump_max_count=NORMAL_PUMP_MAX_COUNT,
456
+ pump_max_ms=NORMAL_PUMP_MAX_MS,
457
+ dispatch_on_qmt_thread=True,
458
+ connect_timeout_ms=PIPE_CONNECT_TIMEOUT_MS,
459
+ )
460
+ if DEFAULT_ACCOUNT_TYPE and _normal_bridge:
461
+ _normal_bridge.account_type = DEFAULT_ACCOUNT_TYPE
462
+
463
+ _trade_bridge = start_pipe_trade_bridge(
464
+ None,
465
+ pipe_name=PIPE_NAME,
466
+ request_channel=BRIDGE_CHANNELS["trade"],
467
+ bridge_id=BRIDGE_ID,
468
+ account_id=DEFAULT_ACCOUNT_ID,
469
+ show=True,
470
+ connect_timeout_ms=PIPE_CONNECT_TIMEOUT_MS,
471
+ )
472
+ if DEFAULT_ACCOUNT_TYPE and _trade_bridge:
473
+ _trade_bridge.account_type = DEFAULT_ACCOUNT_TYPE
474
+
475
+ _print_log("cfquant ctypes all-in-one lowlat bridge module loaded")
476
+ _print_log("cfquant ctypes all-in-one lowlat entry version:%s" % _ENTRY_VERSION)
477
+ _print_log("cfquant ctypes bridge id:%s account:%s/%s pipe:%s normal_channel:%s trade_channel:%s callback_channel:%s" % (
478
+ BRIDGE_ID,
479
+ DEFAULT_ACCOUNT_ID or "-",
480
+ DEFAULT_ACCOUNT_TYPE or "-",
481
+ PIPE_NAME,
482
+ BRIDGE_CHANNELS["normal"],
483
+ BRIDGE_CHANNELS["trade"],
484
+ BRIDGE_CHANNELS["callback"],
485
+ ))
486
+ if QMT_MARKET:
487
+ _print_log("cfquant ctypes market route market:%s bridge_id:%s" % (QMT_MARKET, BRIDGE_ID))
488
+ _print_log("cfquant ctypes trade loop in thread:%s sleep_seconds:%s" % (TRADE_LOOP_IN_THREAD, TRADE_SLEEP_SECONDS))
489
+
490
+
491
+ def _attach_normal_status_extra():
492
+ if not _normal_bridge:
493
+ return
494
+ original_status_extra = _normal_bridge._status_extra
495
+
496
+ def status_extra_with_trade():
497
+ data = original_status_extra()
498
+ data.update({
499
+ "qmt_runtime_core_version": _ENTRY_VERSION,
500
+ "qmt_runtime_module_file": getattr(_cfquant, "__file__", ""),
501
+ "qmt_runtime_entry_file": _entry_file_path() or "<string>",
502
+ "qmt_runtime_market": QMT_MARKET,
503
+ "qmt_runtime_market_role": RUNTIME_CONFIG.get("market_role") or ("trade" if QMT_MARKET else ""),
504
+ "qmt_runtime_market_parent_bridge_id": RUNTIME_CONFIG.get("market_route_parent_bridge_id") or "",
505
+ "ctype_trade_bridge_running": bool(_trade_bridge and _trade_bridge.running),
506
+ "ctype_trade_thread_alive": bool(_trade_thread and _trade_thread.is_alive()),
507
+ "ctype_trade_queue_size": _trade_request_queue.qsize(),
508
+ "ctype_trade_timer_key": _trade_timer_key,
509
+ "ctype_trade_loop_started_at": _trade_loop_started_at,
510
+ "ctype_trade_loop_error": _trade_loop_error,
511
+ "ctype_trade_recv_count": _trade_recv_count,
512
+ "ctype_trade_dispatch_count": _trade_dispatch_count,
513
+ "ctype_trade_direct_dispatch_count": _trade_direct_dispatch_count,
514
+ "ctype_trade_reroute_count": _trade_reroute_count,
515
+ "ctype_trade_queue_full_count": _trade_queue_full_count,
516
+ "ctype_trade_last_recv_at": _trade_last_recv_at,
517
+ "ctype_trade_last_dispatch_at": _trade_last_dispatch_at,
518
+ "ctype_trade_request_channel": BRIDGE_CHANNELS["trade"],
519
+ "ctype_trade_loop_in_thread": TRADE_LOOP_IN_THREAD,
520
+ "ctype_trade_sleep_seconds": TRADE_SLEEP_SECONDS,
521
+ "ctype_trade_pump_max_count": TRADE_PUMP_MAX_COUNT,
522
+ "ctype_trade_pump_max_ms": TRADE_PUMP_MAX_MS,
523
+ "ctype_trade_timer_interval_ms": TRADE_TIMER_INTERVAL_MS,
524
+ "ctype_trade_dispatch_thread": "qmt_timer_or_handlebar",
525
+ "ctype_trade_route_mode": "xttrader_to_normal_qmt_timer",
526
+ })
527
+ return data
528
+
529
+ _normal_bridge._status_extra = status_extra_with_trade
530
+
531
+
532
+ def _run_trade_loop():
533
+ global _trade_loop_error, _trade_recv_count, _trade_queue_full_count, _trade_last_recv_at
534
+
535
+ while _trade_bridge and _trade_bridge.running:
536
+ try:
537
+ tx = _trade_bridge.tx
538
+ if tx is None:
539
+ time.sleep(0.05)
540
+ continue
541
+ raw = tx.Q.get(timeout=TRADE_SLEEP_SECONDS)
542
+ if raw is None:
543
+ continue
544
+ _trade_request_queue.put_nowait(raw)
545
+ _trade_recv_count += 1
546
+ _trade_last_recv_at = time.time()
547
+ except Exception as e:
548
+ if isinstance(e, queue.Empty):
549
+ continue
550
+ if isinstance(e, queue.Full):
551
+ _trade_queue_full_count += 1
552
+ _trade_loop_error = "trade request queue full"
553
+ else:
554
+ _trade_loop_error = "%s:%s" % (type(e).__name__, e)
555
+ _print_log("cfquant ctypes lowlat trade loop error:%s" % _trade_loop_error)
556
+ try:
557
+ time.sleep(0.05)
558
+ except Exception:
559
+ pass
560
+
561
+
562
+ def _drain_trade_requests(source):
563
+ global _trade_dispatch_count, _trade_last_dispatch_at, _trade_loop_error
564
+
565
+ if not _trade_bridge:
566
+ return 0
567
+ start = time.perf_counter()
568
+ count = 0
569
+ while count < TRADE_PUMP_MAX_COUNT:
570
+ if TRADE_PUMP_MAX_MS > 0 and (time.perf_counter() - start) * 1000 >= TRADE_PUMP_MAX_MS:
571
+ break
572
+ try:
573
+ raw = _trade_request_queue.get_nowait()
574
+ except queue.Empty:
575
+ break
576
+ try:
577
+ _handle_trade_raw(raw)
578
+ _trade_dispatch_count += 1
579
+ _trade_last_dispatch_at = time.time()
580
+ except Exception as e:
581
+ _trade_loop_error = "%s:%s" % (type(e).__name__, e)
582
+ _print_log("cfquant ctypes lowlat trade dispatch error source=%s error=%s" % (source, _trade_loop_error))
583
+ count += 1
584
+ return count
585
+
586
+
587
+ def _handle_trade_raw(raw):
588
+ if _should_reroute_trade_raw(raw) and _normal_bridge:
589
+ return _reroute_trade_raw_to_normal(raw)
590
+ _handle_trade_raw_direct(raw)
591
+
592
+
593
+ def _should_reroute_trade_raw(raw):
594
+ msg = loads_message(raw)
595
+ if not msg or msg.get("type") != "request":
596
+ return False
597
+ action = str(msg.get("action") or "")
598
+ return action.startswith("xttrader.") or action == "cfquant.query_info"
599
+
600
+
601
+ def _reroute_trade_raw_to_normal(raw):
602
+ global _trade_reroute_count
603
+
604
+ _normal_bridge._handle_raw_from_thread(raw)
605
+ _trade_reroute_count += 1
606
+
607
+
608
+ def _handle_trade_raw_direct(raw):
609
+ global _trade_direct_dispatch_count
610
+
611
+ _trade_bridge._handle_raw(raw)
612
+ _trade_direct_dispatch_count += 1
613
+
614
+
615
+ def _bind_dispatch_context(ContextInfo):
616
+ if ContextInfo is None:
617
+ return
618
+ if _normal_bridge:
619
+ _normal_bridge.context = ContextInfo
620
+ if _trade_bridge:
621
+ _trade_bridge.context = ContextInfo
622
+
623
+
624
+ def _start_trade_loop():
625
+ global _trade_thread, _trade_loop_started_at, _trade_loop_error
626
+
627
+ if not _trade_bridge:
628
+ return
629
+ if _trade_thread is not None and _trade_thread.is_alive():
630
+ return
631
+ if TRADE_LOOP_IN_THREAD:
632
+ _trade_loop_error = ""
633
+ _trade_loop_started_at = time.time()
634
+ _trade_thread = threading.Thread(target=_run_trade_loop)
635
+ _trade_thread.daemon = True
636
+ _trade_thread.start()
637
+ _print_log("cfquant ctypes lowlat trade loop started in worker thread")
638
+ return
639
+ _print_log("cfquant ctypes lowlat trade loop entering current QMT thread")
640
+ _run_trade_loop()
641
+
642
+
643
+ def cfquant_ctype_trade_timer(*args, **kwargs):
644
+ context = args[0] if args else kwargs.get("ContextInfo") or kwargs.get("context")
645
+ _bind_dispatch_context(context)
646
+ _drain_trade_requests("timer")
647
+ if _normal_bridge:
648
+ try:
649
+ _normal_bridge.pump()
650
+ except Exception as e:
651
+ _print_log("cfquant ctypes normal bridge pump failed source=trade_timer error:%s" % e)
652
+
653
+
654
+ def _schedule_trade_timer(ContextInfo):
655
+ global _trade_timer_key
656
+
657
+ if _trade_timer_key or ContextInfo is None:
658
+ return
659
+ try:
660
+ first_time = dt.datetime.now() + dt.timedelta(seconds=1)
661
+ _trade_timer_key = ContextInfo.schedule_run(
662
+ cfquant_ctype_trade_timer,
663
+ first_time,
664
+ repeat_times=-1,
665
+ interval=dt.timedelta(milliseconds=TRADE_TIMER_INTERVAL_MS),
666
+ name="cfquant_ctype_trade_bridge_pump",
667
+ )
668
+ _print_log("cfquant ctypes lowlat trade timer scheduled key:%s interval_ms:%s" % (_trade_timer_key, TRADE_TIMER_INTERVAL_MS))
669
+ except Exception as e:
670
+ _print_log("cfquant ctypes lowlat trade timer schedule failed:%s" % e)
671
+
672
+
673
+ _attach_normal_status_extra()
674
+ if TRADE_LOOP_IN_THREAD:
675
+ _start_trade_loop()
676
+
677
+
678
+ _QMT_TRADE_CALLBACK_REGISTERED = False
679
+
680
+
681
+ def _register_qmt_trade_callback(ContextInfo, stage):
682
+ global _QMT_TRADE_CALLBACK_REGISTERED
683
+
684
+ if ContextInfo is None or _QMT_TRADE_CALLBACK_REGISTERED:
685
+ return
686
+ func = getattr(ContextInfo, "register_callback", None)
687
+ if not callable(func):
688
+ _print_log("cfquant ctypes qmt trade callback register skipped stage=%s reason=missing register_callback" % stage)
689
+ return
690
+ try:
691
+ func(0)
692
+ _QMT_TRADE_CALLBACK_REGISTERED = True
693
+ _print_log("cfquant ctypes qmt trade callback registered stage=%s" % stage)
694
+ except Exception as e:
695
+ _print_log("cfquant ctypes qmt trade callback register failed stage=%s error=%s" % (stage, e))
696
+
697
+
698
+ def _refresh_auto_trade_callback(stage):
699
+ for bridge_name, bridge in (("normal", _normal_bridge), ("trade", _trade_bridge)):
700
+ if bridge is None or not hasattr(bridge, "_enable_auto_trade_callback"):
701
+ continue
702
+ try:
703
+ bridge.auto_trade_callback_enabled = False
704
+ bridge._enable_auto_trade_callback()
705
+ _print_log("cfquant ctypes auto trade callback refreshed stage=%s bridge=%s" % (stage, bridge_name))
706
+ except Exception as e:
707
+ _print_log("cfquant ctypes auto trade callback refresh failed stage=%s bridge=%s error=%s" % (stage, bridge_name, e))
708
+
709
+
710
+ def _callback_brief(obj):
711
+ try:
712
+ parts = []
713
+ for name in ("account_id", "m_strAccountID", "m_strInstrumentID", "m_strExchangeID", "m_strOrderSysID", "m_nOrderID", "m_strRemark"):
714
+ value = getattr(obj, name, None)
715
+ if value is None and hasattr(obj, "get"):
716
+ value = obj.get(name)
717
+ if value not in (None, ""):
718
+ parts.append("%s=%s" % (name, value))
719
+ return " ".join(parts) or type(obj).__name__
720
+ except Exception:
721
+ return type(obj).__name__
722
+
723
+
724
+ def _object_to_callback_dict(obj):
725
+ if hasattr(obj, "items"):
726
+ return dict(obj)
727
+ if hasattr(obj, "__dict__"):
728
+ return dict(vars(obj))
729
+ return {"value": str(obj)}
730
+
731
+
732
+ def init(ContextInfo):
733
+ _register_qmt_trade_callback(ContextInfo, "init")
734
+ if _normal_bridge:
735
+ _normal_bridge.set_context(ContextInfo)
736
+ _print_log("cfquant ctypes normal context ready version:%s" % _ENTRY_VERSION)
737
+ if _trade_bridge:
738
+ _trade_bridge.set_context(ContextInfo)
739
+ _print_log("cfquant ctypes lowlat trade context ready version:%s" % _ENTRY_VERSION)
740
+ _start_trade_loop()
741
+ _schedule_trade_timer(ContextInfo)
742
+
743
+
744
+ def after_init(ContextInfo):
745
+ _register_qmt_trade_callback(ContextInfo, "after_init")
746
+ _refresh_auto_trade_callback("after_init")
747
+
748
+
749
+ def handlebar(ContextInfo):
750
+ _bind_dispatch_context(ContextInfo)
751
+ if TRADE_LOOP_IN_THREAD:
752
+ _start_trade_loop()
753
+ _drain_trade_requests("handlebar")
754
+ if _normal_bridge:
755
+ _normal_bridge.pump()
756
+
757
+
758
+ def stop(ContextInfo):
759
+ global _normal_bridge, _trade_bridge, _trade_timer_key
760
+
761
+ if ContextInfo is not None and _trade_timer_key:
762
+ try:
763
+ ContextInfo.cancel_schedule_run(_trade_timer_key)
764
+ except Exception as e:
765
+ _print_log("cfquant ctypes lowlat trade timer cancel failed:%s" % e)
766
+ _trade_timer_key = None
767
+
768
+ if _trade_bridge:
769
+ _trade_bridge.close()
770
+ _trade_bridge = None
771
+ _print_log("cfquant ctypes lowlat trade bridge stopped")
772
+ if _normal_bridge:
773
+ _normal_bridge.close()
774
+ _normal_bridge = None
775
+ _print_log("cfquant ctypes normal bridge stopped")
776
+
777
+
778
+ def _publish_callback(event_name, obj):
779
+ try:
780
+ _print_log("cfquant ctypes raw qmt callback received event=%s %s" % (event_name, _callback_brief(obj)))
781
+ if _normal_bridge:
782
+ _normal_bridge.publish_callback_event(event_name, obj)
783
+ except Exception as e:
784
+ _print_log("cfquant ctypes lowlat callback publish failed event=%s error=%s" % (event_name, e))
785
+
786
+
787
+ def account_callback(ContextInfo, accountInfo):
788
+ _publish_callback("trader:on_stock_asset", accountInfo)
789
+
790
+
791
+ def order_callback(ContextInfo, orderInfo):
792
+ _publish_callback("trader:on_stock_order", orderInfo)
793
+
794
+
795
+ def deal_callback(ContextInfo, dealInfo):
796
+ _publish_callback("trader:on_stock_trade", dealInfo)
797
+
798
+
799
+ def trade_callback(ContextInfo, tradeInfo):
800
+ _publish_callback("trader:on_stock_trade", tradeInfo)
801
+
802
+
803
+ def position_callback(ContextInfo, positionInfo):
804
+ _publish_callback("trader:on_stock_position", positionInfo)
805
+
806
+
807
+ def order_error_callback(ContextInfo, orderError):
808
+ _publish_callback("trader:on_order_error", orderError)
809
+
810
+
811
+ def orderError_callback(ContextInfo, passOrderInfo, msg):
812
+ data = _object_to_callback_dict(passOrderInfo)
813
+ data["error_msg"] = msg
814
+ _publish_callback("trader:on_order_error", data)
815
+
816
+
817
+ def cancel_error_callback(ContextInfo, cancelError):
818
+ _publish_callback("trader:on_cancel_error", cancelError)
819
+
820
+
821
+ def cancelError_callback(ContextInfo, cancelError):
822
+ _publish_callback("trader:on_cancel_error", cancelError)
823
+
824
+
825
+ def order_stock_async_response_callback(ContextInfo, response):
826
+ _publish_callback("trader:on_order_stock_async_response", response)
827
+
828
+
829
+ def cancel_order_stock_async_response_callback(ContextInfo, response):
830
+ _publish_callback("trader:on_cancel_order_stock_async_response", response)