xtquant-share 1.1.2__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.
- xqshare/__init__.py +38 -0
- xqshare/auth.py +466 -0
- xqshare/client.py +682 -0
- xqshare/server.py +868 -0
- xqshare/tools/__init__.py +7 -0
- xqshare/tools/common.py +457 -0
- xqshare/tools/xtdata.py +98 -0
- xqshare/tools/xttrader.py +122 -0
- xtquant_share-1.1.2.dist-info/METADATA +756 -0
- xtquant_share-1.1.2.dist-info/RECORD +14 -0
- xtquant_share-1.1.2.dist-info/WHEEL +5 -0
- xtquant_share-1.1.2.dist-info/entry_points.txt +4 -0
- xtquant_share-1.1.2.dist-info/licenses/LICENSE +21 -0
- xtquant_share-1.1.2.dist-info/top_level.txt +1 -0
xqshare/tools/common.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
命令行工具共享模块
|
|
3
|
+
|
|
4
|
+
提供连接管理、参数解析、输出格式化等共享功能。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
import ast
|
|
10
|
+
import argparse
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
|
|
13
|
+
# 环境变量名
|
|
14
|
+
ENV_HOST = "XQSHARE_REMOTE_HOST"
|
|
15
|
+
ENV_PORT = "XQSHARE_REMOTE_PORT"
|
|
16
|
+
ENV_SECRET = "XQSHARE_CLIENT_SECRET"
|
|
17
|
+
ENV_CLIENT_ID = "XQSHARE_CLIENT_ID"
|
|
18
|
+
ENV_FORMAT = "XQSHARE_FORMAT"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@contextmanager
|
|
22
|
+
def create_client(host=None, port=None, secret=None, client_id=None, quiet=True):
|
|
23
|
+
"""创建客户端连接
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
quiet: 是否禁用控制台日志(默认 True)
|
|
27
|
+
"""
|
|
28
|
+
if quiet:
|
|
29
|
+
# 在导入前设置静默模式
|
|
30
|
+
from xqshare.client import set_quiet_mode
|
|
31
|
+
set_quiet_mode(True)
|
|
32
|
+
|
|
33
|
+
from xqshare import XtQuantRemote
|
|
34
|
+
|
|
35
|
+
h = host or os.environ.get(ENV_HOST, "localhost")
|
|
36
|
+
p = port or int(os.environ.get(ENV_PORT, "18812"))
|
|
37
|
+
s = secret or os.environ.get(ENV_SECRET)
|
|
38
|
+
cid = client_id or os.environ.get(ENV_CLIENT_ID, "client-standard")
|
|
39
|
+
|
|
40
|
+
xt = XtQuantRemote(host=h, port=p, client_id=cid, client_secret=s)
|
|
41
|
+
try:
|
|
42
|
+
yield xt
|
|
43
|
+
finally:
|
|
44
|
+
xt.close()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# 已知的全局参数(不应传递给 API)
|
|
48
|
+
GLOBAL_ARGS = {
|
|
49
|
+
'host', 'port', 'secret', 'client_id',
|
|
50
|
+
'limit', 'n', 'verbose', 'v',
|
|
51
|
+
'output', 'o', 'format', 'f', 'compact',
|
|
52
|
+
'userdata_path', 'account_id', 'account_type',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# 带值的全局参数(需要提取值)
|
|
56
|
+
GLOBAL_ARGS_WITH_VALUE = {
|
|
57
|
+
'host', 'port', 'secret', 'client_id',
|
|
58
|
+
'limit', 'output', 'o', 'format', 'f',
|
|
59
|
+
'userdata_path', 'account_id', 'account_type',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# 标志型全局参数(无值,布尔类型)
|
|
63
|
+
GLOBAL_ARGS_FLAG = {
|
|
64
|
+
'verbose', 'v', 'compact',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def extract_global_args(args_list):
|
|
69
|
+
"""从参数列表中提取后置的全局参数
|
|
70
|
+
|
|
71
|
+
支持用户将全局参数放在 command 之后,例如:
|
|
72
|
+
xtdata get_stock_list --sector-name "沪深A股" --compact --limit 10
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
args_list: 原始参数列表
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
tuple: (过滤后的参数列表, 提取到的全局参数字典)
|
|
79
|
+
"""
|
|
80
|
+
# 短参数映射:-f -> format, -n -> limit, -o -> output, -v -> verbose
|
|
81
|
+
SHORT_ARG_MAP = {
|
|
82
|
+
'f': 'format',
|
|
83
|
+
'n': 'limit',
|
|
84
|
+
'o': 'output',
|
|
85
|
+
'v': 'verbose',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
extracted = {}
|
|
89
|
+
filtered = []
|
|
90
|
+
i = 0
|
|
91
|
+
|
|
92
|
+
while i < len(args_list):
|
|
93
|
+
arg = args_list[i]
|
|
94
|
+
|
|
95
|
+
# 处理长参数 --xxx
|
|
96
|
+
if arg.startswith('--'):
|
|
97
|
+
key = arg[2:].replace('-', '_')
|
|
98
|
+
|
|
99
|
+
if key in GLOBAL_ARGS:
|
|
100
|
+
if key in GLOBAL_ARGS_WITH_VALUE:
|
|
101
|
+
# 带值的参数
|
|
102
|
+
if i + 1 < len(args_list) and not args_list[i + 1].startswith('-'):
|
|
103
|
+
extracted[key] = args_list[i + 1]
|
|
104
|
+
i += 2
|
|
105
|
+
else:
|
|
106
|
+
extracted[key] = True
|
|
107
|
+
i += 1
|
|
108
|
+
elif key in GLOBAL_ARGS_FLAG:
|
|
109
|
+
# 标志型参数
|
|
110
|
+
extracted[key] = True
|
|
111
|
+
i += 1
|
|
112
|
+
else:
|
|
113
|
+
i += 1
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# 处理短参数 -x
|
|
117
|
+
elif arg.startswith('-') and len(arg) == 2 and arg[1] in SHORT_ARG_MAP:
|
|
118
|
+
key = SHORT_ARG_MAP[arg[1]]
|
|
119
|
+
|
|
120
|
+
if i + 1 < len(args_list) and not args_list[i + 1].startswith('-'):
|
|
121
|
+
extracted[key] = args_list[i + 1]
|
|
122
|
+
i += 2
|
|
123
|
+
else:
|
|
124
|
+
extracted[key] = True
|
|
125
|
+
i += 1
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
filtered.append(arg)
|
|
129
|
+
i += 1
|
|
130
|
+
|
|
131
|
+
return filtered, extracted
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def parse_kv_args(args_list):
|
|
135
|
+
"""解析 --key value 格式的参数
|
|
136
|
+
|
|
137
|
+
自动过滤已知的全局 CLI 参数,防止它们被误传给 API 函数。
|
|
138
|
+
"""
|
|
139
|
+
params = {}
|
|
140
|
+
i = 0
|
|
141
|
+
while i < len(args_list):
|
|
142
|
+
arg = args_list[i]
|
|
143
|
+
if arg.startswith('--'):
|
|
144
|
+
key = arg[2:]
|
|
145
|
+
# 将连字符转换为下划线,匹配 Python 函数参数命名
|
|
146
|
+
key = key.replace('-', '_')
|
|
147
|
+
|
|
148
|
+
# 跳过全局参数
|
|
149
|
+
if key in GLOBAL_ARGS:
|
|
150
|
+
# 如果有值且不是下一个选项,也跳过值
|
|
151
|
+
if i + 1 < len(args_list) and not args_list[i + 1].startswith('--'):
|
|
152
|
+
i += 2
|
|
153
|
+
else:
|
|
154
|
+
i += 1
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
if i + 1 < len(args_list) and not args_list[i + 1].startswith('--'):
|
|
158
|
+
params[key] = args_list[i + 1]
|
|
159
|
+
i += 2
|
|
160
|
+
else:
|
|
161
|
+
params[key] = True
|
|
162
|
+
i += 1
|
|
163
|
+
else:
|
|
164
|
+
i += 1
|
|
165
|
+
return params
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def preprocess_params(params):
|
|
169
|
+
"""预处理复杂参数(JSON/Python 字面量反序列化)"""
|
|
170
|
+
# 需要转换为整数的参数名
|
|
171
|
+
INT_PARAMS = {'count', 'limit', 'n', 'offset'}
|
|
172
|
+
|
|
173
|
+
for key, value in params.items():
|
|
174
|
+
if not isinstance(value, str):
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
# 列表/字典:使用 ast.literal_eval
|
|
178
|
+
if value.startswith('[') or value.startswith('{'):
|
|
179
|
+
try:
|
|
180
|
+
params[key] = ast.literal_eval(value)
|
|
181
|
+
continue
|
|
182
|
+
except (ValueError, SyntaxError):
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
# 布尔值
|
|
186
|
+
if value.lower() == 'true':
|
|
187
|
+
params[key] = True
|
|
188
|
+
continue
|
|
189
|
+
if value.lower() == 'false':
|
|
190
|
+
params[key] = False
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
# 特定的整数参数
|
|
194
|
+
if key in INT_PARAMS:
|
|
195
|
+
try:
|
|
196
|
+
params[key] = int(value)
|
|
197
|
+
continue
|
|
198
|
+
except ValueError:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
# StockAccount 参数特殊处理
|
|
202
|
+
if 'account' in params and isinstance(params['account'], dict):
|
|
203
|
+
try:
|
|
204
|
+
from xtquant.xttype import StockAccount
|
|
205
|
+
params['account'] = StockAccount(**params['account'])
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
return params
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _is_remote_object(obj):
|
|
213
|
+
"""判断是否为 RPyC 远程对象(netref)
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
obj: 待检测对象
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
bool: 如果是远程对象返回 True
|
|
220
|
+
"""
|
|
221
|
+
module = type(obj).__module__
|
|
222
|
+
return 'rpyc' in module or 'netref' in module
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _format_as_json(result):
|
|
226
|
+
"""将结果转换为 JSON 可序列化格式"""
|
|
227
|
+
import pandas as pd
|
|
228
|
+
from datetime import datetime
|
|
229
|
+
import io
|
|
230
|
+
|
|
231
|
+
if result is None:
|
|
232
|
+
return None
|
|
233
|
+
elif isinstance(result, pd.DataFrame):
|
|
234
|
+
if _is_remote_object(result):
|
|
235
|
+
# 远程 DataFrame:用 to_csv 一次拉取,本地解析
|
|
236
|
+
csv_str = result.to_csv(index=True)
|
|
237
|
+
local_df = pd.read_csv(io.StringIO(csv_str), index_col=0)
|
|
238
|
+
return local_df.to_dict(orient='records')
|
|
239
|
+
else:
|
|
240
|
+
# 本地 DataFrame:直接转换
|
|
241
|
+
return result.to_dict(orient='records')
|
|
242
|
+
elif isinstance(result, dict):
|
|
243
|
+
if _is_remote_object(result):
|
|
244
|
+
result = dict(result)
|
|
245
|
+
return {k: _format_as_json(v) for k, v in result.items()}
|
|
246
|
+
elif isinstance(result, (list, tuple)):
|
|
247
|
+
if _is_remote_object(result):
|
|
248
|
+
result = list(result)
|
|
249
|
+
return [_format_as_json(item) for item in result]
|
|
250
|
+
elif hasattr(result, '__dict__'):
|
|
251
|
+
# 对于远程对象,直接使用 __dict__ 避免 dir() 遍历
|
|
252
|
+
if _is_remote_object(result):
|
|
253
|
+
try:
|
|
254
|
+
attrs = result.__dict__
|
|
255
|
+
if isinstance(attrs, dict):
|
|
256
|
+
return {k: _format_as_json(v) for k, v in attrs.items()
|
|
257
|
+
if not k.startswith('_')}
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
# 本地对象:使用 dir() 遍历
|
|
261
|
+
return {attr: _format_as_json(getattr(result, attr))
|
|
262
|
+
for attr in dir(result)
|
|
263
|
+
if not attr.startswith('_') and not callable(getattr(result, attr))}
|
|
264
|
+
elif isinstance(result, datetime):
|
|
265
|
+
return result.isoformat()
|
|
266
|
+
else:
|
|
267
|
+
return result
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _format_as_text(result, limit=None):
|
|
271
|
+
"""将结果格式化为文本字符串"""
|
|
272
|
+
import pandas as pd
|
|
273
|
+
from pprint import pformat
|
|
274
|
+
import io
|
|
275
|
+
|
|
276
|
+
output = io.StringIO()
|
|
277
|
+
|
|
278
|
+
if result is None:
|
|
279
|
+
output.write("None")
|
|
280
|
+
elif isinstance(result, pd.DataFrame):
|
|
281
|
+
pd.set_option('display.max_columns', None)
|
|
282
|
+
pd.set_option('display.width', None)
|
|
283
|
+
output.write(result.to_string())
|
|
284
|
+
elif isinstance(result, dict):
|
|
285
|
+
has_dataframe = any(isinstance(v, pd.DataFrame) for v in result.values())
|
|
286
|
+
if has_dataframe:
|
|
287
|
+
for key, value in result.items():
|
|
288
|
+
output.write(f"\n=== {key} ===\n")
|
|
289
|
+
if isinstance(value, pd.DataFrame):
|
|
290
|
+
output.write(value.to_string())
|
|
291
|
+
else:
|
|
292
|
+
output.write(pformat(value))
|
|
293
|
+
else:
|
|
294
|
+
output.write(pformat(result))
|
|
295
|
+
elif isinstance(result, (list, tuple)):
|
|
296
|
+
total = len(result)
|
|
297
|
+
display = result[:limit] if limit and total > limit else result
|
|
298
|
+
for i, item in enumerate(display, 1):
|
|
299
|
+
if hasattr(item, '__dict__') or hasattr(item, '__slots__'):
|
|
300
|
+
output.write(f"[{i}] {_format_object_attrs(item)}\n")
|
|
301
|
+
else:
|
|
302
|
+
output.write(f"[{i}] {item}\n")
|
|
303
|
+
if limit and total > limit:
|
|
304
|
+
output.write(f"\n# 共 {total} 条,已显示前 {limit} 条")
|
|
305
|
+
else:
|
|
306
|
+
output.write(pformat(result))
|
|
307
|
+
|
|
308
|
+
return output.getvalue()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _format_as_csv(result):
|
|
312
|
+
"""将结果格式化为 CSV 字符串
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
result: API 返回结果
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
CSV 格式的字符串
|
|
319
|
+
"""
|
|
320
|
+
import pandas as pd
|
|
321
|
+
from pprint import pformat
|
|
322
|
+
|
|
323
|
+
if isinstance(result, pd.DataFrame):
|
|
324
|
+
return result.to_csv(index=True)
|
|
325
|
+
elif isinstance(result, dict) and any(isinstance(v, pd.DataFrame) for v in result.values()):
|
|
326
|
+
lines = []
|
|
327
|
+
for key, value in result.items():
|
|
328
|
+
if isinstance(value, pd.DataFrame):
|
|
329
|
+
lines.append(f"# {key}")
|
|
330
|
+
lines.append(value.to_csv(index=True))
|
|
331
|
+
else:
|
|
332
|
+
lines.append(f"# {key}: {value}")
|
|
333
|
+
return "\n".join(lines) + "\n"
|
|
334
|
+
else:
|
|
335
|
+
return pformat(result) + "\n"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def format_output(result, limit=None, output=None, output_format='text', compact=False):
|
|
339
|
+
"""根据返回类型自动格式化输出
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
result: API 返回结果
|
|
343
|
+
limit: 列表/元组输出数量限制,None 表示不限制
|
|
344
|
+
output: 输出文件路径,None 表示输出到控制台
|
|
345
|
+
output_format: 输出格式(text/json/csv),默认 text
|
|
346
|
+
compact: 是否使用紧凑模式(仅对 json 格式有效),默认 False
|
|
347
|
+
"""
|
|
348
|
+
from pathlib import Path
|
|
349
|
+
|
|
350
|
+
if output_format == 'json':
|
|
351
|
+
data = _format_as_json(result)
|
|
352
|
+
indent = None if compact else 2
|
|
353
|
+
content = json.dumps(data, ensure_ascii=False, indent=indent)
|
|
354
|
+
|
|
355
|
+
elif output_format == 'csv':
|
|
356
|
+
content = _format_as_csv(result)
|
|
357
|
+
|
|
358
|
+
else:
|
|
359
|
+
content = _format_as_text(result, limit)
|
|
360
|
+
|
|
361
|
+
if output:
|
|
362
|
+
Path(output).parent.mkdir(parents=True, exist_ok=True)
|
|
363
|
+
with open(output, 'w', encoding='utf-8') as f:
|
|
364
|
+
f.write(content)
|
|
365
|
+
print(f"结果已保存到: {output}")
|
|
366
|
+
else:
|
|
367
|
+
print(content)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _format_object_attrs(obj):
|
|
371
|
+
"""将对象属性提取为字典格式"""
|
|
372
|
+
result = {}
|
|
373
|
+
# 使用 dir() 遍历所有属性
|
|
374
|
+
for attr in dir(obj):
|
|
375
|
+
if attr.startswith('_'):
|
|
376
|
+
continue
|
|
377
|
+
try:
|
|
378
|
+
value = getattr(obj, attr)
|
|
379
|
+
# 排除方法和函数
|
|
380
|
+
if callable(value):
|
|
381
|
+
continue
|
|
382
|
+
result[attr] = value
|
|
383
|
+
except Exception:
|
|
384
|
+
pass
|
|
385
|
+
return result if result else str(obj)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def add_global_args(parser):
|
|
389
|
+
"""添加全局参数"""
|
|
390
|
+
parser.add_argument("--host", help="服务端地址,可通过 XQSHARE_REMOTE_HOST 环境变量设置")
|
|
391
|
+
parser.add_argument("--port", type=int, help="服务端端口,可通过 XQSHARE_REMOTE_PORT 环境变量设置")
|
|
392
|
+
parser.add_argument("--secret", help="认证密钥,可通过 XQSHARE_CLIENT_SECRET 环境变量设置")
|
|
393
|
+
parser.add_argument("--client-id", dest="client_id", help="客户端标识,可通过 XQSHARE_CLIENT_ID 环境变量设置")
|
|
394
|
+
parser.add_argument("--limit", "-n", type=int, default=50, help="列表输出数量限制 (默认: 50,0 表示不限制)")
|
|
395
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="显示详细日志")
|
|
396
|
+
parser.add_argument("--output", "-o", help="输出文件路径")
|
|
397
|
+
parser.add_argument("--format", "-f", dest="output_format", choices=["text", "json", "csv"],
|
|
398
|
+
help="输出格式 (环境变量: XQSHARE_FORMAT, 默认: text)")
|
|
399
|
+
parser.add_argument("--compact", action="store_true", help="紧凑模式输出 (仅对 json 格式有效)")
|
|
400
|
+
return parser
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def add_trader_args(parser):
|
|
404
|
+
"""添加交易相关参数"""
|
|
405
|
+
parser.add_argument("--userdata-path", help="QMT客户端 userdata_mini 目录路径 (环境变量: QMT_USERDATA_PATH)")
|
|
406
|
+
parser.add_argument("--account-id", help="资金账号 (环境变量: QMT_ACCOUNT_ID)")
|
|
407
|
+
parser.add_argument("--account-type", default="STOCK", choices=["STOCK", "CREDIT", "FUTURE", "HUGANGTONG", "SHENGANGTONG"],
|
|
408
|
+
help="账户类型 (默认: STOCK)")
|
|
409
|
+
return parser
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def create_trader(xt, userdata_path, account_id, account_type):
|
|
413
|
+
"""创建交易实例
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
xt: XtQuantRemote 实例
|
|
417
|
+
userdata_path: userdata_mini 目录路径
|
|
418
|
+
account_id: 资金账号
|
|
419
|
+
account_type: 账户类型
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
(trader, account) 元组
|
|
423
|
+
"""
|
|
424
|
+
# 从环境变量获取默认值
|
|
425
|
+
if not userdata_path:
|
|
426
|
+
import os
|
|
427
|
+
userdata_path = os.environ.get("QMT_USERDATA_PATH")
|
|
428
|
+
if not account_id:
|
|
429
|
+
import os
|
|
430
|
+
account_id = os.environ.get("QMT_ACCOUNT_ID")
|
|
431
|
+
|
|
432
|
+
if not userdata_path:
|
|
433
|
+
raise ValueError("必须提供 userdata_path 参数或设置 QMT_USERDATA_PATH 环境变量")
|
|
434
|
+
if not account_id:
|
|
435
|
+
raise ValueError("必须提供 account_id 参数或设置 QMT_ACCOUNT_ID 环境变量")
|
|
436
|
+
|
|
437
|
+
# 创建交易实例(使用服务端方法)
|
|
438
|
+
trader = xt.create_trader(userdata_path)
|
|
439
|
+
|
|
440
|
+
# 启动交易线程
|
|
441
|
+
trader.start()
|
|
442
|
+
|
|
443
|
+
# 创建账户对象
|
|
444
|
+
account = xt.xttype.StockAccount(account_id, account_type)
|
|
445
|
+
|
|
446
|
+
# 连接交易服务器
|
|
447
|
+
result = trader.connect()
|
|
448
|
+
if result != 0:
|
|
449
|
+
error_codes = {
|
|
450
|
+
-1: "交易服务器未连接",
|
|
451
|
+
-2: "账号未登录",
|
|
452
|
+
-3: "请求超时",
|
|
453
|
+
-4: "资金账号不存在",
|
|
454
|
+
}
|
|
455
|
+
raise ConnectionError(f"连接失败: {error_codes.get(result, f'错误码 {result}')}")
|
|
456
|
+
|
|
457
|
+
return trader, account
|
xqshare/tools/xtdata.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
xtdata - 行情数据命令行工具
|
|
4
|
+
|
|
5
|
+
动态映射到 xtdata API 函数。
|
|
6
|
+
|
|
7
|
+
参数规则:
|
|
8
|
+
- 工具参数(--host, --port, --limit 等)必须放在 command 之前
|
|
9
|
+
- API 函数参数放在 command 之后
|
|
10
|
+
|
|
11
|
+
使用示例:
|
|
12
|
+
# 工具参数在 command 之前
|
|
13
|
+
xtdata --limit 100 get_stock_list_in_sector --sector-name "沪深A股"
|
|
14
|
+
xtdata --host 192.168.1.100 get_full_tick --codes "000001.SZ"
|
|
15
|
+
|
|
16
|
+
# 使用环境变量配置连接,只传 API 参数
|
|
17
|
+
xtdata get_market_data_ex --codes "000001.SZ" --period 1d
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
import os
|
|
22
|
+
import argparse
|
|
23
|
+
from .common import (
|
|
24
|
+
create_client, parse_kv_args, preprocess_params,
|
|
25
|
+
format_output, add_global_args, extract_global_args, ENV_FORMAT
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main():
|
|
30
|
+
parser = argparse.ArgumentParser(
|
|
31
|
+
prog="xtdata",
|
|
32
|
+
description="xtquant 行情数据命令行工具",
|
|
33
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
34
|
+
epilog="""
|
|
35
|
+
参数规则:
|
|
36
|
+
工具参数 (--host, --port, --limit 等) 必须放在 command 之前
|
|
37
|
+
API 函数参数放在 command 之后
|
|
38
|
+
|
|
39
|
+
限制:
|
|
40
|
+
不支持以 subscribe 开头的命令(订阅功能需要回调)
|
|
41
|
+
不支持 callback 参数(回调功能需要使用 Python API)
|
|
42
|
+
|
|
43
|
+
示例:
|
|
44
|
+
xtdata --limit 100 get_stock_list_in_sector --sector-name "沪深A股"
|
|
45
|
+
xtdata --host 192.168.1.100 get_full_tick --codes "000001.SZ"
|
|
46
|
+
"""
|
|
47
|
+
)
|
|
48
|
+
add_global_args(parser)
|
|
49
|
+
parser.add_argument("command", help="API函数名")
|
|
50
|
+
parser.add_argument("args", nargs=argparse.REMAINDER, help="函数参数 (--key value)")
|
|
51
|
+
|
|
52
|
+
args = parser.parse_args()
|
|
53
|
+
|
|
54
|
+
# 补充环境变量默认值
|
|
55
|
+
output_format = args.output_format or os.environ.get(ENV_FORMAT, "text")
|
|
56
|
+
|
|
57
|
+
# 提取后置的全局参数(支持放在 command 之后)
|
|
58
|
+
args.args, global_overrides = extract_global_args(args.args)
|
|
59
|
+
if global_overrides:
|
|
60
|
+
if 'compact' in global_overrides:
|
|
61
|
+
args.compact = True
|
|
62
|
+
if 'verbose' in global_overrides or 'v' in global_overrides:
|
|
63
|
+
args.verbose = True
|
|
64
|
+
if 'output' in global_overrides or 'o' in global_overrides:
|
|
65
|
+
args.output = global_overrides.get('output') or global_overrides.get('o')
|
|
66
|
+
if 'limit' in global_overrides or 'n' in global_overrides:
|
|
67
|
+
args.limit = int(global_overrides.get('limit') or global_overrides.get('n', 0))
|
|
68
|
+
if 'format' in global_overrides or 'f' in global_overrides:
|
|
69
|
+
output_format = global_overrides.get('format') or global_overrides.get('f')
|
|
70
|
+
|
|
71
|
+
# 拒绝订阅相关命令
|
|
72
|
+
if args.command.startswith('subscribe'):
|
|
73
|
+
print(f"错误: 命令行工具不支持订阅功能 '{args.command}'", file=sys.stderr)
|
|
74
|
+
print("提示: 订阅功能需要回调函数支持,请使用 Python API 或 examples 脚本", file=sys.stderr)
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
|
|
77
|
+
with create_client(args.host, args.port, args.secret, args.client_id, quiet=not args.verbose) as xt:
|
|
78
|
+
func = getattr(xt.xtdata, args.command, None)
|
|
79
|
+
if func is None:
|
|
80
|
+
print(f"错误: 未知命令 '{args.command}'", file=sys.stderr)
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
params = parse_kv_args(args.args)
|
|
84
|
+
params = preprocess_params(params)
|
|
85
|
+
|
|
86
|
+
# 检查 callback 参数
|
|
87
|
+
if 'callback' in params:
|
|
88
|
+
print("错误: 命令行工具不支持回调参数 'callback'", file=sys.stderr)
|
|
89
|
+
print("提示: 回调功能需要使用 Python API 或 examples 脚本", file=sys.stderr)
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
result = func(**params)
|
|
93
|
+
limit = None if args.limit == 0 else args.limit
|
|
94
|
+
format_output(result, limit, args.output, output_format, args.compact)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
xttrader - 交易命令行工具
|
|
4
|
+
|
|
5
|
+
支持 XtQuantTrader 的交易操作。
|
|
6
|
+
|
|
7
|
+
参数规则:
|
|
8
|
+
- 工具参数(--host, --port, --account-id 等)必须放在 command 之前
|
|
9
|
+
- API 函数参数放在 command 之后
|
|
10
|
+
- account 参数会自动补充,无需手动传递
|
|
11
|
+
|
|
12
|
+
使用示例:
|
|
13
|
+
# 查询持仓
|
|
14
|
+
xttrader --account-id "12345678" --userdata-path "C:\\\\QMT\\\\userdata_mini" query_stock_positions
|
|
15
|
+
|
|
16
|
+
# 查询资产
|
|
17
|
+
xttrader --account-id "12345678" query_stock_asset
|
|
18
|
+
|
|
19
|
+
# 下单
|
|
20
|
+
xttrader --account-id "12345678" order_stock --stock-code "000001.SZ" --order-type 23 --order-volume 100
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
import os
|
|
25
|
+
import argparse
|
|
26
|
+
from .common import (
|
|
27
|
+
create_client, parse_kv_args, preprocess_params,
|
|
28
|
+
format_output, add_global_args, add_trader_args, create_trader,
|
|
29
|
+
extract_global_args, ENV_FORMAT
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main():
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
prog="xttrader",
|
|
36
|
+
description="xtquant 交易命令行工具",
|
|
37
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
38
|
+
epilog="""
|
|
39
|
+
参数规则:
|
|
40
|
+
工具参数 (--host, --port, --account-id 等) 必须放在 command 之前
|
|
41
|
+
API 函数参数放在 command 之后
|
|
42
|
+
account 参数会自动补充,无需手动传递
|
|
43
|
+
|
|
44
|
+
限制:
|
|
45
|
+
不支持以 subscribe/register 开头的命令(订阅/回调功能)
|
|
46
|
+
不支持 callback 参数(回调功能需要使用 Python API)
|
|
47
|
+
|
|
48
|
+
常用命令:
|
|
49
|
+
query_stock_positions - 查询持仓
|
|
50
|
+
query_stock_asset - 查询资产
|
|
51
|
+
order_stock - 下单
|
|
52
|
+
order_cancel - 撤单
|
|
53
|
+
|
|
54
|
+
示例:
|
|
55
|
+
xttrader --account-id "12345678" query_stock_positions
|
|
56
|
+
xttrader --account-id "12345678" order_stock --stock-code "000001.SZ" --order-type 23 --order-volume 100
|
|
57
|
+
"""
|
|
58
|
+
)
|
|
59
|
+
add_global_args(parser)
|
|
60
|
+
add_trader_args(parser)
|
|
61
|
+
parser.add_argument("command", help="API函数名")
|
|
62
|
+
parser.add_argument("args", nargs=argparse.REMAINDER, help="函数参数 (--key value)")
|
|
63
|
+
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
|
|
66
|
+
# 补充环境变量默认值
|
|
67
|
+
output_format = args.output_format or os.environ.get(ENV_FORMAT, "text")
|
|
68
|
+
|
|
69
|
+
# 提取后置的全局参数(支持放在 command 之后)
|
|
70
|
+
args.args, global_overrides = extract_global_args(args.args)
|
|
71
|
+
if global_overrides:
|
|
72
|
+
if 'compact' in global_overrides:
|
|
73
|
+
args.compact = True
|
|
74
|
+
if 'verbose' in global_overrides or 'v' in global_overrides:
|
|
75
|
+
args.verbose = True
|
|
76
|
+
if 'output' in global_overrides or 'o' in global_overrides:
|
|
77
|
+
args.output = global_overrides.get('output') or global_overrides.get('o')
|
|
78
|
+
if 'limit' in global_overrides or 'n' in global_overrides:
|
|
79
|
+
args.limit = int(global_overrides.get('limit') or global_overrides.get('n', 0))
|
|
80
|
+
if 'format' in global_overrides or 'f' in global_overrides:
|
|
81
|
+
output_format = global_overrides.get('format') or global_overrides.get('f')
|
|
82
|
+
|
|
83
|
+
# 拒绝订阅相关命令
|
|
84
|
+
if args.command.startswith('subscribe') or args.command.startswith('register'):
|
|
85
|
+
print(f"错误: 命令行工具不支持订阅/回调功能 '{args.command}'", file=sys.stderr)
|
|
86
|
+
print("提示: 订阅功能需要回调函数支持,请使用 Python API 或 examples 脚本", file=sys.stderr)
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
with create_client(args.host, args.port, args.secret, args.client_id, quiet=not args.verbose) as xt:
|
|
90
|
+
try:
|
|
91
|
+
trader, account = create_trader(xt, args.userdata_path, args.account_id, args.account_type)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
func = getattr(trader, args.command, None)
|
|
98
|
+
if func is None:
|
|
99
|
+
print(f"错误: 未知命令 '{args.command}'", file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
params = parse_kv_args(args.args)
|
|
103
|
+
params = preprocess_params(params)
|
|
104
|
+
|
|
105
|
+
# 检查 callback 参数
|
|
106
|
+
if 'callback' in params:
|
|
107
|
+
print("错误: 命令行工具不支持回调参数 'callback'", file=sys.stderr)
|
|
108
|
+
print("提示: 回调功能需要使用 Python API 或 examples 脚本", file=sys.stderr)
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
# 自动添加 account 参数
|
|
112
|
+
params['account'] = account
|
|
113
|
+
|
|
114
|
+
result = func(**params)
|
|
115
|
+
limit = None if args.limit == 0 else args.limit
|
|
116
|
+
format_output(result, limit, args.output, output_format, args.compact)
|
|
117
|
+
finally:
|
|
118
|
+
trader.stop()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
main()
|