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