Bug 概述
两个相关 bug 同时出现在 paper trading 模式下的 qd_strategy_trades 表:
- Bug 1: 任何 close 笔的
matched_entry_price 都 = 0.0(FIFO 配对信息丢失)
- Bug 2: 偶发 ghost close(无对应开仓记录的 close 笔)→ 凭空利润虚增 PnL
Bug 1: matched_entry_price 全部写 0.0
症状: 不论 close_long / close_short / reduce_long / reduce_short / server_trailing_stop,matched_entry_price 字段都 = 0.0。entry_price 字段是对的(开仓价 / 平仓价),但 matched_entry_price 没传。
根因 (trading_executor.py 4 处):
| 位置 |
类型 |
缺什么 |
| L3462 |
server_trailing_stop (long trigger) |
返回 dict 缺 matched_entry_price 字段 |
| L3478 |
server_trailing_stop (short trigger) |
返回 dict 缺 matched_entry_price 字段 |
| L4661 |
reduce_long caller |
_record_trade() 没传 matched_entry_price=old_entry |
| L4675 |
close_long caller |
_record_trade() 没传 matched_entry_price=old_entry |
| L4687 |
close_short caller |
_record_trade() 没传 matched_entry_price=old_entry |
| L4713 |
reduce_short caller |
_record_trade() 没传 matched_entry_price=old_entry |
records.py 第 423 行 INSERT 用 kwargs.get('matched_entry_price', 0.0) —— caller 没传就用 0.0 默认值。
修复 diff (4 处 + 1 处 None 保护):
# L3462 / L3478 (server_trailing_stop trigger)
return {
'type': 'close_long', # or 'close_short'
'trigger_price': 0,
'position_size': 0,
'timestamp': candle_ts,
'reason': 'server_trailing_stop',
'matched_entry_price': entry_price, # ← 新增 (L3462 用 entry, L3478 用 entry_price)
'trailing_stop_price': stop_line,
'highest_price': hp, # or 'lowest_price': lp
}
# L4661 / L4675 / L4687 / L4713 (caller)
self._record_trade(
strategy=strategy, symbol=symbol, side='long', qty=close_qty, price=fill_px,
timestamp=fill_ts, profit=profit, status='closed', fee=fee, raw_data=raw,
close_reason=_exit_reason,
matched_entry_price=old_entry if old_entry else 0, # ← 新增 (4 处都加)
)
验证: 策略 id=6 paper trading 跑 1 周, 20 笔 trades 全部 matched_entry_price 修复(10 open + 10 close),未来新 close 自动写入正确值。
Bug 2: Ghost close 凭空利润
症状: 策略 id=6 总 PnL 显示 +2611.48 USDT,实际是 -5.56 USDT。差异 2617.04 来自 2 笔 ghost close:
| id |
type |
price |
profit |
close_reason |
created_at |
| 283 |
close_short |
1761.94 |
+789.83 |
server_trailing_stop |
2026-06-04 09:46:47 |
| 291 |
close_short |
1735.62 |
+1840.23 |
server_trailing_stop |
2026-06-05 04:30:23 |
根因:
- 22 笔 trades = 10 笔
open_short (id=6, 16, 23, 42, 53, 69, 77, 100, 117, 187) + 12 笔 close_short(含 2 笔 ghost id=283/291)
- ghost close 没有对应 open_short 记录
apply_fill_to_local_position 计算 profit 时 cur_entry 字段值 stale(= 0 或 上一笔 close 后的 entry)
- 结果:凭空算出 +789.83 和 +1840.23,加到 PnL 总数上
修复:
- 历史数据: 删 2 笔 ghost close(建议 SQL migration)
- 代码层:
server_trailing_stop trigger 需要校验 "有对应未平仓 open 才能触发",否则不写 ghost close 记录;或者 apply_fill_to_local_position 检测到 cur_entry 不存在时跳过 profit 计算
复现步骤
git clone https://github.com/brokermr810/QuantDinger && cd QuantDinger && docker compose up -d --build
- 登录后建 ETH/USDT paper trading 策略 (IndicatorStrategy),带
trailingEnabled true
- 跑 1-2 周,触发 server_trailing_stop
- 查 db:
SELECT id, type, price, profit, close_reason, matched_entry_price
FROM qd_strategy_trades
WHERE strategy_id = X ORDER BY id;
- 看到:
matched_entry_price = 0.0 (Bug 1) + 偶发 ghost close with server_trailing_stop (Bug 2)
期望行为
matched_entry_price = 对应 open 的 price (FIFO 配对)
- 总 PnL 基于真实 open/close 配对,ghost close 应被 filter 掉
部署方式
- Docker Compose
- 镜像:
quantdinger-backend 重建时间 2026-06-05 16:32
- 复现概率: 100% (matched_entry_price 全 0), 偶发 (ghost close)
临时修复 (在我们自己 db 已做)
-- 1. 删 ghost close (profit 先改 0 保留痕迹)
UPDATE qd_strategy_trades SET profit = 0 WHERE strategy_id = X AND id IN (283, 291);
DELETE FROM qd_strategy_trades WHERE strategy_id = X AND id IN (283, 291);
-- 2. 补 matched_entry_price (FIFO, 同 amount 配对)
UPDATE qd_strategy_trades c
SET matched_entry_price = o.price
FROM (
SELECT c2.*, ROW_NUMBER() OVER (PARTITION BY c2.amount ORDER BY c2.created_at) AS c_seq
FROM qd_strategy_trades c2
WHERE c2.strategy_id = X AND c2.type = 'close_short'
) cc
JOIN (
SELECT o2.*, ROW_NUMBER() OVER (PARTITION BY o2.amount ORDER BY o2.created_at) AS o_seq
FROM qd_strategy_trades o2
WHERE o2.strategy_id = X AND o2.type = 'open_short'
) o ON cc.amount = o.amount AND cc.c_seq = o.o_seq
WHERE c.id = cc.id;
-- 3. open 笔 matched_entry_price = own price
UPDATE qd_strategy_trades
SET matched_entry_price = price
WHERE strategy_id = X AND type = 'open_short' AND matched_entry_price = 0;
修复后:20 笔 trades 全部 matched_entry_price 正确,PnL 真实反映 -5.56 USDT。
关键日志
2026-06-05 04:30:23,280 - app.services.trading_executor - INFO - Strategy 6 triggered signals:
[{'type': 'close_short', 'trigger_price': 0, 'position_size': 0.0, 'timestamp': ...}]
# 注意: trigger_price=0, position_size=0 (ghost trigger 没填真值)
建议 upstream 加日志告警:if trigger_price == 0 and position_size == 0: log.warning('Ghost signal detected')
Bug 概述
两个相关 bug 同时出现在 paper trading 模式下的
qd_strategy_trades表:matched_entry_price都 = 0.0(FIFO 配对信息丢失)Bug 1:
matched_entry_price全部写 0.0症状: 不论
close_long/close_short/reduce_long/reduce_short/server_trailing_stop,matched_entry_price字段都 = 0.0。entry_price字段是对的(开仓价 / 平仓价),但matched_entry_price没传。根因 (
trading_executor.py4 处):server_trailing_stop(long trigger)matched_entry_price字段server_trailing_stop(short trigger)matched_entry_price字段reduce_longcaller_record_trade()没传matched_entry_price=old_entryclose_longcaller_record_trade()没传matched_entry_price=old_entryclose_shortcaller_record_trade()没传matched_entry_price=old_entryreduce_shortcaller_record_trade()没传matched_entry_price=old_entryrecords.py第 423 行 INSERT 用kwargs.get('matched_entry_price', 0.0)—— caller 没传就用 0.0 默认值。修复 diff (4 处 + 1 处 None 保护):
验证: 策略 id=6 paper trading 跑 1 周, 20 笔 trades 全部
matched_entry_price修复(10 open + 10 close),未来新 close 自动写入正确值。Bug 2: Ghost close 凭空利润
症状: 策略 id=6 总 PnL 显示 +2611.48 USDT,实际是 -5.56 USDT。差异 2617.04 来自 2 笔 ghost close:
server_trailing_stopserver_trailing_stop根因:
open_short(id=6, 16, 23, 42, 53, 69, 77, 100, 117, 187) + 12 笔close_short(含 2 笔 ghost id=283/291)apply_fill_to_local_position计算 profit 时cur_entry字段值 stale(= 0 或 上一笔 close 后的 entry)修复:
server_trailing_stoptrigger 需要校验 "有对应未平仓 open 才能触发",否则不写 ghost close 记录;或者apply_fill_to_local_position检测到cur_entry不存在时跳过 profit 计算复现步骤
git clone https://github.com/brokermr810/QuantDinger && cd QuantDinger && docker compose up -d --buildtrailingEnabled truematched_entry_price = 0.0(Bug 1) + 偶发 ghost close withserver_trailing_stop(Bug 2)期望行为
matched_entry_price= 对应 open 的price(FIFO 配对)部署方式
quantdinger-backend重建时间 2026-06-05 16:32临时修复 (在我们自己 db 已做)
修复后:20 笔 trades 全部
matched_entry_price正确,PnL 真实反映 -5.56 USDT。关键日志
建议 upstream 加日志告警:
if trigger_price == 0 and position_size == 0: log.warning('Ghost signal detected')