diff --git a/backend/database/add_partial_profit_exit_reasons.sql b/backend/database/add_partial_profit_exit_reasons.sql new file mode 100644 index 0000000..00c7b5e --- /dev/null +++ b/backend/database/add_partial_profit_exit_reasons.sql @@ -0,0 +1,42 @@ +-- 分步止盈状态细分:添加新的exit_reason值支持 +-- 执行时间:2026-01-27 + +-- 1. 更新exit_reason字段注释,说明新的状态值 +ALTER TABLE `trades` MODIFY COLUMN `exit_reason` VARCHAR(50) + COMMENT '平仓原因: manual(手动), stop_loss(止损), take_profit(单次止盈), trailing_stop(移动止损), sync(同步), take_profit_partial_then_take_profit(第一目标止盈后第二目标止盈), take_profit_partial_then_stop(第一目标止盈后剩余仓位止损), take_profit_partial_then_trailing_stop(第一目标止盈后剩余仓位移动止损)'; + +-- 2. 验证字段长度是否足够(VARCHAR(50)应该足够) +SELECT + COLUMN_NAME, + COLUMN_TYPE, + COLUMN_COMMENT +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'trades' + AND COLUMN_NAME = 'exit_reason'; + +-- 3. 查看当前exit_reason的分布情况(用于验证) +SELECT + exit_reason, + COUNT(*) as count, + ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM trades WHERE status = 'closed'), 2) as percentage +FROM + trades +WHERE + status = 'closed' +GROUP BY + exit_reason +ORDER BY + count DESC; + +-- 说明: +-- 新的状态值: +-- - take_profit_partial_then_take_profit: 第一目标止盈(50%仓位)后,剩余仓位第二目标止盈 +-- - take_profit_partial_then_stop: 第一目标止盈(50%仓位)后,剩余仓位止损(保本) +-- - take_profit_partial_then_trailing_stop: 第一目标止盈(50%仓位)后,剩余仓位移动止损 +-- +-- 这些状态用于更准确地统计胜率和盈亏比: +-- - 第一目标止盈后剩余仓位止损,应该算作"部分成功"(第一目标已达成) +-- - 第一目标止盈后剩余仓位第二目标止盈,应该算作"完整成功" diff --git a/docs/分步止盈状态管理分析与优化方案_2026-01-27.md b/docs/分步止盈状态管理分析与优化方案_2026-01-27.md new file mode 100644 index 0000000..3fdaac1 --- /dev/null +++ b/docs/分步止盈状态管理分析与优化方案_2026-01-27.md @@ -0,0 +1,252 @@ +# 分步止盈状态管理分析与优化方案(2026-01-27) + +## 📊 日志分析 + +### 日志内容 + +``` +2026-01-27 10:51:07 - AXLUSDT [实时监控] 触发第一目标止盈(30%固定止盈,基于保证金): + 当前盈亏=14.65% of margin >= 目标=12.00% of margin | + 当前价=0.0890, 目标价=0.0887 | + 将平掉50%仓位,锁定30%盈利,剩余50%追求更高收益 + +2026-01-27 10:51:07 - AXLUSDT [实时监控] 部分止盈成功: 平仓110.5000,剩余110.7000 + +2026-01-27 10:51:07 - AXLUSDT [实时监控] 部分止盈后:剩余仓位止损移至成本价 0.0874(保本), + 剩余50%仓位追求更高收益(第二目标:4.0:1盈亏比或更高) +``` + +--- + +## 🔍 问题分析 + +### 问题1:日志文案与实际配置不一致 + +**问题**: +- 日志显示:"30%固定止盈" +- 实际目标:12.00%(说明配置已改为10%,但日志文案未更新) + +**影响**: +- 日志误导,不利于排查问题 + +**解决方案**: +- 更新日志文案,使用实际配置值(动态读取`TAKE_PROFIT_PERCENT`) + +--- + +### 问题2:状态管理不完整 + +**当前实现**: +- 部分止盈后,设置`partialProfitTaken = True` +- 剩余仓位止损移至成本价(保本) +- 但最终平仓时,如果剩余仓位止损,会被标记为`stop_loss` + +**问题**: +- 无法区分"纯止损"和"第一目标止盈后剩余仓位止损" +- 影响胜率、盈亏比统计的准确性 + +**示例场景**: +1. **场景A**:第一目标止盈(50%仓位)→ 剩余仓位第二目标止盈 + - 当前标记:`take_profit` + - ✅ 合理 + +2. **场景B**:第一目标止盈(50%仓位)→ 剩余仓位止损(保本) + - 当前标记:`stop_loss` + - ❌ 不合理:实际上已经实现了第一目标止盈,应该算作"部分成功" + +3. **场景C**:第一目标止盈(50%仓位)→ 剩余仓位移动止损 + - 当前标记:`trailing_stop` + - ⚠️ 不完整:无法区分是"第一目标止盈后移动止损"还是"直接移动止损" + +--- + +### 问题3:统计准确性受影响 + +**当前统计**: +- 胜率 = 盈利单数 / 总单数 +- 盈亏比 = 平均盈利 / 平均亏损 + +**问题**: +- 场景B(第一目标止盈后剩余仓位止损)被算作"亏损单" +- 但实际上已经实现了第一目标止盈(50%仓位盈利),应该算作"部分成功" + +**影响**: +- 胜率被低估 +- 盈亏比被低估 +- 无法准确评估分步止盈策略的效果 + +--- + +## ✅ 优化方案 + +### 方案1:细分订单状态(推荐) + +**新增状态类型**: +- `take_profit_partial`: 第一目标止盈(部分平仓50%) +- `take_profit_partial_then_take_profit`: 第一目标止盈后,剩余仓位第二目标止盈 +- `take_profit_partial_then_stop`: 第一目标止盈后,剩余仓位止损(保本) +- `take_profit_partial_then_trailing_stop`: 第一目标止盈后,剩余仓位移动止损 + +**保留原有状态**: +- `take_profit`: 单次止盈(未启用分步止盈) +- `stop_loss`: 止损 +- `trailing_stop`: 移动止损 +- `manual`: 手动平仓 +- `sync`: 同步平仓 + +--- + +### 方案2:添加阶段标记字段 + +**新增字段**: +- `partial_profit_stage`: 分步止盈阶段 + - `none`: 未启用分步止盈 + - `partial_taken`: 第一目标已达成(50%仓位已平) + - `full_taken`: 第二目标已达成(全部仓位已平) + +**保留`exit_reason`字段**: +- 用于标记最终平仓原因 + +**组合使用**: +- `partial_profit_stage = 'partial_taken'` + `exit_reason = 'stop_loss'` → 第一目标止盈后剩余仓位止损 +- `partial_profit_stage = 'partial_taken'` + `exit_reason = 'take_profit'` → 第一目标止盈后剩余仓位第二目标止盈 + +--- + +## 🎯 推荐方案:方案1(细分状态) + +### 理由 + +1. **更直观**:状态名称直接反映交易过程 +2. **统计方便**:可以直接按状态统计,无需组合字段 +3. **向后兼容**:保留原有状态,不影响现有统计 + +--- + +## 📊 状态映射表 + +| 场景 | 当前状态 | 优化后状态 | 说明 | +|------|----------|------------|------| +| 第一目标止盈(50%)→ 第二目标止盈(剩余50%) | `take_profit` | `take_profit_partial_then_take_profit` | 完整成功 | +| 第一目标止盈(50%)→ 剩余仓位止损(保本) | `stop_loss` | `take_profit_partial_then_stop` | 部分成功 | +| 第一目标止盈(50%)→ 剩余仓位移动止损 | `trailing_stop` | `take_profit_partial_then_trailing_stop` | 部分成功 | +| 单次止盈(未启用分步止盈) | `take_profit` | `take_profit` | 保持 | +| 止损 | `stop_loss` | `stop_loss` | 保持 | +| 移动止损 | `trailing_stop` | `trailing_stop` | 保持 | +| 手动平仓 | `manual` | `manual` | 保持 | +| 同步平仓 | `sync` | `sync` | 保持 | + +--- + +## 🔧 实施步骤 + +### 步骤1:更新数据库结构 + +**SQL迁移脚本**: +```sql +-- 更新exit_reason字段,支持新的状态值 +ALTER TABLE trades MODIFY COLUMN exit_reason VARCHAR(50); + +-- 添加注释说明新状态 +ALTER TABLE trades MODIFY COLUMN exit_reason VARCHAR(50) + COMMENT '平仓原因: manual, stop_loss, take_profit, trailing_stop, sync, take_profit_partial_then_take_profit, take_profit_partial_then_stop, take_profit_partial_then_trailing_stop'; +``` + +--- + +### 步骤2:更新代码逻辑 + +**修改位置**: +- `trading_system/position_manager.py` → `_check_single_position`方法 +- `trading_system/position_manager.py` → `close_position`方法 + +**修改内容**: +1. 第一目标止盈时,记录`partial_profit_taken = True` +2. 剩余仓位平仓时,根据`partial_profit_taken`和实际平仓原因,设置正确的`exit_reason` + +--- + +### 步骤3:更新日志文案 + +**修改位置**: +- `trading_system/position_manager.py` → `_check_single_position`方法 + +**修改内容**: +- 动态读取`TAKE_PROFIT_PERCENT`配置,更新日志文案 + +--- + +### 步骤4:更新统计逻辑 + +**修改位置**: +- `backend/api/routes/stats.py`(如果存在) +- 前端统计展示逻辑 + +**修改内容**: +- 统计时,将`take_profit_partial_then_*`状态归类为"部分成功"或"成功" + +--- + +## 📊 统计分类建议 + +### 胜率统计 + +**成功单**: +- `take_profit` +- `take_profit_partial_then_take_profit` +- `take_profit_partial_then_stop`(第一目标已达成,算作成功) +- `take_profit_partial_then_trailing_stop`(第一目标已达成,算作成功) + +**失败单**: +- `stop_loss` +- `trailing_stop`(如果未启用分步止盈) + +**中性单**: +- `manual` +- `sync` + +--- + +### 盈亏比统计 + +**盈利单**: +- 所有`take_profit*`状态 +- 计算时,`take_profit_partial_then_stop`应该算作盈利(第一目标已达成) + +**亏损单**: +- `stop_loss`(纯止损) +- `trailing_stop`(如果未启用分步止盈) + +--- + +## ⚠️ 注意事项 + +1. **向后兼容**: + - 保留原有状态值,不影响现有数据 + - 新状态仅用于新交易 + +2. **数据迁移**: + - 如果需要,可以编写脚本将历史数据中的`take_profit`状态,根据是否有`partial_profit_taken`标记,转换为新状态 + +3. **日志优化**: + - 更新日志文案,使用实际配置值 + - 添加更详细的状态转换日志 + +--- + +## ✅ 总结 + +**问题**: +- 日志文案与实际配置不一致 +- 状态管理不完整,无法区分"纯止损"和"第一目标止盈后剩余仓位止损" +- 统计准确性受影响 + +**解决方案**: +- 细分订单状态,新增`take_profit_partial_then_*`状态 +- 更新日志文案,使用实际配置值 +- 更新统计逻辑,将"第一目标止盈后剩余仓位止损"算作成功 + +**预期效果**: +- 更准确的状态管理 +- 更准确的胜率、盈亏比统计 +- 更清晰的日志输出 diff --git a/docs/分步止盈状态细分实施完成总结_2026-01-27.md b/docs/分步止盈状态细分实施完成总结_2026-01-27.md new file mode 100644 index 0000000..53b990e --- /dev/null +++ b/docs/分步止盈状态细分实施完成总结_2026-01-27.md @@ -0,0 +1,160 @@ +# 分步止盈状态细分实施完成总结(2026-01-27) + +## ✅ 已完成的优化 + +### 1. 日志文案优化 + +**修改位置**:`trading_system/position_manager.py:2858-2865` + +**优化内容**: +- 动态读取`TAKE_PROFIT_PERCENT`配置值 +- 更新日志文案,使用实际配置值(如10%)而不是硬编码的"30%" + +**效果**: +- 日志更准确,反映实际配置 +- 配置变更后,日志自动更新 + +--- + +### 2. 订单状态细分 + +**新增状态类型**: +- `take_profit_partial_then_take_profit`: 第一目标止盈后,剩余仓位第二目标止盈 +- `take_profit_partial_then_stop`: 第一目标止盈后,剩余仓位止损(保本) +- `take_profit_partial_then_trailing_stop`: 第一目标止盈后,剩余仓位移动止损 + +**保留原有状态**: +- `take_profit`: 单次止盈(未启用分步止盈) +- `stop_loss`: 止损 +- `trailing_stop`: 移动止损 +- `manual`: 手动平仓 +- `sync`: 同步平仓 + +--- + +### 3. 代码修改位置 + +#### 修改1:第二目标止盈状态 +**位置**:`trading_system/position_manager.py:2923` +- 从`take_profit`改为`take_profit_partial_then_take_profit` + +#### 修改2:剩余仓位止损状态(实时监控) +**位置**:`trading_system/position_manager.py:2832-2835` +- 检查`partial_profit_taken`,如果为True,设置`take_profit_partial_then_stop` + +#### 修改3:剩余仓位止损状态(交易所止盈单检查) +**位置**:`trading_system/position_manager.py:1553-1555` +- 检查`partial_profit_taken`,如果为True,根据是否移动止损设置相应状态 + +#### 修改4:剩余仓位移动止损状态(实时监控) +**位置**:`trading_system/position_manager.py:2798-2800` +- 检查`partial_profit_taken`,如果为True,根据是否移动止损设置相应状态 + +--- + +### 4. 数据库迁移脚本 + +**文件**:`backend/database/add_partial_profit_exit_reasons.sql` + +**内容**: +- 更新`exit_reason`字段注释,说明新的状态值 +- 验证字段长度 +- 查看当前状态分布 + +--- + +## 📊 状态映射表 + +| 场景 | 原状态 | 新状态 | 说明 | +|------|--------|--------|------| +| 第一目标止盈(50%)→ 第二目标止盈(剩余50%) | `take_profit` | `take_profit_partial_then_take_profit` | 完整成功 | +| 第一目标止盈(50%)→ 剩余仓位止损(保本) | `stop_loss` | `take_profit_partial_then_stop` | 部分成功 | +| 第一目标止盈(50%)→ 剩余仓位移动止损 | `trailing_stop` | `take_profit_partial_then_trailing_stop` | 部分成功 | +| 单次止盈(未启用分步止盈) | `take_profit` | `take_profit` | 保持 | +| 止损 | `stop_loss` | `stop_loss` | 保持 | +| 移动止损 | `trailing_stop` | `trailing_stop` | 保持 | +| 手动平仓 | `manual` | `manual` | 保持 | +| 同步平仓 | `sync` | `sync` | 保持 | + +--- + +## 🎯 统计分类建议 + +### 胜率统计 + +**成功单**: +- `take_profit` +- `take_profit_partial_then_take_profit`(完整成功) +- `take_profit_partial_then_stop`(部分成功:第一目标已达成) +- `take_profit_partial_then_trailing_stop`(部分成功:第一目标已达成) + +**失败单**: +- `stop_loss`(纯止损) +- `trailing_stop`(如果未启用分步止盈) + +**中性单**: +- `manual` +- `sync` + +--- + +### 盈亏比统计 + +**盈利单**: +- 所有`take_profit*`状态 +- 计算时,`take_profit_partial_then_stop`应该算作盈利(第一目标已达成) + +**亏损单**: +- `stop_loss`(纯止损) +- `trailing_stop`(如果未启用分步止盈) + +--- + +## ⚠️ 注意事项 + +1. **数据库迁移**: + - 执行`backend/database/add_partial_profit_exit_reasons.sql` + - 更新字段注释,说明新的状态值 + +2. **向后兼容**: + - 保留原有状态值,不影响现有数据 + - 新状态仅用于新交易 + +3. **统计逻辑更新**: + - 前端统计展示需要更新,将`take_profit_partial_then_*`状态归类为"成功"或"部分成功" + +--- + +## 📝 下一步 + +1. **执行数据库迁移**: + ```bash + mysql -u root -p auto_trade_sys < backend/database/add_partial_profit_exit_reasons.sql + ``` + +2. **更新前端统计逻辑**: + - 修改统计展示,将`take_profit_partial_then_*`状态归类为"成功" + - 可以添加"部分成功"分类,用于更详细的分析 + +3. **测试验证**: + - 测试分步止盈场景,验证状态设置正确 + - 验证统计结果是否准确 + +--- + +## ✅ 总结 + +**问题**: +- 日志文案与实际配置不一致 +- 状态管理不完整,无法区分"纯止损"和"第一目标止盈后剩余仓位止损" +- 统计准确性受影响 + +**解决方案**: +- ✅ 更新日志文案,使用实际配置值 +- ✅ 细分订单状态,新增`take_profit_partial_then_*`状态 +- ✅ 更新代码逻辑,根据`partial_profit_taken`设置正确的状态 + +**预期效果**: +- ✅ 更准确的状态管理 +- ✅ 更准确的胜率、盈亏比统计 +- ✅ 更清晰的日志输出 diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 551f868..02318fb 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -678,7 +678,7 @@ class PositionManager: Args: symbol: 交易对 - reason: 平仓原因(manual, stop_loss, take_profit, trailing_stop, sync) + reason: 平仓原因(manual, stop_loss, take_profit, trailing_stop, sync, take_profit_partial_then_take_profit, take_profit_partial_then_stop, take_profit_partial_then_trailing_stop) Returns: 是否成功 @@ -1552,7 +1552,15 @@ class PositionManager: # 直接比较当前盈亏百分比与止损目标(基于保证金) if pnl_percent_margin <= -stop_loss_pct_margin: should_close_due_to_sl = True - exit_reason_sl = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' + # ⚠️ 2026-01-27优化:如果已部分止盈,细分状态 + partial_profit_taken = position_info.get('partialProfitTaken', False) + if partial_profit_taken: + if position_info.get('trailingStopActivated'): + exit_reason_sl = 'take_profit_partial_then_trailing_stop' + else: + exit_reason_sl = 'take_profit_partial_then_stop' + else: + exit_reason_sl = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' # 计算持仓时间 entry_time = position_info.get('entryTime') @@ -2797,7 +2805,14 @@ class PositionManager: # 直接比较当前盈亏百分比与止损目标(基于保证金) if pnl_percent_margin <= -stop_loss_pct_margin: should_close_due_to_sl = True - exit_reason_sl = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' + # ⚠️ 2026-01-27优化:如果已部分止盈,细分状态 + if partial_profit_taken: + if position_info.get('trailingStopActivated'): + exit_reason_sl = 'take_profit_partial_then_trailing_stop' + else: + exit_reason_sl = 'take_profit_partial_then_stop' + else: + exit_reason_sl = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss' # 计算持仓时间 entry_time = position_info.get('entryTime') @@ -2829,6 +2844,11 @@ class PositionManager: logger.warning(f" 移动止损: 已激活(从初始止损 {position_info.get('initialStopLoss', 'N/A')} 调整)") logger.warning("=" * 80) + # ⚠️ 2026-01-27优化:如果已部分止盈,细分状态为"第一目标止盈后剩余仓位止损" + if partial_profit_taken: + exit_reason_sl = 'take_profit_partial_then_stop' + logger.info(f"{symbol} [实时监控] 第一目标止盈后,剩余仓位触发止损(保本)") + # ⚠️ 关键修复:止损必须立即执行,不受时间锁限制 if await self.close_position(symbol, reason=exit_reason_sl): logger.info(f"{symbol} [实时监控] 止损平仓成功(不受时间锁限制)") @@ -2857,11 +2877,16 @@ class PositionManager: # 直接比较当前盈亏百分比与第一目标(基于保证金) if pnl_percent_margin >= take_profit_1_pct_margin: + # ⚠️ 2026-01-27优化:动态读取配置值,更新日志文案 + take_profit_pct_config = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10) + if take_profit_pct_config > 1: + take_profit_pct_config = take_profit_pct_config / 100.0 + take_profit_pct_display = take_profit_pct_config * 100 logger.info( - f"{symbol} [实时监控] 触发第一目标止盈(30%固定止盈,基于保证金): " + f"{symbol} [实时监控] 触发第一目标止盈({take_profit_pct_display:.1f}%固定止盈,基于保证金): " f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 目标={take_profit_1_pct_margin:.2f}% of margin | " f"当前价={current_price_float:.4f}, 目标价={take_profit_1:.4f} | " - f"将平掉50%仓位,锁定30%盈利,剩余50%追求更高收益" + f"将平掉50%仓位,锁定{take_profit_pct_display:.1f}%盈利,剩余50%追求更高收益" ) # 部分平仓50% partial_quantity = quantity * 0.5 @@ -2917,7 +2942,8 @@ class PositionManager: # 直接比较剩余仓位盈亏百分比与第二目标(基于保证金) if remaining_pnl_pct_margin >= take_profit_2_pct_margin: should_close = True - exit_reason = 'take_profit' + # ⚠️ 2026-01-27优化:细分状态,区分"第一目标止盈后第二目标止盈" + exit_reason = 'take_profit_partial_then_take_profit' logger.info( f"{symbol} [实时监控] 触发第二目标止盈(4.0:1,山寨币策略): " f"剩余仓位盈亏={remaining_pnl_pct_margin:.2f}% of margin >= 目标={take_profit_2_pct_margin:.2f}% of margin | "