auto_trade_sys/trading_system/STOP_LOSS_AND_TRAILING_ANALYSIS.md
薇薇安 c53b67e294 fix(position_manager): 优化止损和止盈价格的获取与应用逻辑
在持仓管理中,增强了从交易所获取止损和止盈价格的逻辑,确保在决定使用交易所的止损或止盈时,优先考虑当前市场条件。同时,调整了止损和止盈的设置流程,确保在同步到交易所前,正确判断并应用已有的止损策略。这一改动旨在提升风险控制能力,确保交易策略的有效性与稳定性。
2026-02-26 09:09:07 +08:00

9.4 KiB
Raw Blame History

止损与移动止损影响点分析

一、止损/移动止损涉及的所有环节

1. 开仓时(初始止损)

位置 作用 可能影响
position_manager.open_position 调用 risk_manager.get_stop_loss_price() 得到初始止损价,写入 position_info['stopLoss']initialStopLoss 初始止损来自 ATR 或 STOP_LOSS_PERCENT保证金比例
开仓后 _ensure_exchange_sltp_orders(symbol, position_info) 把 SL/TP 挂到币安 条件单显示的是此时传入的 stopLoss/takeProfit
config.TRADING_CONFIG STOP_LOSS_PERCENTUSE_ATR_STOP_LOSSATR_STOP_LOSS_MULTIPLIER Redis/配置若把止损压得很紧,容易被打掉
  • 结论:开仓时只设「初始止损」并同步到交易所,是符合设计的;盈利后应由「监控」把止损上移到保本/移动止损。

2. 策略与配置(是否做移动止损)

配置项 默认 说明
PROFIT_PROTECTION_ENABLED True 关闭后不做保本/移动止损
USE_TRAILING_STOP True 关闭后不做移动止损(保本仍可做,若上面为 True
LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT 0.03 盈利达保证金 3% 时移至保本0=关闭)
TRAILING_STOP_ACTIVATION 0.10 盈利达保证金 10% 后激活移动止损
TRAILING_STOP_PROTECT 0.02 移动止损保护利润 2%
FEE_BUFFER_PCT 0.0015 保本价含手续费缓冲
  • 读取方式position_manager._check_single_position 里用 config.TRADING_CONFIG.get(...) 直接读,没有get_effective_config
  • 结论:移动止损/保本不受市场状态low_volatility/normal切换影响;只有 Redis/DB 里把 PROFIT_PROTECTION_ENABLEDUSE_TRAILING_STOP 设为 False 时才会不做。

3. 市场状态market regime

  • get_effective_config 只用于:TIME_STOP_MAX_HOLD_HOURSMAX_POSITION_PERCENTMIN_SIGNAL_STRENGTHSCAN_FUNDING_RATE_MAX_ABS 等。
  • 结论:保本/移动止损相关配置不经过 market regime市场状态不会导致“不能移动止损”。

4. 监控与同步到交易所(核心)

