Repository navigation
Expand file tree
/
Copy pathconfig.py
More file actions
740 lines (616 loc) · 32.1 KB
/
Copy pathconfig.py
File metadata and controls
740 lines (616 loc) · 32.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672<
8000
/div>
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
"""
配置参数管理模块,集中管理所有可配置参数
优化版本:增强止盈止损配置的清晰度
"""
import os
import json
from datetime import datetime
# ======================= 系统配置 =======================
# 调试开关
DEBUG = False
DEBUG_SIMU_STOCK_DATA= False
LOG_LEVEL = "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_FILE = "qmt_trading.log"
LOG_MAX_SIZE = 10 * 1024 * 1024 # 10MB
LOG_BACKUP_COUNT = 5 # 保留5个备份文件
# Web访问日志配置
ENABLE_WEB_ACCESS_LOG = True # 是否启用web访问日志
WEB_ACCESS_LOG_LEVEL = "WARNING" # web访问日志级别(DEBUG/INFO/WARNING)
WEB_ACCESS_LOG_EXCLUDE_PATHS = ["/api/sse", "/api/positions/stream", "/<path:filename>"] # 排除的路径(静态文件、SSE长连接等)
WEB_ACCESS_LOG_INCLUDE_TIMING = True # 是否包含请求耗时统计
# 循环日志优化配置
VERBOSE_LOOP_LOGGING = False # 是否输出详细的循环日志(开启后会在DEBUG模式下输出每次循环的开始/结束)
ENABLE_HEARTBEAT_LOG = True # 是否启用系统心跳日志(定期输出运行状态摘要)
HEARTBEAT_INTERVAL = 1800 # 心跳日志间隔(秒,默认1800=30分钟)
# ======================= 功能开关 =======================
def _env_bool(name: str, default: bool) -> bool:
"""从环境变量读 bool 开关;未设置或值非法时返回 default。
用于让 miniqmt.bat / scripts/_launcher.py 在启动时显式控制模式,
避免菜单 [7](实盘)启动后进程默认仍是模拟,需要用户在 web UI
手动切换的尴尬。
"""
v = os.environ.get(name)
if v is None or v == "":
return default
return str(v).strip().lower() in ("1", "true", "yes", "on", "y", "t")
# 模拟交易模式开关:环境变量 ENABLE_SIMULATION_MODE 优先(启动器控制),
# 否则用源码默认 True(避免开发者本地误连实盘)。
ENABLE_SIMULATION_MODE = _env_bool("ENABLE_SIMULATION_MODE", True)
ENABLE_MONITORING = False # 控制前端UI监控状态
ENABLE_AUTO_TRADING = False # 动态止盈止损自动执行开关(不影响网格交易)
ENABLE_ALLOW_BUY = True # 是否允许买入操作
ENABLE_ALLOW_SELL = True # 是否允许卖出操作
# 策略功能模块开关(独立控制)
ENABLE_DYNAMIC_STOP_PROFIT = True # 止盈止损功能开关(信号检测)
# ENABLE_GRID_TRADING 已移至第470行的新网格交易配置区域
# 重要说明:
# - ENABLE_AUTO_TRADING:控制止盈止损信号的自动执行
# - ENABLE_GRID_TRADING:控制网格交易的检测和执行(独立开关,互不影响)
# - ENABLE_DYNAMIC_STOP_PROFIT:控制止盈止损信号的检测
# baostock 降级配置(防止网络阻塞)
BAOSTOCK_LOGIN_TIMEOUT = 5 # baostock login 超时秒数(防止无超时阻塞)
BAOSTOCK_RETRY_COOLDOWN = 300 # 连续失败后冷却时间(秒,默认5分钟)
BAOSTOCK_MAX_CONSECUTIVE_FAILURES = 3 # 连续失败N次后进入冷却期
# 其他功能开关
ENABLE_DATA_SYNC = True # 是否启用数据同步
ENABLE_POSITION_MONITOR = True # 是否启用持仓监控
ENABLE_LOG_CLEANUP = True # 是否启用日志清理
# QMT API配置
USE_SYNC_ORDER_API = False # 使用同步下单接口(True)还是异步接口(False)
# False: 使用order_stock_async()返回seq号,需要回调映射
# True: 使用order_stock()直接返回order_id
QMT_CONNECT_TIMEOUT = 30 # QMT交易接口连接超时(秒),避免connect卡死
# 注释说明:
# - 策略线程始终运行,进行信号检测和监控
# - ENABLE_AUTO_TRADING 控制是否执行检测到的交易信号
# - ENABLE_DYNAMIC_STOP_PROFIT 控制止盈止损模块
# - ENABLE_GRID_TRADING 控制网格交易模块
# - ENABLE_SIMULATION_MODE 控制交易执行方式(模拟/实盘)
# ======================= 数据配置 =======================
# 历史数据存储路径
DATA_DIR = "data"
# 数据库配置(如果使用SQLite)
DB_PATH = os.path.join(DATA_DIR, "trading.db")
# 行情数据周期
PERIODS = ["1d", "1h", "30m", "15m", "5m", "1m"]
# 默认使用的周期
DEFAULT_PERIOD = "1d"
# 历史数据初始获取天数
INITIAL_DAYS = 365
# 定时更新间隔(秒)
UPDATE_INTERVAL = 60
# 备选池股票文件路径
STOCK2BUY_FILE = os.path.join(DATA_DIR, "stock2buy.json")
# 实时数据源配置
REALTIME_DATA_CONFIG = {
'enable_multi_source': True,
'health_check_interval': 30,
'source_timeout': 5,
'max_error_count': 3,
'preferred_sources': [
'XtQuant',
'Mootdx'
]
}
# ======================= 交易配置 =======================
# 交易账号信息(从外部文件读取,避免敏感信息硬编码)
ACCOUNT_CONFIG_FILE = "account_config.json"
# QMT路径配置优先级:
# 1. 环境变量 QMT_PATH
# 2. account_config.json 中的 qmt_path 字段
# 3. 默认值(自动检测常见路径)
DEFAULT_QMT_PATHS = [
r'C:/QMT/userdata_mini',
r'C:/QMT1/userdata_mini',
r'C:/QMT2/userdata_mini',
r'C:/光大证券金阳光QMT实盘/userdata_mini',
r'D:/QMT/userdata_mini',
r'D:/QMT1/userdata_mini',
r'D:/QMT2/userdata_mini',
r'D:/光大证券金阳光QMT实盘/userdata_mini',
]
def _read_raw_account_config():
"""读取原始 account_config.json 文件内容(无任何账号选择逻辑)。"""
try:
with open(ACCOUNT_CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
return {"account_id": "", "account_type": "STOCK"}
except Exception as e:
print(f"读取账户配置文件失败: {str(e)}")
return {"account_id": "", "account_type": "STOCK"}
def get_account_config():
"""
返回"当前进程"实际使用的账号配置。
选择规则(向后兼容):
- 设置了环境变量 QMT_ACCOUNT_ID 且能在 accounts 列表中找到匹配项 → 返回该子配置;
- 否则 → 返回 account_config.json 顶层字段(与改动前完全一致)。
返回结构始终保留顶层的 accounts 字段,便于下游访问全量列表。
"""
raw = _read_raw_account_config()
target_id = os.environ.get('QMT_ACCOUNT_ID', '').strip()
if target_id:
for acc in (raw.get("accounts") or []):
if acc.get("account_id") == target_id:
# 合并:以子账号字段为主,缺失字段用顶层兜底
merged = {k: v for k, v in raw.items() if k != "accounts"}
merged.update({k: v for k, v in acc.items() if v is not None})
merged["accounts"] = raw.get("accounts", [])
return merged
return raw
def get_all_accounts_config():
"""
返回所有账号配置列表,支持单账号和多账号两种格式。
格式 1(单账号,向后兼容):
{"account_id": "xxx", "account_type": "STOCK", "qmt_path": "..."}
→ 返回 [{"account_id": "xxx", ...}]
格式 2(多账号):
{"account_id": "xxx", ..., "accounts": [{"account_id": "aaa", ...}, ...]}
→ 返回 accounts 列表
Returns:
list[dict]: 账号配置列表,至少包含一个元素
"""
raw = _read_raw_account_config()
accounts = raw.get("accounts")
if accounts and isinstance(accounts, list):
valid = [a for a in accounts if a.get("account_id")]
if valid:
return valid
# 兜底:单账号模式(剥离 accounts 字段,避免污染)
fallback = {k: v for k, v in raw.items() if k != "accounts"}
return [fallback]
def get_qmt_path():
"""
获取QMT路径 - 灵活配置方案
优先级:
1. 环境变量 QMT_PATH
2. account_config.json 中的 qmt_path 字段
3. 自动检测常见路径(存在性检查)
4. 使用第一个默认路径(兜底)
Returns:
str: QMT userdata_mini 路径
"""
import os
# 优先级1: 环境变量
env_path = os.environ.get('QMT_PATH')
if env_path and os.path.exists(env_path):
print(f"[QMT_PATH] Using environment variable: {env_path}")
return env_path
# 优先级2: 配置文件中的qmt_path字段
account_config = get_account_config()
config_path = account_config.get('qmt_path')
if config_path and os.path.exists(config_path):
print(f"[QMT_PATH] Using path from account_config.json: {config_path}")
return config_path
# 优先级3: 自动检测常见路径
for path in DEFAULT_QMT_PATHS:
if os.path.exists(path):
print(f"[QMT_PATH] Auto-detected path: {path}")
return path
# 优先级4: 兜底,返回第一个默认路径(即使不存在,后续会有错误提示)
fallback_path = DEFAULT_QMT_PATHS[0]
print(f"[QMT_PATH] WARNING: QMT path not found, using default: {fallback_path}")
print(f"[QMT_PATH] TIP: If QMT is installed elsewhere, set 'qmt_path' in account_config.json")
return fallback_path
# 动态获取QMT路径
QMT_PATH = get_qmt_path()
# 账号信息
ACCOUNT_CONFIG = get_account_config()
# ======================= XtQuantManager 集成开关 =======================
# False(默认): 直接使用本地 xtquant 接口(现有行为不变)
# True: 所有 xtquant 调用通过 XtQuantManager HTTP 服务路由
ENABLE_XTQUANT_MANAGER = False
# XtQuantManager 服务地址(ENABLE_XTQUANT_MANAGER=True 时生效)
XTQUANT_MANAGER_URL = "http://127.0.0.1:8888"
# XtQuantManager API Token(空字符串表示不验证)
XTQUANT_MANAGER_TOKEN = ""
# XtQuantManager HTTP 服务速率限制(次/分钟,0=不限速)
# 默认 600:持仓监控每3秒×3只股票=60次/分钟,HealthMonitor+资产查询约30次/分钟,
# 合计约90次/分钟;设600留足6倍余量,避免 HTTP 429 错误。
XTQUANT_MANAGER_RATE_LIMIT = 600
# ======================= 策略配置 =======================
# 仓位管理
POSITION_UNIT = 35000 # 每次买入金额
MAX_POSITION_VALUE = 70000 # 单只股票最大持仓金额
MAX_TOTAL_POSITION_RATIO = 0.95 # 最大总持仓比例(占总资金)
SIMULATION_BALANCE = 1000000 # 模拟持仓
# ======================= 补仓策略配置(止盈止损策略专用) =======================
# 说明:此配置仅用于动态止盈止损策略的补仓功能,与网格交易策略无关
BUY_GRID_LEVELS = [1.0, 0.93, 0.88] # 建仓价格网格(第一个是初次建仓价格比例,后面是补仓价格比例)
# 说明:
# - BUY_GRID_LEVELS[0] = 1.0:初次建仓价格比例(100%)
# - BUY_GRID_LEVELS[1] = 0.93:首次补仓阈值,当前价格跌至成本价的93%时触发补仓(下跌7%)
# - BUY_GRID_LEVELS[2] = 0.88:第二次补仓阈值,当前价格跌至成本价的88%时触发补仓(下跌12%)
#
# 补仓金额说明:
# - 补仓策略固定使用 POSITION_UNIT(35000元)作为补仓金额
# - 补仓金额会受 MAX_POSITION_VALUE 限制,剩余空间不足时补仓金额自动调整
# - 不再使用 BUY_AMOUNT_RATIO 比例,避免配置冗余
# ⚠️ 已废弃:BUY_AMOUNT_RATIO(保留仅为向后兼容)
# 此参数仅被网格交易策略使用,补仓策略不使用此参数
# 建议使用新的网格交易配置参数:GRID_PRICE_LEVELS 和 GRID_AMOUNT_RATIOS
BUY_AMOUNT_RATIO = [0.4, 0.3, 0.3] # 每次买入金额占单元的比例(已废弃,保留向后兼容)
# ======================= 网格交易策略配置(独立配置) =======================
# ===== 旧的网格交易配置已废弃 =====
# GRID_PRICE_LEVELS, GRID_AMOUNT_RATIOS, get_grid_config()
# 已被config.py末尾的新网格交易配置替代(第504-547行)
# ======================= 止盈止损策略配置 =======================
# 补仓功能开关
ENABLE_STOP_LOSS_BUY = True # 是否启用止损补仓功能
# 统一的止损比例
STOP_LOSS_RATIO = -0.075 # 固定止损比例:成本价下跌7.5%触发止损
# 动态止盈配置
ENABLE_DYNAMIC_STOP_PROFIT = True # 启用动态止盈功能
INITIAL_TAKE_PROFIT_RATIO = 0.06 # 首次止盈触发阈值:盈利6%时触发
INITIAL_TAKE_PROFIT_PULLBACK_RATIO = 0.005 # 回撤比例:0.5%(可配置)
INITIAL_TAKE_PROFIT_RATIO_PERCENTAGE = 0.6 # 首次止盈卖出比例
# 🔑 委托单超时管理配置
ENABLE_PENDING_ORDER_AUTO_CANCEL = True # 是否启用委托单超时自动撤单
PENDING_ORDER_TIMEOUT_MINUTES = 5 # 委托单超时时间(分钟),默认5分钟
PENDING_ORDER_AUTO_REORDER = True # 撤单后是否自动重新挂单
PENDING_ORDER_REORDER_PRICE_MODE = "best" # 重新挂单价格模式: "market"=市价, "limit"=限价, "best"=对手价
MAX_CANCEL_RETRIES = 3 # 撤单最大重试次数
CANCEL_RETRY_INTERVAL_SECONDS = 1 # 撤单重试间隔(秒)
# ========== 行情异常兜底(风险保护) ==========
ENABLE_MARKET_DATA_CIRCUIT_BREAKER = True # 是否启用行情熔断
MARKET_DATA_FAILURE_THRESHOLD = 3 # 连续失败次数阈值
MARKET_DATA_FAILURE_WINDOW_SECONDS = 60 # 统计窗口(秒)
MARKET_DATA_CIRCUIT_BREAK_SECONDS = 300 # 熔断时长(秒)
# ========== 全仓止盈与委托冲突控制 ==========
ALLOW_TAKE_PROFIT_FULL_WITH_PENDING = False # 全仓止盈是否允许跳过活跃委托检查
# 说明:
# - 当止盈止损委托单提交后超过指定时间仍未成交时:
# 1. ENABLE_PENDING_ORDER_AUTO_CANCEL=True: 自动撤销旧委托单
# 2. PENDING_ORDER_AUTO_REORDER=True: 撤单后自动以新价格重新挂单
# 3. 价格模式:
# - "market": 以当前市价(最新成交价)挂单
# - "limit": 以原价格挂单(适用于价格回调情况)
# - "best": 以对手方最优价格挂单(买单用卖三价,卖单用买三价)
# - ENABLE_PENDING_ORDER_AUTO_CANCEL=False: 仅提示用户,不自动处理
# 分级动态止盈设置(已触发首次止盈后的动态止盈位)
# 格式:(最高盈利比例阈值, 止盈位系数)
# 说明:当最高盈利达到阈值后,止盈位 = 最高价 × 系数
DYNAMIC_TAKE_PROFIT = [
(0.05, 0.96), # 最高浮盈达5%时,止盈位为最高价的96%
(0.10, 0.93), # 最高浮盈达10%时,止盈位为最高价的93%
(0.15, 0.90), # 最高浮盈达15%时,止盈位为最高价的90%
(0.20, 0.87), # 最高浮盈达20%时,止盈位为最高价的87%
(0.30, 0.85), # 最高浮盈达30%时,止盈位为最高价的85%
(0.40, 0.83), # 最高浮盈达40%时,止盈位为最高价的83%
(0.50, 0.80) # 最高浮盈达50%时,止盈位为最高价的80%
]
# 止盈止损优先级说明:
# 1. 止损检查优先级最高
# 2. 未触发首次止盈时:盈利5%触发首次止盈(卖出50%)
# 3. 已触发首次止盈后:使用动态止盈位进行全仓止盈
# 4. 止损价格计算:未触发首次止盈时为成本价×(1-7%),已触发后为最高价×对应系数
# ===== 旧的网格交易参数已废弃 =====
# ENABLE_GRID_TRAD
8000
ING, GRID_DEFAULT_PRICE_INTERVAL, GRID_DEFAULT_POSITION_RATIO
# 已被config.py末尾的新网格交易配置替代(第504-547行)
# ======================= 指标配置 =======================
# MACD参数
MACD_FAST = 12
MACD_SLOW = 26
MACD_SIGNAL = 9
# 均线参数
MA_PERIODS = [10, 20, 30, 60]
# ======================= 参数配置范围 =======================
# 参数范围定义,用于前后端校验
CONFIG_PARAM_RANGES = {
"singleBuyAmount": {"min": 1000, "max": 100000, "type": "float", "desc": "单只单次买入金额"},
"firstProfitSell": {"min": 1.0, "max": 20.0, "type": "float", "desc": "首次止盈比例(%)"},
"stockGainSellPencent": {"min": 1.0, "max": 100.0, "type": "float", "desc": "首次盈利平仓卖出比例(%)"},
"stopLossBuy": {"min": 1.0, "max": 20.0, "type": "float", "desc": "补仓跌幅(%)"},
"stockStopLoss": {"min": 1.0, "max": 20.0, "type": "float", "desc": "止损比例(%)"},
"singleStockMaxPosition": {"min": 10000, "max": 100000, "type": "float", "desc": "单只股票最大持仓"},
"totalMaxPosition": {"min": 50000, "max": 1000000, "type": "float", "desc": "最大总持仓"},
"connectPort": {"min": 1, "max": 65535, "type": "int", "desc": "连接端口"}
}
# 实现参数校验函数
def validate_config_param(param_name, value):
"""验证配置参数是否在有效范围内"""
if param_name not in CONFIG_PARAM_RANGES:
return True, "" # 未定义范围的参数默认通过
param_range = CONFIG_PARAM_RANGES[param_name]
param_type = param_range.get("type", "float")
param_min = param_range.get("min")
param_max = param_range.get("max")
try:
# 类型转换
if param_type == "float":
value = float(value)
elif param_type == "int":
value = int(value)
# 范围检查
if param_min is not None and value < param_min:
return False, f"{param_range['desc']}不能小于{param_min}"
if param_max is not None and value > param_max:
return False, f"{param_range['desc']}不能大于{param_max}"
return True, ""
except (ValueError, TypeError):
return False, f"{param_range['desc']}必须是{param_type}类型"
# ======================= Web服务配置 =======================
WEB_SERVER_HOST = "127.0.0.1"
WEB_SERVER_PORT = 5000
WEB_SERVER_DEBUG = False
WEB_API_TOKEN = os.environ.get("QMT_API_TOKEN", "") # 设置后启用 Token 验证,空字符串=不验证(仅限内网部署)
# ======================= 日志清理配置 =======================
LOG_CLEANUP_DAYS = 30 # 保留最近30天的日志
LOG_CLEANUP_TIME = "00:00:00" # 每天凌晨执行清理
# ======================= 功能配置 =======================
# 交易时间配置
# DEBUG模式下使用24小时全周交易,方便测试
if DEBUG:
TRADE_TIME = {
"morning_start": "00:00:00",
"morning_end": "23:59:59",
"afternoon_start": "00:00:00",
"afternoon_end": "23:59:59",
"trade_days": [1, 2, 3, 4, 5, 6, 7] # 周一至周日
}
else:
TRADE_TIME = {
"morning_start": "09:30:00",
"morning_end": "13:00:00",
"afternoon_start": "13:00:00",
"afternoon_end": "15:00:00",
"trade_days": [1, 2, 3, 4, 5] # 周一至周五
}
# ============ 新增: 盘前同步配置 ============
PREMARKET_SYNC_TIME = {
"hour": 9, # 同步时间: 9点
"minute": 25, # 25分(9:30开盘前5分钟)
"compensation_window_minutes": 5 # 补偿窗口: 9:25-9:30
}
# 盘前同步功能开关
ENABLE_PREMARKET_XTQUANT_REINIT = True # 是否在盘前重新初始化xtquant接口
PREMARKET_REINIT_XTDATA = True # 是否重新初始化xtdata行情接口
PREMARKET_REINIT_XTTRADER = True # 是否重新初始化xttrader交易接口
ENABLE_WEB_REFRESH_AFTER_REINIT = True # 接口初始化成功后是否触发Web数据刷新
# ============ xtquant接口鲁棒性配置 ============
XTQUANT_RECONNECT_INTERVAL = 30 # xtquant重连冷却间隔(秒),防止重连风暴
# 改为30秒:QMT重启约需60秒,30秒冷却可在1-2分钟内完成恢复
# on_disconnected触发时会重置此计时,无需担心跨轮次残余冷却
XTQUANT_CALL_TIMEOUT = 3.0 # xtquant默认调用超时(秒)
XTQUANT_NON_TRADE_TIMEOUT = 1.0 # 非交易时段超时(秒)
QMT_RECONNECT_ON_ERRORS = 3 # 连续N次持仓查询失败后触发自动重连
# ============ 线程监控配置 ============
ENABLE_THREAD_MONITOR = True # 启用线程健康监控
THREAD_CHECK_INTERVAL = 60 # 线程检查间隔(秒)
THREAD_RESTART_COOLDOWN = 60 # 重启冷却时间(秒)
# ============ 持仓监控优化配置 ============
MONITOR_LOOP_INTERVAL = 3 # 监控循环间隔(秒)
MONITOR_CALL_TIMEOUT = 8.0 # 监控调用超时(秒) - 增加到8秒,避免QMT API调用超时
MONITOR_NON_TRADE_SLEEP = 60 # 非交易时段休眠(秒)
GRID_POSITION_QUERY_TIMEOUT = 5.0 # 网格交易持仓查询超时(秒)
HISTORY_DATA_DOWNLOAD_TIMEOUT = 5 # 启动时单只股票历史数据下载超时(秒),超时则跳过
GRID_LOCK_ACQUIRE_TIMEOUT = 5.0 # 网格交易锁获取超时(秒)
def is_t
8BD0
rade_time():
"""判断当前是否为交易时间"""
if DEBUG_SIMU_STOCK_DATA or ENABLE_SIMULATION_MODE:
return True
now = datetime.now()
weekday = now.weekday() + 1 # 转换为1-7表示周一至周日
if weekday not in TRADE_TIME["trade_days"]:
return False
current_time = now.strftime("%H:%M:%S")
if (TRADE_TIME["morning_start"] <= current_time <= TRADE_TIME["morning_end"]) or \
(TRADE_TIME["afternoon_start"] <= current_time <= TRADE_TIME["afternoon_end"]):
return True
return False
# ======================= 预设股票池 =======================
# 可以在这里定义预设的股票池,也可以从外部文件加载
DEFAULT_STOCK_POOL = [
"000001.SZ", # 平安银行
"600036.SH", # 招商银行
"000333.SZ", # 美的集团
"600519.SH", # 贵州茅台
"000858.SZ", # 五粮液
]
STOCK_POOL_FILE = "stock_pool.json"
def load_stock_pool(file_path=STOCK_POOL_FILE):
"""从外部文件加载股票池"""
try:
with open(file_path, 'r') as f:
return json.load(f)
except FileNotFoundError:
return DEFAULT_STOCK_POOL
# 实际使用的股票池
STOCK_POOL = load_stock_pool()
# ======================= 动态优先级决策函数 =======================
def determine_stop_loss_add_position_priority():
"""
根据配置参数动态确定止损和补仓的优先级顺序
设计逻辑:
- 场景A: 补仓阈值 < 止损阈值 → 先补仓,达到仓位上限后再止损
- 场景B: 止损阈值 < 补仓阈值 → 先止损,永不补仓
返回:
dict: {
'priority': str, # 'add_position_first' 或 'stop_loss_first'
'add_position_threshold': float, # 补仓阈值(正数,如0.05表示5%)
'stop_loss_threshold': float, # 止损阈值(正数,如0.07表示7%)
'scenario': str # 'A' 或 'B'
}
"""
# 计算补仓阈值:1 - BUY_GRID_LEVELS[1]
# 例如: BUY_GRID_LEVELS[1]=0.95 → 补仓阈值=1-0.95=0.05 (5%)
add_position_threshold = 1 - BUY_GRID_LEVELS[1]
# 计算止损阈值:abs(STOP_LOSS_RATIO)
# 例如: STOP_LOSS_RATIO=-0.07 → 止损阈值=0.07 (7%)
stop_loss_threshold = abs(STOP_LOSS_RATIO)
# 动态判断优先级
if add_position_threshold < stop_loss_threshold:
# 场景A: 补仓5% < 止损7% → 先补仓后止损
return {
'priority': 'add_position_first',
'add_position_threshold': add_position_threshold,
'stop_loss_threshold': stop_loss_threshold,
'scenario': 'A',
'description': f'补仓{add_position_threshold*100:.0f}% < 止损{stop_loss_threshold*100:.0f}% → 先补仓(至仓位上限)后止损'
}
else:
# 场景B: 止损5% <= 补仓7% → 先止损,永不补仓
return {
'priority': 'stop_loss_first',
'add_position_threshold': add_position_threshold,
'stop_loss_threshold': stop_loss_threshold,
'scenario': 'B',
'description': f'止损{stop_loss_threshold*100:.0f}% <= 补仓{add_position_threshold*100:.0f}% → 先止损,永不补仓'
}
def log_priority_scenario():
"""记录当前优先级场景信息(用于系统启动时打印)"""
priority_info = determine_stop_loss_add_position_priority()
scenario = priority_info['scenario']
description = priority_info['description']
print(f"\n{'='*60}")
print(f"动态优先级止损补仓系统 - 场景{scenario}")
print(f"{'='*60}")
print(f"补仓阈值: {priority_info['add_position_threshold']*100:.1f}%")
print(f"止损阈值: {priority_info['stop_loss_threshold']*100:.1f}%")
print(f"执行策略: {description}")
print(f"{'='*60}\n")
return priority_info
# ============================================================
# 性能优化配置 - 2025-12-12
# ============================================================
# QMT持仓查询间隔(秒) - 从3秒延长到10秒
QMT_POSITION_QUERY_INTERVAL = 10.0 # ↓70% API调用
# SQLite同步间隔(秒) - 从5秒延长到15秒
POSITION_SYNC_INTERVAL = 15.0 # ↓87% I/O操作
# 缓存配置
CACHE_CONFIG = {
'positions_ttl': 5.0, # 持仓数据缓存5秒
'quotes_ttl': 3.0, # 行情数据缓存3秒
'max_cache_size': 100 # 最大缓存条目
}
# HTTP API节流
HTTP_API_MIN_INTERVAL = 1.0 # 最小请求间隔1秒
# HTTP版本号机制(用于减少无效数据传输)
ENABLE_HTTP_VERSION_CONTROL = True # 是否启用版本号机制
# 版本号定期升级间隔(秒) - 用于Web界面定期刷新
VERSION_INCREMENT_INTERVAL = 15.0 # 版本心跳间隔(秒)
ENABLE_VERSION_HEARTBEAT = True # 是否启用“定期版本号自增”(默认关闭,避免前端频繁全量刷新)
# ======================= 卖出监控配置 (2026-01-12) =======================
# 卖出监控功能开关
ENABLE_SELL_MONITOR = True # 监控总开关
ENABLE_SELL_ALERT_NOTIFICATION = False # 告警通知开关(微信/企微)
# 告警配置
SELL_ALERT_CONFIG = {
'P0_notification': True, # P0级别告警推送通知(极高风险)
'P1_notification': True, # P1级别告警推送通知(高风险)
'P2_notification': True # P2级别告警推送通知(中等风险)
}
# ======================= 网格交易高级配置 (2026-01-24) =======================
# ⭐ 网格交易总开关(独立控制,与ENABLE_AUTO_TRADING互不影响)
# - ENABLE_GRID_TRADING = True:启用网格交易检测和执行
# - ENABLE_GRID_TRADING = False:完全禁用网格交易功能
ENABLE_GRID_TRADING = True # 启用后才能使用网格交易功能
# 回调触发机制
GRID_CALLBACK_RATIO = 0.005 # 回调比例0.5%触发交易
# 档位冷却时间(秒)
GRID_LEVEL_COOLDOWN = 60 # 同一档位60秒内不重复触发
# 成功买入后的最短间隔(秒) - 防止级联买入过快
# 9:25开盘后若价格已低于下轨,连续跌穿多个档位会快速触发多次买入
# 设为300(5分钟)可避免在极端开盘波动中过快消耗 max_investment
GRID_BUY_COOLDOWN = 300 # 0=不限制(向后兼容默认值);建议实盘设为 300
# 成功卖出后的最短间隔(秒) - 防止价格在上轨附近震荡时级联触发多次卖出
# 与 GRID_BUY_COOLDOWN 对称(A-4/B-5修复):下单成功后立即记录冷却时间,
# 即使 DB 写入失败,冷却保护依然有效,阻止重复下单。
GRID_SELL_COOLDOWN = 300 # 0=不限制;建议实盘设为 300
# 卖出冷却自适应缩短阈值
# 当触发价比上次成交价高出该比例时,冷却期减半,避免单边上涨行情中错过更高档位
# 例:上次卖出7.42,当前触发价7.68,涨幅3.5%>2%,冷却期从300s缩短为150s
GRID_SELL_COOLDOWN_PRICE_THRESHOLD = 0.02 # 2%涨幅触发自适应缩短;0=禁用
# 启动条件配置
GRID_REQUIRE_PROFIT_TRIGGERED = True # 是否要求已触发止盈才能启动网格交易(True=更安全,False=更灵活)
# 混合退出机制 - 默认值
GRID_MAX_DEVIATION_RATIO = 0.15 # 网格中心最大偏离±15%
GRID_TARGET_PROFIT_RATIO = 0.10 # 目标盈利10%
GRID_STOP_LOSS_RATIO = -0.10 # 止损-10%
GRID_DEFAULT_DURATION_DAYS = 7 # 默认运行7天
# Web界面默认值
GRID_DEFAULT_PRICE_INTERVAL = 0.05 # 默认价格间隔5%
GRID_DEFAULT_POSITION_RATIO = 0.25 # 默认每档交易25%
GRID_DEFAULT_MAX_INVESTMENT_RATIO = 0.5 # 默认最大投入为持仓市值50%
# 日志级别
GRID_LOG_LEVEL = "INFO" # DEBUG时输出详细价格追踪
# 网格交易策略标识
GRID_STRATEGY_NAME = "grid" # 用于trade_records表的strategy字段
# 实盘网格委托确认机制
# True: 实盘网格下单成功后仅登记待确认委托,等成交回报到达后再更新网格统计/重建网格
# False: 兼容旧行为(不推荐实盘使用)
GRID_CONFIRM_LIVE_ORDER_BY_DEAL = True
# 网格信号执行前复核
GRID_SIGNAL_MAX_AGE_SECONDS = 60 # 网格信号最长有效期(秒)
GRID_SIGNAL_MAX_PRICE_DRIFT_RATIO = 0.01 # 执行前最新价相对触发价最大容忍偏离(1%)
# 网格对手价下单(提高成交概率)
# True: 实盘下单不指定价格,由 executor 取卖三价(买)/买三价(卖),参考动态止盈下单逻辑,提高成交概率
# False: 实盘下单使用 trigger_price 限价(兼容旧行为)
# 注意: 对手价仅在 GRID_CONFIRM_LIVE_ORDER_BY_DEAL=True 时启用,
# 因为成交以真实回报价落账(handle_deal_callback),统计才准确;
# 非确认模式下用 trigger_price 估算落账,强制回退限价以保持 V1/V1-SELL 修复的统计一致性。
GRID_USE_COUNTERPARTY_PRICE = True
# 网格涨跌停/停牌防护(仅实盘生效)
# True: 实盘下单前检查标的涨跌停/停牌状态,封板时跳过本次交易,避免无效挂单
# - 买入: 现价已达涨停价 → 跳过(封板买不进/追涨)
# - 卖出: 现价已达跌停价 → 跳过(封板卖不出)
# - 停牌/无有效现价 → 买卖均跳过
# 涨跌停价获取失败时 fail-open(放行),仅保留停牌判断
GRID_ENABLE_PRICE_LIMIT_GUARD = True
GRID_PRICE_LIMIT_EPS = 0.001 # 涨跌停判定容差(元),补偿浮点误差
def get_grid_default_config(position_market_value: float) -> dict:
"""
获取网格交易默认配置
Args:
position_market_value: 当前持仓市值
Returns:
默认配置字典
"""
return {
'price_interval': GRID_DEFAULT_PRICE_INTERVAL,
'position_ratio': GRID_DEFAULT_POSITION_RATIO,
'callback_ratio': GRID_CALLBACK_RATIO,
# 非交易时间市值可能为None,使用默认值10000元兜底
'max_investment': (position_market_value * GRID_DEFAULT_MAX_INVESTMENT_RATIO) if position_market_value and position_market_value > 0 else 10000,
'max_deviation': GRID_MAX_DEVIATION_RATIO,
'target_profit': GRID_TARGET_PROFIT_RATIO,
'stop_loss': GRID_STOP_LOSS_RATIO,
'duration_days': GRID_DEFAULT_DURATION_DAYS
}
def _apply_per_account_settings():
"""根据环境变量 QMT_ACCOUNT_ID,将全局配置覆写为账号专属值。
单账号模式(不设置环境变量)时直接返回,行为与改动前完全一致。
"""
global DATA_DIR, DB_PATH, STOCK2BUY_FILE, LOG_FILE, WEB_SERVER_PORT
global STOCK_POOL_FILE, STOCK_POOL
account_id = os.environ.get('QMT_ACCOUNT_ID', '').strip()
if not account_id:
return # 单账号/默认模式,保持全部默认值
# 查找账号在列表中的索引(决定端口偏移量)
all_accounts = get_all_accounts_config()
account_ids = [a.get('account_id', '') for a in all_accounts]
if account_id not in account_ids:
print(f"[CONFIG] 警告: QMT_ACCOUNT_ID={account_id} 不在账号列表中,使用默认配置")
return
idx = account_ids.index(account_id)
DATA_DIR = f"data_{account_id}"
DB_PATH = os.path.join(DATA_DIR, "trading.db")
STOCK2BUY_FILE = os.path.join(DATA_DIR, "stock2buy.json")
LOG_FILE = f"account_{account_id}.log"
WEB_SERVER_PORT = 5000 + idx
# 股票池按账号无条件隔离到 data_<id>/stock_pool.json:
# 旧逻辑用 "文件存在才走账号目录,否则回落根目录" — 首次启动时账号目录
# 都还没有 stock_pool.json,两个账号都写根目录,position_manager 的
# _update_stock_positions_file 每轮持仓监控会比较文件与自身持仓,
# 互不一致就覆盖,导致两个账号每 10 秒互相覆盖一次。
# 现在无条件用账号目录;load_stock_pool 读不到文件会返回 DEFAULT_STOCK_POOL
# 兜底,所以"账号目录里还没建文件"也是安全的。
os.makedirs(DATA_DIR, exist_ok=True)
STOCK_POOL_FILE = os.path.join(DATA_DIR, "stock_pool.json")
STOCK_POOL = load_stock_pool(STOCK_POOL_FILE)
# QMT_PATH 和 ACCOUNT_CONFIG 在模块顶部已经通过 get_account_config()
# 读取到了正确的子账号值(get_account_config 感知 QMT_ACCOUNT_ID),无需此处重复刷新。
print(f"[CONFIG] 账号 {account_id}(第 {idx + 1} 个): "
f"DATA_DIR={DATA_DIR}, PORT={WEB_SERVER_PORT}, LOG={LOG_FILE}, "
f"STOCK_POOL={STOCK_POOL_FILE}, QMT_PATH={QMT_PATH}")
_apply_per_account_settings()