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
qmt_scripts/CFQUANT.py ADDED
@@ -0,0 +1,507 @@
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
+
11
+
12
+ _cf_bridge = None
13
+ _cf_timer_key = None
14
+ _TIMER_INTERVAL_MS = 500
15
+ _PUMP_MAX_COUNT = 20
16
+ _PUMP_MAX_MS = 0
17
+ DEFAULT_ACCOUNT_ID = str(os.environ.get("CFQUANT_ACCOUNT_ID") or "").strip()
18
+ DEFAULT_ACCOUNT_TYPE = str(os.environ.get("CFQUANT_ACCOUNT_TYPE") or "STOCK").strip().upper()
19
+ USER_BRIDGE_ID = "default"
20
+ BRIDGE_ID = os.environ.get("CFQUANT_BRIDGE_ID", USER_BRIDGE_ID)
21
+ RUNTIME_CONFIG = {}
22
+ RUNTIME_CHANNELS = {}
23
+
24
+
25
+ def _entry_file_path():
26
+ path = globals().get("__file__") or ""
27
+ path = str(path or "").strip()
28
+ if path and not path.startswith("<"):
29
+ try:
30
+ return os.path.abspath(path)
31
+ except Exception:
32
+ return path
33
+ return ""
34
+
35
+
36
+ def _entry_base_dir():
37
+ entry_file = _entry_file_path()
38
+ if entry_file:
39
+ return os.path.dirname(entry_file)
40
+ for name in ("CFQUANT_QMT_SCRIPT_DIR", "CFQUANT_SCRIPT_DIR", "CFQUANT_ENTRY_DIR"):
41
+ path = str(os.environ.get(name) or "").strip()
42
+ if path and os.path.isdir(path):
43
+ return os.path.abspath(path)
44
+ try:
45
+ cwd = os.path.abspath(os.getcwd())
46
+ if (
47
+ os.path.isfile(os.path.join(cwd, "CFQUANT.py"))
48
+ or os.path.isfile(os.path.join(cwd, "cfquant_bridge_config.json"))
49
+ or os.path.isdir(os.path.join(cwd, "cfquant"))
50
+ ):
51
+ return cwd
52
+ except Exception:
53
+ pass
54
+ for path in sys.path:
55
+ path = str(path or "").strip()
56
+ if path and os.path.isdir(path):
57
+ base = os.path.abspath(path)
58
+ if (
59
+ os.path.isfile(os.path.join(base, "CFQUANT.py"))
60
+ or os.path.isfile(os.path.join(base, "cfquant_bridge_config.json"))
61
+ or os.path.isdir(os.path.join(base, "cfquant"))
62
+ ):
63
+ return base
64
+ try:
65
+ return os.path.abspath(os.getcwd())
66
+ except Exception:
67
+ return ""
68
+
69
+
70
+ def _runtime_config_paths():
71
+ try:
72
+ base_dir = _entry_base_dir()
73
+ parent_dir = os.path.dirname(base_dir)
74
+ candidates = []
75
+ env_path = os.environ.get("CFQUANT_BRIDGE_CONFIG_FILE")
76
+ if env_path:
77
+ candidates.append(env_path)
78
+ if os.path.basename(base_dir).lower() == "python":
79
+ candidates.append(os.path.join(parent_dir, "bin.x64", "cfquant_bridge_config.json"))
80
+ candidates.append(os.path.join(base_dir, "cfquant_bridge_config.json"))
81
+ else:
82
+ candidates.append(os.path.join(base_dir, "cfquant_bridge_config.json"))
83
+ candidates.append(os.path.join(base_dir, "bin.x64", "cfquant_bridge_config.json"))
84
+ candidates.append(os.path.join(parent_dir, "cfquant_bridge_config.json"))
85
+ result = []
86
+ seen = set()
87
+ for path in candidates:
88
+ if not path:
89
+ continue
90
+ path = os.path.abspath(os.path.expandvars(os.path.expanduser(path)))
91
+ key = os.path.normcase(path)
92
+ if key in seen:
93
+ continue
94
+ seen.add(key)
95
+ result.append(path)
96
+ return result
97
+ except Exception:
98
+ return []
99
+
100
+
101
+ def _load_runtime_config():
102
+ for path in _runtime_config_paths():
103
+ if not os.path.isfile(path):
104
+ continue
105
+ for opener in (
106
+ lambda: io.open(path, "r", encoding="utf-8"),
107
+ lambda: open(path, "r"),
108
+ ):
109
+ try:
110
+ with opener() as f:
111
+ data = json.loads(f.read())
112
+ if isinstance(data, dict):
113
+ return data
114
+ except Exception:
115
+ pass
116
+ return {}
117
+
118
+
119
+ def _config_bool(value, default=True):
120
+ if isinstance(value, bool):
121
+ return value
122
+ if value is None:
123
+ return default
124
+ text = str(value).strip().lower()
125
+ if text in ("0", "false", "no", "off", "disable", "disabled", "closed", "close"):
126
+ return False
127
+ if text in ("1", "true", "yes", "on", "enable", "enabled", "open"):
128
+ return True
129
+ return default
130
+
131
+
132
+ def _env_allows_runtime_override(name, default_value=""):
133
+ value = str(os.environ.get(name) or "").strip()
134
+ if not value:
135
+ return True
136
+ if str(os.environ.get("%s_SOURCE" % name) or "").strip() == "cfquant_entry":
137
+ return True
138
+ return bool(default_value and value == default_value)
139
+
140
+
141
+ def _apply_runtime_config():
142
+ global BRIDGE_ID, RUNTIME_CONFIG, RUNTIME_CHANNELS, DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_TYPE
143
+
144
+ data = _load_runtime_config()
145
+ RUNTIME_CONFIG = data
146
+ if not data:
147
+ return
148
+ if data.get("account_id") and not DEFAULT_ACCOUNT_ID:
149
+ DEFAULT_ACCOUNT_ID = str(data.get("account_id") or "").strip()
150
+ if data.get("account_type"):
151
+ DEFAULT_ACCOUNT_TYPE = str(data.get("account_type") or DEFAULT_ACCOUNT_TYPE or "STOCK").strip().upper()
152
+ if data.get("bridge_id") and _env_allows_runtime_override("CFQUANT_BRIDGE_ID", USER_BRIDGE_ID):
153
+ BRIDGE_ID = data.get("bridge_id")
154
+ channels = data.get("channels") or {}
155
+ if isinstance(channels, dict):
156
+ RUNTIME_CHANNELS = channels
157
+ if not os.environ.get("CFQUANT_QMT_LOG_LANGUAGE") and data.get("qmt_log_language"):
158
+ os.environ["CFQUANT_QMT_LOG_LANGUAGE"] = str(data.get("qmt_log_language") or "zh")
159
+ if not os.environ.get("CFQUANT_QMT_LOG_ENABLED") and "qmt_log_enabled" in data:
160
+ os.environ["CFQUANT_QMT_LOG_ENABLED"] = "1" if _config_bool(data.get("qmt_log_enabled"), True) else "0"
161
+
162
+
163
+ def _ensure_path():
164
+ try:
165
+ base_dir = _entry_base_dir()
166
+ parent_dir = os.path.dirname(base_dir)
167
+ env_paths = [p for p in os.environ.get("CFQUANT_PYTHONPATH", "").split(os.pathsep) if p]
168
+ if os.path.basename(base_dir).lower() == "python":
169
+ candidates = env_paths + [os.path.join(parent_dir, "bin.x64"), base_dir, parent_dir]
170
+ else:
171
+ candidates = env_paths + [
172
+ base_dir,
173
+ os.path.join(base_dir, "bin.x64"),
174
+ parent_dir,
175
+ os.path.join(parent_dir, "bin.x64"),
176
+ os.path.join(parent_dir, "python"),
177
+ ]
178
+ ordered = []
179
+ seen = set()
180
+ for path in candidates:
181
+ if not path or not os.path.isdir(path):
182
+ continue
183
+ path = os.path.abspath(path)
184
+ key = os.path.normcase(path)
185
+ if key in seen:
186
+ continue
187
+ seen.add(key)
188
+ ordered.append(path)
189
+ if ordered:
190
+ sys.path[:] = ordered + [
191
+ path for path in sys.path
192
+ if os.path.normcase(os.path.abspath(path or os.curdir)) not in seen
193
+ ]
194
+ except Exception:
195
+ pass
196
+
197
+
198
+ _ensure_path()
199
+ _apply_runtime_config()
200
+
201
+ from cfquant import __version__ as _ENTRY_VERSION
202
+
203
+
204
+ def _install_logging_compat():
205
+ """Keep this entry compatible with older cfquant packages in QMT."""
206
+ logging_module = importlib.import_module("cfquant.logging_i18n")
207
+
208
+ if not hasattr(logging_module, "normalize_log_language"):
209
+ def _normalize_log_language(value=None):
210
+ value = str(value or "").strip().lower()
211
+ return "en" if value in ("en", "english") else "zh"
212
+
213
+ logging_module.normalize_log_language = _normalize_log_language
214
+
215
+ if not hasattr(logging_module, "normalize_log_enabled"):
216
+ def _normalize_log_enabled(value=None):
217
+ if isinstance(value, bool):
218
+ return value
219
+ if value is None:
220
+ return True
221
+ text = str(value).strip().lower()
222
+ return text not in ("0", "false", "no", "off", "disable", "disabled", "closed", "close")
223
+
224
+ logging_module.normalize_log_enabled = _normalize_log_enabled
225
+
226
+ if not hasattr(logging_module, "_CFQUANT_COMPAT_LOG_LANGUAGE"):
227
+ logging_module._CFQUANT_COMPAT_LOG_LANGUAGE = ""
228
+ if not hasattr(logging_module, "_CFQUANT_COMPAT_LOG_ENABLED"):
229
+ logging_module._CFQUANT_COMPAT_LOG_ENABLED = None
230
+
231
+ if not hasattr(logging_module, "get_log_language"):
232
+ def _get_log_language():
233
+ if logging_module._CFQUANT_COMPAT_LOG_LANGUAGE:
234
+ return logging_module._CFQUANT_COMPAT_LOG_LANGUAGE
235
+ return logging_module.normalize_log_language(
236
+ os.environ.get("CFQUANT_QMT_LOG_LANGUAGE")
237
+ or os.environ.get("CFQUANT_LOG_LANGUAGE")
238
+ or "zh"
239
+ )
240
+
241
+ logging_module.get_log_language = _get_log_language
242
+
243
+ if not hasattr(logging_module, "set_log_language"):
244
+ def _set_log_language(value):
245
+ language = logging_module.normalize_log_language(value)
246
+ logging_module._CFQUANT_COMPAT_LOG_LANGUAGE = language
247
+ return language
248
+
249
+ logging_module.set_log_language = _set_log_language
250
+
251
+ if not hasattr(logging_module, "get_log_enabled"):
252
+ def _get_log_enabled():
253
+ if logging_module._CFQUANT_COMPAT_LOG_ENABLED is not None:
254
+ return bool(logging_module._CFQUANT_COMPAT_LOG_ENABLED)
255
+ return logging_module.normalize_log_enabled(
256
+ os.environ.get("CFQUANT_QMT_LOG_ENABLED")
257
+ or os.environ.get("CFQUANT_LOG_ENABLED")
258
+ or "1"
259
+ )
260
+
261
+ logging_module.get_log_enabled = _get_log_enabled
262
+
263
+ if not hasattr(logging_module, "set_log_enabled"):
264
+ def _set_log_enabled(value):
265
+ enabled = logging_module.normalize_log_enabled(value)
266
+ logging_module._CFQUANT_COMPAT_LOG_ENABLED = enabled
267
+ return enabled
268
+
269
+ logging_module.set_log_enabled = _set_log_enabled
270
+
271
+ if not hasattr(logging_module, "translate_log"):
272
+ def _translate_log(message, language=None):
273
+ return str(message)
274
+
275
+ logging_module.translate_log = _translate_log
276
+
277
+ return logging_module
278
+
279
+
280
+ _logging_i18n = _install_logging_compat()
281
+ get_log_enabled = _logging_i18n.get_log_enabled
282
+ translate_log = _logging_i18n.translate_log
283
+
284
+
285
+ def _print_log(message):
286
+ if get_log_enabled():
287
+ print(translate_log(message))
288
+
289
+
290
+ def _load_bridge_starter():
291
+ import cfquant.tx_trade_bridge as tx_trade_bridge
292
+ import cfquant.normal_bridge as normal_bridge
293
+
294
+ try:
295
+ tx_trade_bridge = importlib.reload(tx_trade_bridge)
296
+ except Exception as e:
297
+ _print_log("tx trade bridge reload failed:%s" % e)
298
+ try:
299
+ normal_bridge = importlib.reload(normal_bridge)
300
+ except Exception as e:
301
+ _print_log("normal bridge reload failed:%s" % e)
302
+ return normal_bridge.start_normal_bridge
303
+
304
+
305
+ start_normal_bridge = _load_bridge_starter()
306
+
307
+ from cfquant.channels import channels_for_bridge, normalize_bridge_id
308
+
309
+ BRIDGE_ID = normalize_bridge_id(BRIDGE_ID)
310
+ BRIDGE_CHANNELS = channels_for_bridge(BRIDGE_ID)
311
+ for _channel_key in ("normal", "callback"):
312
+ _channel_value = RUNTIME_CHANNELS.get(_channel_key) or RUNTIME_CONFIG.get("%s_channel" % _channel_key)
313
+ if _channel_value:
314
+ BRIDGE_CHANNELS[_channel_key] = str(_channel_value).strip()
315
+
316
+ _cf_bridge = start_normal_bridge(
317
+ None,
318
+ ip="127.0.0.1",
319
+ port=2049,
320
+ token="LTtx",
321
+ request_channel=BRIDGE_CHANNELS["normal"],
322
+ callback_event_channel=BRIDGE_CHANNELS["callback"],
323
+ bridge_id=BRIDGE_ID,
324
+ account_id=DEFAULT_ACCOUNT_ID,
325
+ show=True,
326
+ schedule_timer=False,
327
+ pump_max_count=_PUMP_MAX_COUNT,
328
+ pump_max_ms=_PUMP_MAX_MS,
329
+ )
330
+ if DEFAULT_ACCOUNT_TYPE and _cf_bridge:
331
+ _cf_bridge.account_type = DEFAULT_ACCOUNT_TYPE
332
+ _print_log("cfquant normal bridge module loaded")
333
+ _print_log("cfquant entry version:%s" % _ENTRY_VERSION)
334
+ _print_log("cfquant bridge id:%s account:%s/%s normal_channel:%s callback_channel:%s" % (
335
+ BRIDGE_ID,
336
+ DEFAULT_ACCOUNT_ID or "-",
337
+ DEFAULT_ACCOUNT_TYPE or "-",
338
+ BRIDGE_CHANNELS["normal"],
339
+ BRIDGE_CHANNELS["callback"],
340
+ ))
341
+ _print_log("cfquant normal bridge pump max_count:%s max_ms:%s" % (_PUMP_MAX_COUNT, _PUMP_MAX_MS))
342
+
343
+
344
+ def cfquant_normal_timer(*args, **kwargs):
345
+ if _cf_bridge:
346
+ _cf_bridge.on_timer(*args, **kwargs)
347
+
348
+
349
+ def _schedule_cf_timer(ContextInfo):
350
+ global _cf_timer_key
351
+
352
+ if _cf_timer_key or ContextInfo is None:
353
+ return
354
+ try:
355
+ first_time = dt.datetime.now() + dt.timedelta(seconds=1)
356
+ _cf_timer_key = ContextInfo.schedule_run(
357
+ cfquant_normal_timer,
358
+ first_time,
359
+ repeat_times=-1,
360
+ interval=dt.timedelta(milliseconds=_TIMER_INTERVAL_MS),
361
+ name="cfquant_normal_bridge_pump",
362
+ )
363
+ _print_log("cfquant normal bridge timer scheduled key:%s interval_ms:%s" % (_cf_timer_key, _TIMER_INTERVAL_MS))
364
+ except Exception as e:
365
+ _print_log("cfquant normal bridge timer schedule failed:%s" % e)
366
+
367
+
368
+ _QMT_TRADE_CALLBACK_REGISTERED = False
369
+
370
+
371
+ def _register_qmt_trade_callback(ContextInfo, stage):
372
+ global _QMT_TRADE_CALLBACK_REGISTERED
373
+
374
+ if ContextInfo is None or _QMT_TRADE_CALLBACK_REGISTERED:
375
+ return
376
+ func = getattr(ContextInfo, "register_callback", None)
377
+ if not callable(func):
378
+ _print_log("cfquant qmt trade callback register skipped stage=%s reason=missing register_callback" % stage)
379
+ return
380
+ try:
381
+ func(0)
382
+ _QMT_TRADE_CALLBACK_REGISTERED = True
383
+ _print_log("cfquant qmt trade callback registered stage=%s" % stage)
384
+ except Exception as e:
385
+ _print_log("cfquant qmt trade callback register failed stage=%s error=%s" % (stage, e))
386
+
387
+
388
+ def _refresh_auto_trade_callback(stage):
389
+ if _cf_bridge is None or not hasattr(_cf_bridge, "_enable_auto_trade_callback"):
390
+ return
391
+ try:
392
+ _cf_bridge.auto_trade_callback_enabled = False
393
+ _cf_bridge._enable_auto_trade_callback()
394
+ _print_log("cfquant auto trade callback refreshed stage=%s" % stage)
395
+ except Exception as e:
396
+ _print_log("cfquant auto trade callback refresh failed stage=%s error=%s" % (stage, e))
397
+
398
+
399
+ def _callback_brief(obj):
400
+ try:
401
+ parts = []
402
+ for name in ("account_id", "m_strAccountID", "m_strInstrumentID", "m_strExchangeID", "m_strOrderSysID", "m_nOrderID", "m_strRemark"):
403
+ value = getattr(obj, name, None)
404
+ if value is None and hasattr(obj, "get"):
405
+ value = obj.get(name)
406
+ if value not in (None, ""):
407
+ parts.append("%s=%s" % (name, value))
408
+ return " ".join(parts) or type(obj).__name__
409
+ except Exception:
410
+ return type(obj).__name__
411
+
412
+
413
+ def _object_to_callback_dict(obj):
414
+ if hasattr(obj, "items"):
415
+ return dict(obj)
416
+ if hasattr(obj, "__dict__"):
417
+ return dict(vars(obj))
418
+ return {"value": str(obj)}
419
+
420
+
421
+ def init(ContextInfo):
422
+ _register_qmt_trade_callback(ContextInfo, "init")
423
+ if _cf_bridge:
424
+ _cf_bridge.set_context(ContextInfo)
425
+ _schedule_cf_timer(ContextInfo)
426
+ _print_log("cfquant normal bridge context ready version:%s" % _ENTRY_VERSION)
427
+
428
+
429
+ def after_init(ContextInfo):
430
+ _register_qmt_trade_callback(ContextInfo, "after_init")
431
+ _refresh_auto_trade_callback("after_init")
432
+
433
+
434
+ def handlebar(ContextInfo):
435
+ if _cf_bridge:
436
+ _cf_bridge.pump()
437
+
438
+
439
+ def stop(ContextInfo):
440
+ global _cf_bridge, _cf_timer_key
441
+
442
+ if ContextInfo is not None and _cf_timer_key:
443
+ try:
444
+ ContextInfo.cancel_schedule_run(_cf_timer_key)
445
+ except Exception as e:
446
+ _print_log("cfquant normal bridge timer cancel failed:%s" % e)
447
+ _cf_timer_key = None
448
+
449
+ if _cf_bridge:
450
+ _cf_bridge.close()
451
+ _cf_bridge = None
452
+ _print_log("cfquant normal bridge stopped")
453
+
454
+
455
+ def _publish_callback(event_name, obj):
456
+ try:
457
+ _print_log("cfquant raw qmt callback received event=%s %s" % (event_name, _callback_brief(obj)))
458
+ if _cf_bridge:
459
+ _cf_bridge.publish_callback_event(event_name, obj)
460
+ except Exception as e:
461
+ _print_log("cfquant callback publish failed event=%s error=%s" % (event_name, e))
462
+
463
+
464
+ def account_callback(ContextInfo, accountInfo):
465
+ _publish_callback("trader:on_stock_asset", accountInfo)
466
+
467
+
468
+ def order_callback(ContextInfo, orderInfo):
469
+ _publish_callback("trader:on_stock_order", orderInfo)
470
+
471
+
472
+ def deal_callback(ContextInfo, dealInfo):
473
+ _publish_callback("trader:on_stock_trade", dealInfo)
474
+
475
+
476
+ def trade_callback(ContextInfo, tradeInfo):
477
+ _publish_callback("trader:on_stock_trade", tradeInfo)
478
+
479
+
480
+ def position_callback(ContextInfo, positionInfo):
481
+ _publish_callback("trader:on_stock_position", positionInfo)
482
+
483
+
484
+ def order_error_callback(ContextInfo, orderError):
485
+ _publish_callback("trader:on_order_error", orderError)
486
+
487
+
488
+ def orderError_callback(ContextInfo, passOrderInfo, msg):
489
+ data = _object_to_callback_dict(passOrderInfo)
490
+ data["error_msg"] = msg
491
+ _publish_callback("trader:on_order_error", data)
492
+
493
+
494
+ def cancel_error_callback(ContextInfo, cancelError):
495
+ _publish_callback("trader:on_cancel_error", cancelError)
496
+
497
+
498
+ def cancelError_callback(ContextInfo, cancelError):
499
+ _publish_callback("trader:on_cancel_error", cancelError)
500
+
501
+
502
+ def order_stock_async_response_callback(ContextInfo, response):
503
+ _publish_callback("trader:on_order_stock_async_response", response)
504
+
505
+
506
+ def cancel_order_stock_async_response_callback(ContextInfo, response):
507
+ _publish_callback("trader:on_cancel_order_stock_async_response", response)