环节 说明 可能导致「条件单仍是初始止损」的原因
谁触发检查 _check_single_position(symbol, current_price) 在两种时机被调用:① WebSocket @ticker 收到价格;② 启动时「仅币安有仓」接入后立即调一次。 若 WS 断线/未连,只有启动那一次检查;若当时盈利未达 3%,不会移保本,后续又没价格推送,就不会再更新。
保本条件 lock_pct > 0not breakevenStopSetpnl_percent_margin >= lock_pct*100(如 3%)→ 设 position_info['stopLoss'] = breakevenbreakevenStopSet = True,再调 _ensure_exchange_sltp_orders 条件不满足(盈利不够、或已标 breakevenStopSet、或下面同步失败。
同步到交易所 _ensure_exchange_sltp_orders:先取消旧条件单,再按当前 position_info['stopLoss']/takeProfit2 挂新单。 EXCHANGE_SLTP_ENABLED=False 会直接 return或取消/挂单 API 报错(-2021、网络等被 catch 后只打 log条件单未更新。
保本/移动止损价合法性 多单 stop_loss >= entry、空单 stop_loss <= entry 时视为保本/移动止损,不会被改成亏损价。 无(此处已允许保本/移动止损)。

5. 补建(状态同步)——重要缺口

场景 行为 问题
补建币安有仓、DB 无 open 记录,且 sync_recover 等允许补建) risk_manager.get_stop_loss_price()初始止损,写入 position_info,然后调用 _ensure_exchange_sltp_orders 先读交易所当前 SL/TP。若交易所上已经是保本或移动止损会被这次「初始止损」覆盖条件单又变回亏损价。
仅币安有仓(无 DB 记录、走「仅币安有仓」分支) _get_sltp_from_exchange(symbol, side),有则用交易所 SL/TP 填 position_info,缺的才用 risk_manager。 逻辑正确,不会覆盖已有保本。
  • 结论补建路径在「建 position_info + 第一次挂 SL/TP」时没有使用交易所现有止损用初始止损覆盖已在交易所的保本/移动止损,是导致「盈利单条件单仍显示一开始的止损」的一个重要原因。

6. 其他

  • EXCHANGE_SLTP_ENABLED:配置为 False 时,_ensure_exchange_sltp_orders 不执行,任何保本/移动止损都不会写到交易所。
  • listen key / User Data Stream:只影响订单/持仓推送,不影响价格流。价格来自 @ticker 公开流,不依赖 listen key。但若进程重启或监控未启动就不会收到价格也就不会跑 _check_single_position

二、根因归纳(为何条件单止损不是正盈利)

  1. 补建时用初始止损覆盖交易所
    重启或状态同步时,对「缺 DB 记录」的持仓做补建,直接用 risk_manager 的初始止损挂单,没有先读交易所当前 SL若交易所已是保本/移动止损,会被覆盖回初始止损。

  2. 监控未及时或未执行

    • WebSocket 断线/重连导致一段时间没有价格推送,_check_single_position 不跑。
    • 或启动时只执行了一次检查,当时盈利未达 3%,之后没有新价格触发再次检查。
  3. 配置关闭了保护
    Redis/DB 中 PROFIT_PROTECTION_ENABLEDUSE_TRAILING_STOP 为 False则不会做保本/移动止损。

  4. 同步到交易所失败
    _ensure_exchange_sltp_orders 内取消/挂单失败(如 -2021、网络异常只打 warning条件单未更新。


三、补建路径 SL/TP 执行顺序(必须严格保证)

两处补建(系统单补建、手动开仓补建)都按同一逻辑顺序执行,保证「不先用初始止损覆盖交易所已有保本/移动止损」:

步骤 动作 说明
1 先读交易所 _get_sltp_from_exchange(symbol, side) 得到当前条件单上的 sl_from_extp_from_ex
2 算保本价 breakeven = _breakeven_stop_price(entry_price, side),用于判断交易所止损是否已达保本或更优。
3 决定止损 sl_from_ex 存在且多单 sl_from_ex >= breakeven / 空单 sl_from_ex <= breakeven → 采用交易所止损、breakevenStopSet=True;否则用 risk_manager.get_stop_loss_price(...) 作为初始止损。initialStopLoss 始终记录 risk_manager 的初始值(用于展示/统计)。
4 决定止盈 tp_from_ex 存在则用交易所止盈,否则用 risk_manager.get_take_profit_price(...)
5 组装 position_info 用上面决定好的 stop_loss_pricetake_profit_priceinitial_stop_lossbreakevenStopSet 写入 position_info
6 写入内存 self.active_positions[symbol] = position_info
7 同步到交易所(仅系统单补建) 系统单补建会调用 _ensure_exchange_sltp_orders(symbol, position_info, current_price),用当前 position_info 的 SL/TP 挂单,因此传入的必须是步骤 3/4 决定好的价格,这样不会把已有保本覆盖成初始止损。手动开仓补建不在此处调 _ensure_exchange_sltp_orders,仅靠 position_info 正确,后续监控若同步时不会误覆盖。

错误顺序示例:若先算 stop_loss_price = risk_manager.get_stop_loss_price(...),再 position_info = {..., "stopLoss": stop_loss_price},再 _ensure_exchange_sltp_orders(...),就会用初始止损覆盖交易所上已有的保本/移动止损。因此必须先读交易所、再决定用哪套价格、最后再挂单或写内存。


四、已实现修复

  1. 补建时优先使用交易所 SL/TPposition_manager.py

    • 系统单补建(约 34053439 行):在计算 stop_loss_price 前先 _get_sltp_from_exchange(symbol, side);若 sl_from_ex 已达保本或更优(多单 sl >= breakeven,空单 sl <= breakeven),则用 sl_from_ex 作为 position_info['stopLoss'],并设 breakevenStopSet=TrueinitialStopLoss 仍为 risk_manager 的初始值用于记录;止盈同理,有则用 tp_from_ex
    • 手动开仓补建(约 35243590 行):同样先读交易所 SL/TP若止损已达保本则采用并设 breakevenStopSet=True
      这样重启或状态同步触发的补建不会用初始止损覆盖交易所上已有的保本/移动止损。
  2. 补建 position_info 补全 breakevenStopSet
    两处补建在构建 position_info 时均显式写入 breakevenStopSet(及 trailingStopActivated),与「仅币安有仓」分支一致。

  3. 代码内注释
    系统单补建处(约 3405 行起)与手动开仓补建处(约 3532 行起)已加「执行顺序」注释块,与本节一致。

  4. 排查与观测

    • 确认 Redis 中 PROFIT_PROTECTION_ENABLEDUSE_TRAILING_STOP 为 TrueLOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT 为 0.03(或期望值)。
    • 看日志是否有「[补建] 使用交易所已有止损(保本/移动)」或「[补建-手动] 使用交易所已有止损…」,以及「同步…失败」类告警。
    • 补建修复后,盈利单条件单在重启/同步后应保持为正盈利(保本或移动止损)。