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.
@@ -0,0 +1,7 @@
1
+ """
2
+ 命令行工具模块
3
+
4
+ 提供 xtdata 和 xttrader 命令行工具。
5
+ """
6
+
7
+ __all__ = ["common", "xtdata", "xttrader"]
@@ -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
@@ -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()