# QMT 交易模块缺陷修复任务清单 > 生成时间:2026-02-17 > 基于代码审查报告生成 > 状态:🔴 **阻止上线** - 必须先修复 CRITICAL 和 HIGH 级别问题 --- ## 📋 执行指南 ### 优先级说明 - **P0 (Critical)**:必须立即修复,可能导致资金损失 - **P1 (High)**:必须修复,可能导致交易异常 - **P2 (Medium)**:建议修复,影响系统稳定性 - **P3 (Low)**:可选修复,长期优化项 ### 执行顺序 1. 先完成所有 P0 任务 2. 再进行 P1 任务 3. 最后完成 P2/P3 任务 4. 每个任务完成后需在 [x] 中标记完成者姓名和日期 --- ## 🔴 P0 - 严重缺陷(阻止上线) ### [x] 1. 修复重复的重连逻辑代码块 **文件**: `qmt_trader.py` **行号**: 642-672 #### 问题描述 ```python # 第642-672行存在完全重复的代码块 if datetime.date.today().weekday() >= 5: time.sleep(3600) continue # ... 重连逻辑 ... # 下面又重复了一次完全相同的逻辑! if datetime.date.today().weekday() >= 5: time.sleep(3600) continue # ... 重复的重连逻辑 ... ``` #### 风险分析 - 重连成功后可能立即再次执行重连 - 导致连接状态混乱 - 可能产生重复的 trader 实例 #### 修复方案 ```python # 删除第642-672行中的重复代码块 # 只保留一组重连逻辑 ``` #### 验收标准 - [x] 第642-672行范围内没有重复代码 - [x] 重连逻辑只执行一次 - [x] 日志输出正常,无重复重连记录 **负责人**: Sisyphus **完成日期**: 2026-02-17 --- ### [x] 2. 修复卖出逻辑的持仓双重验证 **文件**: `qmt_engine.py` **行号**: 733-753 #### 问题描述 ```python def _execute_sell(self, unit, strategy_name, data): v_vol = self.pos_manager.get_position(strategy_name, data['stock_code']) if v_vol <= 0: return # 仅检查虚拟持仓,未验证实际持仓 # ... 直接执行卖出 ``` #### 风险分析 - 幽灵卖出:Redis 有记录但实盘已清仓 - 超卖风险:卖出量超过实际可用持仓 - 可能导致负持仓或违规交易 #### 修复方案 ```python def _execute_sell(self, unit, strategy_name, data): # 1. 查询实盘持仓(一切以实盘为准) real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj) rp = next((p for p in real_pos if p.stock_code == data['stock_code']), None) if real_pos else None can_use = rp.can_use_volume if rp else 0 # 2. 检查虚拟持仓 v_vol = self.pos_manager.get_position(strategy_name, data['stock_code']) # 3. 实盘无持仓 -> 拒绝卖出(清理幽灵持仓) if can_use <= 0: self.logger.warning(f"[{strategy_name}] 卖出拦截: {data['stock_code']} 实盘无可用持仓") # 如果虚拟持仓存在但实盘已清仓,清理幽灵持仓 if v_vol > 0: self.pos_manager.force_delete(strategy_name, data['stock_code']) self.logger.info(f"[{strategy_name}] 已清理幽灵持仓: {data['stock_code']} 虚拟{v_vol}股") return # 4. 实盘有持仓 -> 必须卖出(取虚拟和实盘的最小值,虚拟无持仓则取实盘) if v_vol <= 0: self.logger.warning(f"[{strategy_name}] 卖出提醒: {data['stock_code']} 虚拟无持仓但实盘有{can_use}股,以实盘为准执行卖出") final_vol = min(v_vol, can_use) if v_vol > 0 else can_use if final_vol <= 0: self.logger.warning(f"[{strategy_name}] 卖出拦截: {data['stock_code']} 计算后卖出量为0") return # ... 执行卖出 ``` #### 验收标准 - [x] 卖出前同时验证虚拟持仓和实盘持仓 - [x] 当实盘持仓为0时拒绝卖出并记录日志 - [x] 添加幽灵持仓自动清理机制 - [x] 模拟盘测试超卖场景被正确拦截 - [x] **核心逻辑:一切以实盘持仓为准** - 信号卖出+实盘有持仓=必须执行 **负责人**: Sisyphus **完成日期**: 2026-02-17 --- ### [ ] 3. 统一价格偏移配置项名称 **文件**: `qmt_engine.py`, `config.json` #### 问题描述 - 代码中使用:`buy_price_offset` / `sell_price_offset` - 配置中使用:`buy_drift_pct` / `sell_drift_fixed` - 配置项名称不匹配导致策略失效 #### 风险分析 - 价格偏移策略失效 - 可能以不利价格成交 - 实际交易行为与策略设计不符 #### 修复方案(二选一) **方案A:修改代码(推荐)** ```python # qmt_engine.py 第707-708行 offset = strat_cfg.get("execution", {}).get("buy_drift_pct", 0.0) # 改为配置中的名称 # 第555-558行 offset = ( self.config["strategies"][strategy_name] .get("execution", {}) .get("sell_drift_fixed", 0.0) # 改为配置中的名称 ) ``` **方案B:修改配置文件** ```json { "strategies": { "ST_Strategy": { "execution": { "buy_price_offset": 0.005, // 改为代码中使用的名称 "sell_price_offset": -0.01 } } } } ``` #### 验收标准 - [ ] 代码和配置中的价格偏移配置项名称一致 - [ ] 策略能正确读取并使用价格偏移 - [ ] 日志中显示的价格偏移值正确 **负责人**: ___________ **截止日期**: ___________ --- ## 🟠 P1 - 高风险(强烈建议修复) ### [ ] 4. 修复买入资金计算逻辑 **文件**: `qmt_engine.py` **行号**: 696-698 #### 问题描述 ```python total_equity = asset.cash + asset.market_value # 使用总资产 target_amt = total_equity * weight / total_weighted_slots actual_amt = min(target_amt, asset.cash * 0.98) # 仅预留 2% 手续费 ``` #### 风险分析 - 使用总资产(含已持仓市值)而非可用资金计算 - 2% 手续费预留可能不足 - 已持仓较大时可能下单金额超过实际可用资金 #### 修复方案 ```python def _execute_buy(self, unit, strategy_name, data): # ... asset = unit.xt_trader.query_stock_asset(unit.acc_obj) if not asset: return # 使用可用资金而非总资产 available_cash = asset.cash # 获取该终端下所有策略的持仓情况 terminal_strategies = self.get_strategies_by_terminal(unit.qmt_id) total_weighted_slots = sum( self.config["strategies"][s].get("total_slots", 1) * self.config["strategies"][s].get("weight", 1) for s in terminal_strategies ) if total_weighted_slots <= 0: return weight = strat_cfg.get("weight", 1) # 计算目标金额(基于可用资金) target_amt = available_cash * weight / total_weighted_slots # 预留更多手续费缓冲(5%) actual_amt = min(target_amt, available_cash * 0.95) # 增加最小金额检查 min_buy_amount = strat_cfg.get("execution", {}).get("min_buy_amount", 2000) if actual_amt < min_buy_amount: self.logger.warning(f"[{strategy_name}] 单笔金额 {actual_amt:.2f} 不足 {min_buy_amount},取消买入") return # ... 继续执行 ``` #### 验收标准 - [ ] 使用 `asset.cash` 而非 `asset.cash + asset.market_value` - [ ] 手续费预留改为 5%(可配置) - [ ] 增加最小买入金额配置项检查 - [ ] 资金不足时正确拦截并记录日志 **负责人**: ___________ **截止日期**: ___________ --- ### [x] 5. 修复消息处理的静默失败 **文件**: `qmt_engine.py` **行号**: 556-557 #### 问题描述 ```python try: # 消息处理逻辑 except: pass # 异常被完全吞掉,无日志记录 ``` #### 风险分析 - 交易信号丢失无法追溯 - 无法排查问题原因 - 系统表现与预期不符时无法定位 #### 修复方案 ```python def process_route(self, strategy_name): # ... try: data = json.loads(msg_json) # ... 处理逻辑 except json.JSONDecodeError as e: self.logger.error(f"[{strategy_name}] JSON解析失败: {e}, 消息: {msg_json[:200]}") except KeyError as e: self.logger.error(f"[{strategy_name}] 消息缺少必要字段: {e}") except Exception as e: self.logger.error(f"[{strategy_name}] 消息处理异常: {str(e)}", exc_info=True) # 可选:将失败消息存入死信队列以便后续处理 # self.r.rpush(f"{strategy_name}_dead_letter", msg_json) ``` #### 其他同步修复的静默处理问题 本次修复同时检查了qmt模块中所有裸`except: pass`语句,并修复了以下静默处理问题: | 文件 | 行号 | 问题 | 修复方式 | |------|------|------|----------| | `qmt_engine.py` | 171 | 配置文件读取失败静默处理 | 添加日志警告 | | `qmt_trader.py` | 551 | 健康检查资产查询异常静默处理 | 添加日志警告 | | `qmt_trader.py` | 651 | 心跳文件写入异常静默处理 | 添加日志警告 | | `qmt_trader.py` | 736 | API查询持仓异常静默处理 | 添加日志警告 | **qmt模块现在禁止出现任何静默处理** - 所有异常都必须被捕获并记录到日志。 #### 验收标准 - [x] 所有异常都被捕获并记录到日志 - [x] 包含异常类型、消息内容和堆栈信息 - [ ] 失败消息可追溯(可选:死信队列) **负责人**: Sisyphus **完成日期**: 2026-02-17 --- ### [ ] 6. 添加槽位检查的原子性保护 **文件**: `qmt_engine.py` **行号**: 669-673 #### 问题描述 ```python # 非原子性操作 if (self.pos_manager.get_holding_count(strategy_name) >= strat_cfg["total_slots"]): return # 此时槽位可能被其他线程占用,导致超仓 ``` #### 风险分析 - 竞态条件导致超仓 - 多线程环境下槽位计数不准确 - 可能超过策略设定的最大持仓数 #### 修复方案 ```python # 使用 Redis 原子操作实现槽位占用 def _try_acquire_slot(self, strategy_name, stock_code): """尝试原子性占用槽位,返回是否成功""" key = f"POS:{strategy_name}" # Lua脚本实现原子性检查和占用 lua_script = """ local key = KEYS[1] local code = ARGV[1] local max_slots = tonumber(ARGV[2]) local current_count = redis.call('HLEN', key) local exists = redis.call('HEXISTS', key, code) -- 如果已存在该股票,允许(可能是加仓) if exists == 1 then return 1 end -- 检查是否还有空槽位 if current_count >= max_slots then return 0 end -- 占用槽位 redis.call('HSETNX', key, code, 0) return 1 """ max_slots = strat_cfg["total_slots"] result = self.r.eval(lua_script, 1, key, stock_code, max_slots) return result == 1 # 在 _execute_buy 中使用 if not self._try_acquire_slot(strategy_name, data['stock_code']): self.logger.warning(f"[{strategy_name}] 槽位已满,拦截买入") return ``` #### 验收标准 - [ ] 槽位检查和占用是原子性操作 - [ ] 并发测试不会出现超仓 - [ ] 性能影响可接受 **负责人**: ___________ **截止日期**: ___________ --- ### [ ] 7. 修复 API 服务器 CORS 配置 **文件**: `api_server.py` **行号**: 90-95 #### 问题描述 ```python app.add_middleware( CORSMiddleware, allow_origins=["*"], # 允许所有来源 allow_methods=["*"], # 允许所有方法 allow_headers=["*"], # 允许所有头 ) ``` #### 风险分析 - 生产环境允许任意跨域访问 - 存在 CSRF 风险 - API 可被任意网站调用 #### 修复方案 ```python import os # 从环境变量读取允许的域名 ALLOWED_ORIGINS = os.getenv( "QMT_ALLOWED_ORIGINS", "http://localhost:8001,http://127.0.0.1:8001" ).split(",") app.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, allow_methods=["GET", "POST"], # 只允许必要的方法 allow_headers=["Content-Type", "Authorization"], # 限制请求头 allow_credentials=False, # 不携带凭证 ) ``` #### 验收标准 - [ ] CORS 只允许配置的白名单域名 - [ ] 生产环境不允许 `*` - [ ] 方法和头信息限制在最小必要范围 **负责人**: ___________ **截止日期**: ___________ --- ## 🟡 P2 - 中等问题 ### [ ] 8. 移除测试用的价格兜底逻辑 **文件**: `qmt_trader.py` **行号**: 373-374 #### 问题描述 ```python if price <= 0: logger.warning(f"价格异常: {price},强制设为1.0以计算股数(仅测试用)") price = 1.0 # 测试用代码留在生产环境! ``` #### 修复方案 ```python if price <= 0: logger.error(f"[{strategy_name}] 买入拦截: 价格异常 {price}") return # 直接拒绝,不使用兜底值 ``` **负责人**: ___________ **截止日期**: ___________ --- ### [ ] 9. 为 qmt_engine.py 添加日终撤单逻辑 **文件**: `qmt_engine.py` #### 问题描述 `qmt_engine.py` 的 `DailySettlement` 类缺少撤单逻辑(与 `qmt_trader.py` 不同) #### 修复方案 ```python class DailySettlement: # ... 现有代码 ... def run_settlement(self): trader = self.unit.xt_trader acc = self.unit.acc_obj if not trader: return logger = logging.getLogger("QMT_Engine") logger.info("=" * 40) logger.info("执行收盘清算流程...") # 1. 撤销所有可撤订单 try: orders = trader.query_stock_orders(acc, cancelable_only=True) if orders: for o in orders: logger.info(f"收盘清算 - 撤单: OrderID={o.order_id}, Stock={o.stock_code}") trader.cancel_order_stock(acc, o.order_id) time.sleep(2) logger.info(f"收盘清算 - 完成撤单 {len(orders)} 个订单") except Exception as e: logger.error(f"收盘清算 - 撤单失败: {str(e)}", exc_info=True) # 2. 持仓对账(现有逻辑) # ... ``` **负责人**: ___________ **截止日期**: ___________ --- ### [ ] 10. 敏感信息加密存储 **文件**: `config.json` #### 问题描述 ```json { "redis": { "password": "Redis520102" // 明文存储 } } ``` #### 修复方案 ```python # 使用环境变量覆盖配置文件 import os # 配置加载时优先使用环境变量 redis_cfg = CONFIG.get("redis", {}) redis_cfg["password"] = os.getenv("REDIS_PASSWORD", redis_cfg.get("password")) ``` **部署说明**: 生产环境应设置环境变量: ```bash set REDIS_PASSWORD=Redis520102 set QMT_ACCOUNT_PASSWORD=your_password ``` **负责人**: ___________ **截止日期**: ___________ --- ## 🟢 P3 - 长期优化 ### [ ] 11. 添加交易前价格范围检查 **建议**: 在下单前检查价格是否在合理范围(如前收盘价±10%),防止异常价格导致大额损失 ### [ ] 12. 添加订单确认机制 **建议**: 大额订单添加二次确认机制,可通过 WebSocket 推送到前端确认 ### [ ] 13. 完善监控告警 **建议**: - 连接断开告警 - 成交异常告警 - 持仓偏差告警 - 资金异常告警 ### [ ] 14. 增加单元测试覆盖 **建议**: 为核心交易逻辑添加单元测试,特别是: - 买入/卖出逻辑 - 持仓计算 - 价格偏移计算 - 重连逻辑 ### [ ] 15. 添加交易审计日志 **建议**: 将所有交易操作记录到独立的审计日志,包含: - 下单时间、价格、数量 - 成交回报 - 错误信息 - 操作来源(信号来源) --- ## 📊 修复进度追踪 | 任务ID | 优先级 | 状态 | 负责人 | 开始日期 | 完成日期 | |--------|--------|------|--------|----------|----------| | 1 | P0 | ✅ | Sisyphus | 2026-02-17 | 2026-02-17 | | 2 | P0 | ✅ | Sisyphus | 2026-02-17 | 2026-02-17 | | 3 | P0 | ⬜ | | | | | 4 | P1 | ⬜ | | | | | 5 | P1 | ✅ | Sisyphus | 2026-02-17 | 2026-02-17 | | 6 | P1 | ⬜ | | | | | 7 | P1 | ⬜ | | | | | 8 | P2 | ⬜ | | | | | 9 | P2 | ⬜ | | | | | 10 | P2 | ⬜ | | | | --- ## ✅ 上线前最终检查清单 - [ ] 所有 P0 任务已完成并测试通过 - [ ] 所有 P1 任务已完成并测试通过 - [ ] 代码审查通过 - [ ] 模拟盘测试运行 3 天以上无异常 - [ ] 日终清算功能验证通过 - [ ] 重连机制测试通过 - [ ] API 安全配置验证 - [ ] 日志系统正常工作 - [ ] 监控告警配置完成 - [ ] 回滚方案准备就绪 --- ## 📝 版本历史 | 版本 | 日期 | 修改人 | 修改内容 | |------|------|--------|----------| | v1.0 | 2026-02-17 | Assistant | 初始版本,基于代码审查报告生成 | | v1.1 | 2026-02-17 | Sisyphus | 修复缺陷#1(重复重连逻辑)和缺陷#2(卖出双重验证) | | v1.2 | 2026-02-17 | Sisyphus | 修复缺陷#5(消息处理静默失败)及所有其他静默处理问题 |