Files
mt-data-mcp/止损方法梳理与伪代码.md
gavindiazandClaude Code bd2253af01 docs: sync trading/research docs and add the risk-library mapping
Root README now points at the auto-trading shell and research pipeline;
AGENTS.md gains a keep-docs-in-sync rule; trading/README.md maps every method
from the two research docs to trading/risk and documents manage_position;
research/README.md documents the backtest hook and --backtest CLI.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-25 20:21:46 +08:00

53 KiB
Raw Permalink Blame History

止损方法梳理与平台无关伪代码

目的:从 MQL5 知识库中提炼出常见的止损方法,去除所有 MQL5 平台特性(PositionModify、iATR、SymbolInfoDouble、MqlTradeRequest、TRADE_RETCODE、SymbolInfoInteger、_Point/_Digits、CTrade、PositionGet*、PositionSelectByTicket 等),改写为 Python-like 伪代码,便于向 Python、JavaScript、C++、其他交易框架移植时作为灵感参考。

所有代码均使用统一的抽象层,如 position、broker、account、indicator、get_last_price()、modify_stop_loss()、close_position() 等。每个方法末尾会列出核心参数含义与实现注意点。


0. 抽象层约定(供全文伪代码复用)

# 统一抽象对象(不同平台对应不同实现)
class Position:
    ticket: int
    symbol: str
    side: str           # "buy" 或 "sell"
    open_price: float
    open_time: datetime
    size: float         # 合约数量
    stop_loss: float    # 0 表示未设置
    take_profit: float  # 0 表示未设置
    current_price: float
    unrealized_pnl: float
    magic_number: int
    comment: str

class Broker:
    point: float                    # 最小价格单位(1 个 tick)
    pip: float                      # 1 pip 对应的价格单位
    digits: int                     # 报价小数位
    stop_level_points: int          # 止损最小距离(点)
    freeze_level_points: int        # 冻结价距离
    volume_min: float
    volume_max: float
    volume_step: float              # 手数步长
    tick_size: float
    tick_value: float
    def normalize(price: float) -> float: ...
    def current_ask(symbol: str) -> float: ...
    def current_bid(symbol: str) -> float: ...

class Account:
    balance: float
    equity: float
    margin: float
    free_margin: float

# 常用动作抽象
def modify_stop_loss(pos: Position, new_sl: float): bool: ...
def close_position(pos: Position): bool: ...
def close_partial(pos: Position, size: float): bool: ...
def get_indicator(symbol: str, name: str, period: int, shift: int = 0) -> float: ...
def get_bars(symbol: str, timeframe: str, count: int) -> list: ...

1. 方法总览表

# 方法 触发条件 计算逻辑核心 典型使用场景
1 固定点数止损 开仓时 SL = open ± fixed_pips * pip 通用;新手 EA
2 固定金额止损(按风险反推手数) 开仓时 lots = risk_amount / loss_per_lot 稳健风控
3 ATR 倍数止损 开仓时 SL = open ∓ atr * multiplier 趋势/波动跟随
4 波动率范围过滤止损 开仓前 if atr < min or atr > max: 拒绝 规避极端行情
5 结构位止损(Swing 高/低点) 开仓时 SL = open ∓ (price - swing_extreme) S/R 打法
6 一目均衡云图结构止损 开仓时 SL = 前引带 A/B 或基准线 一目策略
7 移动平均线止损(MA Trailing) 每 tick if price 穿越 MA: 平仓;否则 SL = MA 趋势跟踪
8 吊灯止损(Chandelier Exit) 每 tick SL = max(high_N) - k * ATR 趋势跟随
9 PSAR 止损 每 tick SL = PSAR 值 反转识别
10 经典移动止损(Trail Start/Stop/Step) 每 tick 触发→距离→步长 三层控制 通用追踪
11 ATR 移动止损 每 tick SL = current ∓ ATR * mult,仅收紧 波动自适应追踪
12 激进 R 倍数追踪止损 每 tick 盈利达 N×R 后启动,短距离追踪 剥头皮
13 保本止损(固定阈值) 每 tick if profit_pips >= trigger: SL = open 通用
14 保本止损(带偏移) 每 tick if profit >= trigger: SL = open + offset 锁盈
15 分批止盈/分批止损(阶梯 TP) 每 tick 分档比例平仓,剩余上移 SL 波段策略
16 时间止损 每 tick if hold_time > max_minutes: 平仓 剥头皮/日内
17 动量反转退出 每 tick if momentum 反转: 市价平仓 剥头皮
18 网格/金字塔对冲止损 每次加仓 每层 SL=上一层开仓价;RR 决定 TP 逆势策略
19 账户净值追踪止损 每 tick equity <= peak - trail * peak: 全平 账户级
20 账户最大回撤止损 每 tick equity/equity_peak - 1 <= -max_drawdown: 全平 账户级
21 每日亏损百分比止损 每 tick if daily_pnl/balance <= -limit: 停止交易 账户级
22 交易次数限制 开仓前 按日/会话/小时限制 账户级
23 冷却时间止损 开仓前 亏损后/开仓后 N 分钟内禁开 账户级
24 时段过滤 开仓前 只在允许会话内开仓 账户级
25 点差上限过滤 开仓前 if spread > max_spread: 拒绝 账户级
26 新闻/事件止损 每 tick 事件窗口内全平+关闭交易 宏观事件

2. 初始止损类(开仓时确定)

2.1 固定点数止损

最简单的形式,开仓时按点数决定 SL 位置。

def set_fixed_point_stop(pos: Position, broker: Broker, pips: int):
    if pos.side == "buy":
        new_sl = pos.open_price - pips * broker.pip
    else:
        new_sl = pos.open_price + pips * broker.pip
    new_sl = broker.normalize(new_sl)
    modify_stop_loss(pos, new_sl)

参数:pips = 止损距离(pips)。

知识点:MQL5 中常见写法 Ask - 100 * _Point,100 点通常按 5 位小数的 10 个 pip 换算,跨品种需要判断 digits 是否为 3 或 5。移植时建议直接用 pip 单位,屏蔽 point/pip 换算。


2.2 固定金额止损(风险百分比仓位)

以"愿意亏多少钱"倒推手数与止损的组合。

def calc_lot_by_risk_percent(account: Account, broker: Broker,
                             open_price: float, sl_price: float,
                             risk_percent: float) -> float:
    sl_distance = abs(open_price - sl_price)
    if sl_distance <= 0 or broker.tick_value <= 0 or broker.tick_size <= 0:
        return 0.0

    risk_amount = account.balance * risk_percent / 100.0
    loss_per_lot = (sl_distance / broker.tick_size) * broker.tick_value

    if loss_per_lot <= 0:
        return 0.0

    lots = risk_amount / loss_per_lot
    lots = max(broker.volume_min, min(lots, broker.volume_max))
    if broker.volume_step > 0:
        lots = math.floor(lots / broker.volume_step) * broker.volume_step
    return broker.normalize(lots, 2)

参数:

  • risk_percent:单笔风险占总资金的百分比(常见 0.5%~3%)。
  • 返回值:建议手数,最终需与实际止损一起使用才能锁定风险。

知识点:这是几乎所有成熟交易系统的基石。原 MQL5 代码中 lossPerLot = (slDist / tickSize) * tickValue 是最核心的公式,跨品种通用。


2.3 ATR 倍数止损

按真实波幅 ATR 决定 SL 距离,自动适应波动。

def set_atr_stop(pos: Position, broker: Broker,
                 symbol: str, atr_period: int = 14,
                 atr_mult: float = 1.5, shift: int = 1):
    atr = get_indicator(symbol, "ATR", atr_period, shift)
    if atr <= 0:
        return

    sl_distance = atr * atr_mult
    # 强制不低于经纪商最小止损距离
    sl_distance = max(sl_distance, broker.stop_level_points * broker.point)

    if pos.side == "buy":
        new_sl = pos.open_price - sl_distance
    else:
        new_sl = pos.open_price + sl_distance
    modify_stop_loss(pos, broker.normalize(new_sl))

参数:

  • atr_period:ATR 周期(默认 14)。
  • atr_mult:ATR 倍数(0.8 紧致,1.5~3 宽松)。
  • shift:ATR 取哪根 K 线(1 = 已收盘的前一根,最常用)。

知识点:

  • shift=1 避免用未收盘 K 线的 ATR(数值不稳定)。
  • 必须强制 ≥ stop_level_points,否则经纪商会拒单。

2.4 ATR + 结构位止损(取最大者)

在剥头皮/激进模式下,把 ATR 止损与最近 N 根 K 线的高低点结构位合并,取距离更大者(更保守)。

def set_atr_or_swing_stop(pos: Position, broker: Broker,
                          symbol: str, timeframe: str,
                          atr_mult: float = 0.8, atr_period: int = 14,
                          lookback_bars: int = 10):
    atr = get_indicator(symbol, "ATR", atr_period, 1)
    atr_distance = atr * atr_mult

    bars = get_bars(symbol, timeframe, lookback_bars)
    last_price = broker.current_ask(symbol) if pos.side == "buy" \
                 else broker.current_bid(symbol)

    if pos.side == "buy":
        swing_low = min(b.low for b in bars)
        swing_distance = last_price - swing_low
    else:
        swing_high = max(b.high for b in bars)
        swing_distance = swing_high - last_price

    sl_distance = max(atr_distance, swing_distance)
    sl_distance = max(sl_distance, broker.stop_level_points * broker.point)

    new_sl = last_price - sl_distance if pos.side == "buy" else last_price + sl_distance
    modify_stop_loss(pos, broker.normalize(new_sl))

知识点:这是"机械止损 + 结构位"的组合。MathMax(atr_dist, swing_dist) 是关键,避免止损被随机噪声或局部波动打掉。


2.5 一目均衡云图结构止损

用一目均衡表(Ichimoku)的前引带 A/B 与基准线确定 SL 位置。

def set_ichimoku_stop(pos: Position, broker: Broker,
                      senkou_span_a: float, senkou_span_b: float,
                      kijun: float):
    if pos.side == "buy":
        # 前引带 B < 前引带 A,且基准线在中间 → 结构清晰,SL 放到前引带 B
        if senkou_span_b < senkou_span_a and senkou_span_b < kijun < senkou_span_a:
            new_sl = senkou_span_b
        # 前引带 A < 前引带 B,且基准线在中间 → SL 放到前引带 A
        elif senkou_span_a < senkou_span_b and kijun > senkou_span_a and kijun < senkou_span_b:
            new_sl = senkou_span_a
        else:
            new_sl = kijun  # 结构不清时退化为基准线止损
    else:
        # SELL 方向对称逻辑(略)
        ...
    modify_stop_loss(pos, broker.normalize(new_sl))

知识点:结构位止损在趋势明确时能提供比固定点数更好的风控,因为止损位置在自然支撑/阻力之外。


3. 波动率类过滤(开仓前拦截)

3.1 ATR 上下限过滤

只在合理波动区间内开仓,避免极端行情。

def volatility_guard(symbol: str, atr_period: int = 14,
                     min_atr: float = 0.0003, max_atr: float = 0.003) -> bool:
    atr = get_indicator(symbol, "ATR", atr_period, 1)
    if atr <= 0:
        return False
    return min_atr <= atr <= max_atr

参数:

  • min_atr:过低会缺乏机会,或触发"死水"行情。
  • max_atr:过高说明已进入异常波动,风险放大。

3.2 ADR / 波动带过滤(拓展思路)

def volatility_band_guard(symbol: str, timeframe: str,
                          lookback: int = 20,
                          low_pct: float = 0.2, high_pct: float = 0.8):
    """取最近 N 根 K 线的振幅作为波动区间,
       当前振幅应在历史 [low_pct, high_pct] 分位之间才允许交易。"""
    bars = get_bars(symbol, timeframe, lookback)
    ranges = [(b.high - b.low) for b in bars]
    current_range = ranges[-1]
    sorted_ranges = sorted(ranges)
    low_threshold = sorted_ranges[int(low_pct * len(sorted_ranges))]
    high_threshold = sorted_ranges[int(high_pct * len(sorted_ranges))]
    return low_threshold <= current_range <= high_threshold

4. 移动止损类(持仓期调整)

4.1 经典移动止损(Trail Start / Trail Stop / Trail Step)

三层控制:触发启动 → 距离跟踪 → 步长保护。

def update_classic_trailing(pos: Position, broker: Broker,
                            trail_start_pips: float,
                            trail_stop_pips: float,
                            trail_step_pips: float):
    if pos.stop_loss <= 0 and pos.unrealized_pnl <= 0:
        return  # 无初始 SL 时通常依赖 EA 首次触发

    start_dist = trail_start_pips * broker.pip
    stop_dist  = trail_stop_pips * broker.pip
    step_dist  = trail_step_pips * broker.pip

    current_price = broker.current_bid(symbol=pos.symbol) if pos.side == "buy" \
                    else broker.current_ask(symbol=pos.symbol)
    open_dist = abs(current_price - pos.open_price)

    if pos.side == "buy":
        # 只有价格上涨超过启动距离才启动追踪
        if open_dist < start_dist:
            return
        candidate = current_price - stop_dist
        # 候选必须优于当前 SL(BUY 时更高)
        if candidate <= pos.stop_loss:
            return
        # 步长保护:不足一个 step 时,推进一个 step
        if pos.stop_loss > 0 and (candidate - pos.stop_loss) < step_dist:
            candidate = pos.stop_loss + step_dist
    else:
        if open_dist < start_dist:
            return
        candidate = current_price + stop_dist
        if candidate >= pos.stop_loss:
            return
        if pos.stop_loss > 0 and (pos.stop_loss - candidate) < step_dist:
            candidate = pos.stop_loss - step_dist

    candidate = broker.normalize(candidate)
    modify_stop_loss(pos, candidate)

参数:

  • trail_start_pips:触发距离。价格离开开仓价多少才启动追踪(如 50 pip)。
  • trail_stop_pips:跟踪距离。SL 与现价之间的固定距离(如 20 pip)。
  • trail_step_pips:步长。一次修改 SL 的最小幅度(如 10 pip),抑制高频修改。

知识点:

  • 单向收紧是隐含规则:BUY 只上移,SELL 只下移。
  • 步长保护能显著降低服务器请求与手续费。
  • 修改前必须 new_sl 严格优于 pos.stop_loss,否则跳过。

4.2 ATR 移动止损

用 ATR 动态决定 SL 与现价的距离,波动大时距离远,波动小时距离近。

def update_atr_trailing(pos: Position, broker: Broker,
                        atr_period: int = 14, atr_mult: float = 1.0,
                        atr_shift: int = 1):
    atr = get_indicator(pos.symbol, "ATR", atr_period, atr_shift)
    if atr <= 0:
        return

    if pos.side == "buy":
        bid = broker.current_bid(symbol=pos.symbol)
        candidate = bid - atr * atr_mult
        # 必须满足经纪商最小止损距离
        if candidate > bid - broker.stop_level_points * broker.point:
            return
        # 只上移
        if candidate <= pos.stop_loss:
            return
    else:
        ask = broker.current_ask(symbol=pos.symbol)
        candidate = ask + atr * atr_mult
        if candidate < ask + broker.stop_level_points * broker.point:
            return
        # 只下移
        if candidate >= pos.stop_loss:
            return

    # 按 tick_size 对齐精度
    if broker.tick_size > 0:
        candidate = round(candidate / broker.tick_size) * broker.tick_size
    modify_stop_loss(pos, broker.normalize(candidate))

参数:

  • atr_period:ATR 周期。
  • atr_mult:倍数。倍数为 1 时 SL 距离 ≈ 平均波动幅度。

知识点:

  • 相比固定 pip 追踪,ATR 追踪能自动适应不同品种和时间段。
  • 一定要对齐 tick_size,避免精度错误被经纪商拒绝。

4.3 移动平均线止损(MA Trailing)

将 SL 挂到 MA 值;如果价格已穿越 MA,则直接市价平仓。

def update_ma_trailing(pos: Position, broker: Broker,
                       symbol: str, ma_period: int,
                       ma_shift: int = 1):
    ma = get_indicator(symbol, "MA", ma_period, ma_shift)
    if ma <= 0:
        return

    current_price = broker.current_bid(symbol) if pos.side == "buy" \
                    else broker.current_ask(symbol)

    # 触发条件:价格已穿越均线(趋势反转)
    if pos.side == "buy" and ma > current_price:
        close_position(pos)
        return
    if pos.side == "sell" and ma < current_price:
        close_position(pos)
        return

    # 否则把 SL 挂到 MA 值(仅当与当前 SL 不同时才修改)
    if abs(pos.stop_loss - ma) > broker.point:
        modify_stop_loss(pos, broker.normalize(ma))

参数:

  • ma_period:均线周期(如 20、50、200)。
  • ma_shift:取哪根 K 线的均线值(1 = 已收盘前一根)。

知识点:

  • "穿越均线 = 市价平仓" 是趋势反转的强势信号,比继续持有到止损更果断。
  • 均线追踪天然具备单向收紧特性(BUY 时 MA 越上移 SL 越高)。

4.4 吊灯止损(Chandelier Exit)

以最近 N 根 K 线最高价(或最低价)为基准,减去/加上 k × ATR 得到 SL。

def update_chandelier(pos: Position, broker: Broker,
                      symbol: str, timeframe: str,
                      chandelier_period: int = 22,
                      atr_period: int = 22,
                      chandelier_mult: float = 3.0):
    bars = get_bars(symbol, timeframe, chandelier_period)
    atr = get_indicator(symbol, "ATR", atr_period, 1)
    if not bars or atr <= 0:
        return

    if pos.side == "buy":
        highest = max(b.high for b in bars)
        candidate = highest - chandelier_mult * atr
    else:
        lowest = min(b.low for b in bars)
        candidate = lowest + chandelier_mult * atr

    # 只收紧
    if pos.side == "buy" and candidate <= pos.stop_loss:
        return
    if pos.side == "sell" and candidate >= pos.stop_loss:
        return

    modify_stop_loss(pos, broker.normalize(candidate))

参数:

  • chandelier_period:回看 K 线数(常见 22)。
  • chandelier_mult:倍数(常见 2~3)。

知识点:吊灯止损本质是"结构位 + ATR"的融合,兼顾了止损的自然支撑属性和波动自适应。适合趋势跟踪策略。


4.5 PSAR 止损

用 Parabolic SAR 值作为 SL,一旦价格突破 SAR 即反向平仓。

def update_psar_stop(pos: Position, broker: Broker,
                     symbol: str, timeframe: str):
    psar = get_indicator(symbol, "PSAR", 0, shift=0)  # 取当前值
    psar_prev = get_indicator(symbol, "PSAR", 0, shift=1)
    if psar <= 0 or psar_prev <= 0:
        return

    if pos.side == "buy":
        # 价格跌破 SAR → 平仓
        if broker.current_bid(symbol) < psar:
            close_position(pos)
            return
        if psar <= pos.stop_loss:
            return  # 只收紧
    else:
        if broker.current_ask(symbol) > psar:
            close_position(pos)
            return
        if psar >= pos.stop_loss:
            return

    modify_stop_loss(pos, broker.normalize(psar))

4.6 激进 R 倍数追踪止损(剥头皮)

先在盈利达到 N 个 R 后启动,然后用极小距离+步长追踪。

def update_aggressive_trailing(pos: Position, broker: Broker,
                               initial_risk: float,          # 开仓时记录的初始风险距离
                               activate_r: float = 0.5,
                               trail_pips: float = 3.0,
                               step_pips: float = 1.0):
    if initial_risk <= 0:
        return

    profit_pips = (pos.unrealized_pnl / broker.tick_value) / broker.tick_size
    if pos.side == "sell":
        profit_pips = -profit_pips
    activate_threshold = initial_risk * activate_r

    if profit_pips * broker.pip < activate_threshold:
        return  # 盈利未达阈值

    stop_dist = trail_pips * broker.pip
    step_dist = step_pips * broker.pip

    current_price = broker.current_bid(symbol=pos.symbol) if pos.side == "buy" \
                    else broker.current_ask(symbol=pos.symbol)

    if pos.side == "buy":
        candidate = current_price - stop_dist
        if candidate <= pos.stop_loss:
            return
        if pos.stop_loss > 0 and (candidate - pos.stop_loss) < step_dist:
            candidate = pos.stop_loss + step_dist
    else:
        candidate = current_price + stop_dist
        if candidate >= pos.stop_loss:
            return
        if pos.stop_loss > 0 and (pos.stop_loss - candidate) < step_dist:
            candidate = pos.stop_loss - step_dist

    modify_stop_loss(pos, broker.normalize(candidate))

参数:

  • initial_risk:开仓时记录的初始 SL 距离(R 的定义)。
  • activate_r:启动倍数,如 0.5 表示盈利 0.5R 后启动。
  • trail_pips、step_pips:小数值,用于剥头皮级别的紧致追踪。

知识点:一定要在开仓时把 initial_risk 存到持仓的结构体或注释里,因为保本/移动止损会把 SL 移走,之后再算就失去"原始 R" 的含义。


5. 保本止损类

5.1 固定盈利阈值保本

浮盈达到阈值后立即把 SL 移到开仓价。

def update_breakeven_simple(pos: Position, broker: Broker,
                            trigger_pips: float):
    trigger_dist = trigger_pips * broker.pip
    if pos.side == "buy":
        profit_dist = broker.current_bid(symbol=pos.symbol) - pos.open_price
    else:
        profit_dist = pos.open_price - broker.current_ask(symbol=pos.symbol)

    if profit_dist >= trigger_dist:
        # 已经保本就跳过(BUY: SL >= open 即可)
        if pos.side == "buy" and pos.stop_loss >= pos.open_price:
            return
        if pos.side == "sell" and pos.stop_loss <= pos.open_price:
            return
        # 目标:SL = open_price
        modify_stop_loss(pos, broker.normalize(pos.open_price))

参数:

  • trigger_pips:触发保本所需浮盈(如 5 pip)。

知识点:

  • 判断"是否已保本"要比较 SL 与 open_price,而不是判断是否已修改过——因为可能已经用移动止损把 SL 移到了更远的位置。
  • 常见变体是"用金额"触发(profit >= 0.5 美元),跨品种一致性差,不推荐。

5.2 保本 + 偏移(锁定小利)

保本之上再加一段偏移,确保平掉时是净盈利。

def update_breakeven_offset(pos: Position, broker: Broker,
                            trigger_pips: float, offset_pips: float):
    trigger_dist = trigger_pips * broker.pip
    offset_dist = offset_pips * broker.pip

    if pos.side == "buy":
        bid = broker.current_bid(symbol=pos.symbol)
        if (bid - pos.open_price) < trigger_dist:
            return
        target = pos.open_price + offset_dist
        if target <= pos.stop_loss:
            return
    else:
        ask = broker.current_ask(symbol=pos.symbol)
        if (pos.open_price - ask) < trigger_dist:
            return
        target = pos.open_price - offset_dist
        if target >= pos.stop_loss:
            return

    modify_stop_loss(pos, broker.normalize(target))

参数:

  • offset_pips:超过开仓价的额外锁利距离。

5.3 分批保本(阶梯保本)

在多个盈利档位分别把 SL 移到 open + level × pip。

def update_stepped_breakeven(pos: Position, broker: Broker,
                              levels: list):
    """levels = [(trigger_pips, target_pips), ...]
       例:[(5, 0), (15, 8), (30, 20)]"""
    if pos.side == "buy":
        bid = broker.current_bid(symbol=pos.symbol)
        profit_pips = (bid - pos.open_price) / broker.pip
    else:
        ask = broker.current_ask(symbol=pos.symbol)
        profit_pips = (pos.open_price - ask) / broker.pip

    for trigger_pips, target_pips in levels:
        if profit_pips >= trigger_pips:
            if pos.side == "buy":
                target = pos.open_price + target_pips * broker.pip
                if target <= pos.stop_loss:
                    return  # 已达成,且后续 tier 距离更远,停止处理
            else:
                target = pos.open_price - target_pips * broker.pip
                if target >= pos.stop_loss:
                    return
            modify_stop_loss(pos, broker.normalize(target))
            return  # 每 tick 只处理最近触发的一档

6. 分批平仓 / 阶梯止盈

6.1 分批止盈 + SL 上移(保留剩余单继续跑)

class StepManager:
    """每个持仓关联一个 StepManager,记录已触发档位。"""
    def __init__(self):
        self.done_levels: set = set()

def check_partial_take_profit(pos: Position, broker: Broker,
                              step_mgr: StepManager,
                              steps: list):
    """steps = [(rr, partial_pct), ...]
       rr = 相对初始风险的回报倍数(R 倍数)
       partial_pct = 该档位平掉的持仓比例
    """
    initial_risk = pos.initial_risk  # 开仓时存下的初始 SL 距离
    if initial_risk <= 0:
        return

    if pos.side == "buy":
        profit_pips = (broker.current_bid(symbol=pos.symbol) - pos.open_price) / broker.pip
    else:
        profit_pips = (pos.open_price - broker.current_ask(symbol=pos.symbol)) / broker.pip
    current_r = (profit_pips * broker.pip) / initial_risk

    for level_idx, (rr_threshold, partial_pct) in enumerate(steps):
        if level_idx in step_mgr.done_levels:
            continue
        if current_r < rr_threshold:
            break

        close_size = pos.size * partial_pct
        close_size = max(broker.volume_min,
                         math.floor(close_size / broker.volume_step) * broker.volume_step)
        if close_size >= pos.size:
            close_position(pos)
            return
        if close_partial(pos, close_size):
            step_mgr.done_levels.add(level_idx)
            # 剩余部分 SL 上移到本档止盈价附近,实现"锁利"
            lock_sl = pos.open_price + (rr_threshold * initial_risk - broker.stop_level_points * broker.point) \
                      if pos.side == "buy" else \
                      pos.open_price - (rr_threshold * initial_risk + broker.stop_level_points * broker.point)
            modify_stop_loss(pos, broker.normalize(lock_sl))

知识点:

  • 每次成功平仓后立刻把剩余仓位的 SL 上移/下移,避免回吐。
  • 关闭后记得更新 step 状态,避免下个 tick 重复触发。

6.2 网格/金字塔对冲止损

网格策略中,每一层持仓都有独立 SL(上一层的开仓价)与 TP(按 RR 计算)。总层数达到上限时切换到"止损+止盈"保守模式。

class GridStopLoss:
    def __init__(self, symbol: str, magic: int,
                 max_layers: int = 7,
                 rr: float = 2.0,
                 lot_chain: list = None):
        self.symbol = symbol
        self.magic = magic
        self.max_layers = max_layers
        self.rr = rr
        self.lot_chain = lot_chain or [0.01]

    def process(self, positions: list, broker: Broker):
        # positions 已按开仓时间排序,layer=1 是最早一单
        if not positions:
            return
        layers = len(positions)
        last = positions[-1]

        if layers >= self.max_layers:
            # 达到上限:切换到保护模式,为第 1 单设置 SL/TP
            self._enforce_final_sl_tp(positions[0], broker)
            return

        # 计算下一层的入场价与止损
        layer_no = layers + 1
        # 对称网格:偶数层挂在第 1 单开仓价,奇数层挂在第 2 单开仓价
        if layer_no == 2:
            entry = last.stop_loss  # 用最后一层的止损做反向挂单
        elif layer_no % 2 == 0:
            entry = positions[0].open_price
        else:
            entry = positions[1].open_price if len(positions) > 1 else last.open_price

        # 每层 SL = 上一层的开仓价
        layer_sl = positions[-2].open_price if layers > 1 else last.open_price
        # TP = 开仓价 ± SL 距离 × RR
        if last.side == "buy":
            sl_dist = last.open_price - layer_sl
            tp = last.open_price + sl_dist * self.rr
        else:
            sl_dist = layer_sl - last.open_price
            tp = last.open_price - sl_dist * self.rr

        lot = self.lot_chain[layer_no - 1] if layer_no - 1 < len(self.lot_chain) else self.lot_chain[-1]
        # 发送反向挂单 (略)

    def _enforce_final_sl_tp(self, first_pos, broker):
        # 达到最大层数后,为最早的仓位重新设置 SL/TP 以锁定最终风险
        ...

知识点:

  • 网格的核心是"每层 SL = 上一层开仓价",保证单笔亏损可控。
  • 到最大层后必须停止加仓并启用保护性 SL/TP,否则回撤会失控。
  • 用 RR 决定 TP,是网格策略常见做法。

7. 时间类与信号类退出

7.1 时间止损

持仓超过指定分钟数即平仓,适用于剥头皮/日内。

def update_time_stop(pos: Position, broker: Broker,
                     max_hold_minutes: int = 30):
    hold_seconds = (now() - pos.open_time).total_seconds()
    if hold_seconds >= max_hold_minutes * 60:
        close_position(pos)

参数:max_hold_minutes = 0 表示禁用。

知识点:

  • 时间止损特别适合剥头皮,防止"死锁"持仓。
  • 也可结合"持仓 N 分钟未盈利则平仓"的变体:if hold_time > threshold and unrealized_pnl <= 0: close。

7.2 动量反转退出

def update_momentum_exit(pos: Position, broker: Broker,
                         symbol: str, threshold: float = 0.0003):
    momentum = get_indicator(symbol, "MOM", 14, shift=0)  # 或自研动量指标
    if pos.side == "buy" and momentum < -threshold:
        close_position(pos)
    elif pos.side == "sell" and momentum > threshold:
        close_position(pos)

参数:threshold 单位通常是百分比。


8. 账户级风控止损(全局视角)

8.1 账户净值追踪止损(隐藏 SL 线)

跟踪账户权益峰值,跌破 peak × (1 - trail%) 则全平所有仓位。

class EquityTrailingStop:
    def __init__(self, trail_percent: float = 5.0,
                 trail_amount: float = 0.0):
        self.peak_equity = 0.0
        self.trail_percent = trail_percent   # 百分比模式
        self.trail_amount = trail_amount     # 金额模式(美元/本币)

    def process(self, account: Account, positions: list, broker: Broker):
        if account.equity > self.peak_equity:
            self.peak_equity = account.equity

        if self.trail_percent > 0:
            stop_line = self.peak_equity * (1 - self.trail_percent / 100)
        elif self.trail_amount > 0:
            stop_line = self.peak_equity - self.trail_amount
        else:
            return

        if account.equity <= stop_line:
            for pos in positions:
                close_position(pos)

参数:trail_percent = 5% 表示允许从峰值回撤 5% 后强制平仓。

知识点:

  • 这是"止损放在账户层"而不是"放在订单层"的思路,对防止爆仓非常有效。
  • 通常与"日亏损百分比"、"最大回撤百分比"组合使用。

8.2 每日亏损百分比止损

class DailyLossStop:
    def __init__(self, max_daily_loss_percent: float = 3.0):
        self.day_start_balance = 0.0
        self.current_day = None
        self.max_daily_loss_percent = max_daily_loss_percent

    def process(self, account: Account, positions: list) -> bool:
        today = now().date()
        if today != self.current_day:
            self.current_day = today
            self.day_start_balance = account.balance

        if self.day_start_balance <= 0:
            return True
        daily_pnl = account.equity - self.day_start_balance
        max_loss = self.day_start_balance * self.max_daily_loss_percent / 100
        if daily_pnl < -max_loss:
            # 触发:平仓并今日停止交易
            for pos in positions:
                close_position(pos)
            return False  # 不允许继续交易
        return True

知识点:

  • 日初余额应记在"当天第一次 tick",而不是账户启动时。
  • 触发后不仅平仓,还应在当日剩余时间内禁开仓。

8.3 交易次数限制(日/会话/小时)

class TradeCountGuard:
    def __init__(self, max_per_day=30, max_per_session=10, max_per_hour=20):
        self.per_day = 0
        self.per_session = 0
        self.per_hour = 0
        self.max_per_day = max_per_day
        self.max_per_session = max_per_session
        self.max_per_hour = max_per_hour
        self.current_day = None
        self.current_session = None
        self.current_hour = None
        self.day_start_balance = 0.0

    def rollover(self, account: Account):
        today = now().date()
        session = current_session()      # Asia / London / NY / Overlap / None
        hour = now().hour
        if today != self.current_day:
            self.current_day = today
            self.per_day = 0
            self.day_start_balance = account.balance
        if session != self.current_session:
            self.current_session = session
            self.per_session = 0
        if hour != self.current_hour:
            self.current_hour = hour
            self.per_hour = 0

    def can_open(self) -> bool:
        if self.per_day >= self.max_per_day:
            return False
        if self.per_session >= self.max_per_session:
            return False
        if self.per_hour >= self.max_per_hour:
            return False
        return True

    def on_open(self):
        self.per_day += 1
        self.per_session += 1
        self.per_hour += 1

知识点:三层计数器独立重置,会话切换时只重置 session 计数器。


8.4 冷却时间止损

亏损后或开仓后一段时间内禁止开仓。

class CooldownGuard:
    def __init__(self, min_seconds_between_trades: int = 300,
                 loss_cooldown_minutes: int = 30):
        self.last_trade_time = None
        self.last_loss_time = None
        self.min_seconds_between_trades = min_seconds_between_trades
        self.loss_cooldown_seconds = loss_cooldown_minutes * 60

    def can_open(self) -> bool:
        now_t = now().timestamp()
        if self.last_trade_time and \
           now_t < self.last_trade_time + self.min_seconds_between_trades:
            return False
        if self.last_loss_time and \
           now_t < self.last_loss_time + self.loss_cooldown_seconds:
            return False
        return True

    def on_open(self):
        self.last_trade_time = now().timestamp()

    def on_loss_closed(self):
        self.last_loss_time = now().timestamp()

8.5 会话/时段过滤

只在指定交易时段开仓(Asia/London/NY/Overlap)。

SESSIONS = {
    "asia":   (0,  0,  8,  0),   # GMT
    "london": (7,  0, 16,  0),
    "ny":     (13, 0, 21,  0),
    "overlap":(12, 0, 16,  0),
}

def in_session(gmt_minutes: int, session_name: str) -> bool:
    sh, sm, eh, em = SESSIONS[session_name]
    s = sh * 60 + sm
    e = eh * 60 + em
    return s <= gmt_minutes < e

def current_session(now_gmt_minutes: int, enabled: set) -> str:
    if "overlap" in enabled and in_session(now_gmt_minutes, "overlap"):
        return "overlap"
    for s in ["asia", "london", "ny"]:
        if s in enabled and in_session(now_gmt_minutes, s):
            return s
    return None

知识点:

  • 使用 GMT 时间而非本地时间,避免夏令时问题。
  • "Overlap"(伦敦-纽约重叠时段)通常是波动最剧烈的时段,很多策略只在此时段交易。

8.6 点差过滤

def spread_guard(symbol: str, broker: Broker, max_spread_points: int) -> bool:
    spread = broker.current_ask(symbol) - broker.current_bid(symbol)
    spread_points = spread / broker.point
    return spread_points <= max_spread_points

9. 事件类止损

9.1 新闻止损(Event Stopper)

在重大经济事件窗口内强制平仓 + 关闭自动交易,事件结束后自动恢复。

class NewsStopper:
    def __init__(self,
                 min_before: int = 5,          # 事件前 N 分钟开始平仓
                 min_before_zero: int = 60,    # 事件前 N 分钟开始"零利润平仓"窗口
                 min_after: int = 45,          # 事件后 N 分钟内保持关闭
                 keywords: list = None,
                 currencies: list = None,
                 close_profit: float = 1.0,
                 update_interval_hours: int = 2):
        self.min_before = min_before * 60
        self.min_before_zero = min_before_zero * 60
        self.min_after = min_after * 60
        self.keywords = keywords or ["CPI", "FOMC", "Non-Farm", "NFP", "GDP", "PCE", "PMI", "Retail"]
        self.currencies = currencies or ["USD", "EUR", "GBP", "JPY", "CAD"]
        self.close_profit = close_profit
        self.update_interval = update_interval_hours * 3600
        self.last_fetch = 0
        self.allow_trade = True
        self.events = []   # list of (currency, title, time, impact)

    def fetch_calendar(self):
        # 从财经日历 API 拉取未来 24 小时的高影响事件
        self.events = external_calendar_fetch(self.currencies, self.keywords)
        self.last_fetch = now().timestamp()

    def _find_active_event(self) -> str:
        now_t = now().timestamp()
        for currency, title, event_time, impact in self.events:
            if event_time - self.min_before <= now_t <= event_time + self.min_after:
                return currency
        return None

    def _in_zero_profit_window(self) -> str:
        now_t = now().timestamp()
        for currency, title, event_time, impact in self.events:
            if event_time - self.min_before_zero <= now_t <= event_time:
                return currency
        return None

    def process(self, account: Account, positions: list, broker: Broker):
        if now().timestamp() - self.last_fetch >= self.update_interval:
            self.fetch_calendar()

        # 阶段 1:零利润提前平仓
        zero_pair = self._in_zero_profit_window()
        if zero_pair and self.allow_trade and account.equity - account.balance >= self.close_profit:
            target = [p for p in positions if p.symbol == zero_pair] if zero_pair else positions
            for pos in target:
                close_position(pos)
            self.allow_trade = False
            # 可关闭自动交易(略)

        # 阶段 2:事件窗口全平
        active = self._find_active_event()
        if active and self.allow_trade:
            for pos in positions:
                close_position(pos)
            self.allow_trade = False

        # 阶段 3:事件结束恢复
        if not active and not self.allow_trade:
            self.allow_trade = True

参数:

  • min_before / min_after:事件窗口边界。
  • min_before_zero:更宽松的"零利润平仓"窗口,允许在事件前较长时间就把浮盈仓位提前平仓。
  • keywords:按标题过滤,只关注真正高影响的事件。
  • currencies:按货币过滤,只关心与自己持仓相关的事件。
  • close_profit:零利润平仓所需的最小利润(默认 1 美元,保证交易成本后仍略盈利)。

知识点:

  • 三层时机非常关键:min_before_zero → min_before → min_after,从宽到窄逐级收紧。
  • 平仓后应立即关闭自动交易,防止新单在事件期间被打。
  • 恢复交易要在 min_after 之后,且要重新检查账户状态。

10. 主循环装配(推荐调用顺序)

把所有止损方法组合成"入场 → 持仓管理 → 账户风控"的三层调度:

def on_tick(account, broker, symbol, positions, config):
    # ============= 第 1 层:账户级风控(每 tick) =============
    # 1.1 会话/时段切换(重置计数器)
    session = current_session(...)
    if session != last_session:
        trade_guard.on_session_change()
        last_session = session

    # 1.2 每日亏损检查(触发即全平并停止交易)
    if not daily_loss_stop.process(account, positions):
        halt_trading_today = True

    # 1.3 账户净值追踪止损
    if equity_trailing_stop.process(account, positions, broker):
        halt_trading_today = True

    # 1.4 交易次数限制
    if not trade_guard.can_open():
        halt_trading_today = True

    # 1.5 冷却时间
    if not cooldown_guard.can_open():
        halt_trading_today = True

    # 1.6 新闻窗口
    if not news_stopper.process(account, positions, broker):
        halt_trading_today = True

    # ============= 第 2 层:持仓管理(每 tick) =============
    for pos in positions:
        # 2.1 时间止损(优先,简单粗暴)
        if config.scalping_mode and config.max_hold_minutes > 0:
            update_time_stop(pos, broker, config.max_hold_minutes)

        # 2.2 动量退出(剥头皮)
        if config.scalping_mode and config.use_momentum_exit:
            update_momentum_exit(pos, broker, symbol, config.momentum_threshold)

        # 2.3 移动止损(互斥:激进追踪 vs 经典追踪)
        if config.aggressive_mode and config.use_aggressive_trailing:
            update_aggressive_trailing(pos, broker,
                                       pos.initial_risk,
                                       config.aggr_activate_r,
                                       config.aggr_trail_pips,
                                       config.aggr_step_pips)
        elif config.use_trailing:
            update_classic_trailing(pos, broker,
                                    config.trail_start_pips,
                                    config.trail_stop_pips,
                                    config.trail_step_pips)

        # 2.4 ATR 追踪(可与经典追踪二选一)
        if config.use_atr_trailing:
            update_atr_trailing(pos, broker,
                                config.atr_period, config.atr_mult)

        # 2.5 吊灯 / PSAR 追踪(互斥)
        if config.use_chandelier:
            update_chandelier(pos, broker, symbol, config.timeframe,
                              config.chandelier_period, config.atr_period,
                              config.chandelier_mult)

        # 2.6 保本(独立,可与追踪并存)
        if config.use_break_even:
            update_breakeven_offset(pos, broker,
                                    config.be_trigger_pips,
                                    config.be_offset_pips)

        # 2.7 分批止盈
        if config.use_partial_tp:
            step_mgr = get_step_mgr(pos.ticket)
            check_partial_take_profit(pos, broker, step_mgr, config.steps)

        # 2.8 均线跟随(趋势策略专用)
        if config.use_ma_trailing:
            update_ma_trailing(pos, broker, symbol, config.ma_period)

    # ============= 第 3 层:入场判断 =============
    if not halt_trading_today and signal:
        # 3.1 波动率过滤
        if not volatility_guard(symbol, config.atr_period,
                                config.min_atr, config.max_atr):
            return

        # 3.2 点差过滤
        if not spread_guard(symbol, broker, config.max_spread_points):
            return

        # 3.3 初始止损(三选一)
        if config.use_atr_stop:
            initial_sl = calc_initial_sl_atr(open_price, symbol, broker,
                                             config.atr_period, config.atr_mult)
        elif config.use_swing_stop:
            initial_sl = calc_initial_sl_swing(open_price, symbol, broker,
                                               config.timeframe, config.lookback_bars)
        else:
            initial_sl = calc_initial_sl_fixed(open_price, broker, config.fixed_sl_pips)

        # 3.4 强制不低于 stop_level
        if open_price - initial_sl < broker.stop_level_points * broker.point:
            initial_sl = open_price - broker.stop_level_points * broker.point

        # 3.5 风险百分比算手数
        lots = calc_lot_by_risk_percent(account, broker, open_price, initial_sl,
                                        config.risk_percent)

        # 3.6 发送订单(略)

11. 移植注意事项

11.1 单向收紧规则(最重要)

规则:BUY 单只上移 SL,SELL 单只下移 SL。任何时候都不能把 SL 移动得比当前更"不利"。

# BUY: 只有新 SL > 旧 SL 才允许
if pos.side == "buy" and new_sl <= pos.stop_loss:
    return  # 不允许
# SELL: 只有新 SL < 旧 SL 才允许
if pos.side == "sell" and new_sl >= pos.stop_loss:
    return

违反此规则会导致:

  • 服务器返回 10016 / ERR_TRADE_INVALID_STOP_PRICE。
  • 更糟糕的是,如果实盘里出现 SL 被"回退",一次反向波动就可能爆仓。

11.2 最小止损距离(StopLevel)

规则:SL 与当前价之间的距离必须 ≥ 经纪商的最小止损距离(SYMBOL_TRADE_STOPS_LEVEL)。

def ensure_min_stop_distance(new_sl, current_price, broker, buffer=1.0):
    min_dist = broker.stop_level_points * broker.point * buffer
    if abs(new_sl - current_price) < min_dist:
        # 二选一:要么拉远到 min_dist,要么放弃本次修改
        if new_sl < current_price:
            new_sl = current_price - min_dist
        else:
            new_sl = current_price + min_dist
    return new_sl

建议:加一个 buffer=1.0~2.0 的安全系数,避免因市场跳动越过 min_dist。

11.3 冻结价距离(FreezeLevel)

部分经纪商不允许在接近冻结价距离内修改 SL/TP。

if abs(new_sl - current_price) < broker.freeze_level_points * broker.point:
    # 跳过本次修改,等待价格走远
    return

11.4 精度对齐

  • SL/TP 必须对齐到报价精度(SYMBOL_DIGITS)。
  • 修改值必须对齐到 tick_size,否则会被拒绝。
def normalize_sl(new_sl, broker):
    if broker.tick_size > 0:
        new_sl = round(new_sl / broker.tick_size) * broker.tick_size
    new_sl = round(new_sl, broker.digits)
    return new_sl

11.5 Point vs Pip

MQL5 中 _Point 是"最小价格单位",但用户习惯用 pip:

  • 4 位小数货币对(如 EURUSD=1.1000):1 pip = 10 point。
  • 2 位小数货币对(如 USDMXN=17.89):1 pip = 1 point。
  • JPY 货币对(如 EURJPY=140.500):1 pip = 10 point。

移植建议:所有参数用 pip 表达,内部通过 broker.pip 统一换算,屏蔽 point/pip 差异。

11.6 修改频率与服务器请求限制

频繁修改 SL 会导致:

  • 服务器拒绝(ERR_TRADE_TOO_MANY_REQUESTS)。
  • 手续费累积(部分经纪商按修改次数收费)。
  • 回测与实盘偏差(回测按 tick 处理,实盘可能被限流)。

缓解:

  • 用 step_pips 步长控制(新 SL 与旧 SL 差距不足一个 step 就不修改)。
  • 时间间隔控制(如每 5 秒最多修改一次)。
  • 修改前比较新旧 SL,一致时跳过。
if abs(new_sl - pos.stop_loss) <= broker.point:
    return  # 无变化,跳过

11.7 回测 vs 实盘差异

差异点 回测行为 实盘行为 影响
Tick 精度 通常 1 秒 M1 tick 可能几秒才一 tick 止损触发延迟
滑点 可配置为 0 通常 1~5 pip 移动止损精度下降
StopLevel 通常不强制 严格强制 回测止损位置可能实盘无法下单
冻结价 通常不建模 严格 修改 SL 可能失败
服务器限流 无 有 高频修改被拒
数据源 经纪商历史 实时报价 指标值可能微差

建议:回测时用 tick data + 模拟 StopLevel/FreezeLevel,最贴近实盘。

11.8 初始风险存储

关键:开仓时记录 initial_risk = |open_price - initial_sl| 到持仓结构体(或注释中)。

原因:

  • 保本止损会立刻把 SL 移到 open_price,导致后续再算"当前 R"变成 0。
  • R 倍数追踪、分批止盈等依赖"初始 R"的计算必须用它。
# 开仓成功后立刻记录
pos.initial_risk = abs(pos.open_price - pos.stop_loss)
# 或用 magic + 注释的方式记录到订单上,便于重启 EA 后恢复

11.9 从后往前遍历持仓

修改或关闭持仓时,务必从最后一个往前遍历,避免索引错乱。

for i in range(PositionsTotal() - 1, -1, -1):
    ...

11.10 事件循环与幂等性

每个止损函数应当是幂等的:多次调用不会产生副作用(除了实际的 SL 修改)。

def update_breakeven_simple(pos, broker, trigger_pips):
    # 已保本就直接返回,不重复调用 modify
    if pos.side == "buy" and pos.stop_loss >= pos.open_price:
        return

11.11 魔术号(Magic Number)隔离

多 EA 共存时,务必用 magic 区分订单,避免误改他 EA 的 SL。

if pos.magic_number != my_magic:
    continue

11.12 品种精度差异

不同品种的 SL 精度、tick size 差异巨大:

  • EURUSD:digits=5, tick_size=0.00001
  • USDJPY:digits=3, tick_size=0.001
  • XAUUSD(黄金):digits=2, tick_size=0.01

统一使用 broker.normalize() 与 broker.tick_size 处理,避免手工计算出错。

11.13 移动止损与分批平仓的顺序

推荐顺序(从后往前):

  1. 时间/动量退出(简单直接,先执行)
  2. 移动止损(激进/经典/ATR/吊灯,二选一)
  3. 保本(独立,与移动止损可共存)
  4. 分批止盈(在 SL 已锁定后再执行分批)

理由:先移动 SL 保证剩余仓位有基础保护,再触发分批止盈时,即使后续波动也能保住已锁定部分。


12. 常见策略组合模板

12.1 趋势跟踪(黄金/股票/加密)

初始止损 = ATR × 2
移动止损 = 吊灯(22, 3.0)
保本 = 触发 1.5R 后 SL = open + 0.5 pip
分批止盈 = [2R: 30%, 3R: 30%]
账户级 = 净值追踪 10% + 日亏损 3%

12.2 日内剥头皮

初始止损 = ATR × 0.8 或 M1 结构位取大
移动止损 = 激进追踪(激活 0.5R,距离 3 pip,步长 1 pip)
保本 = 触发 5 pip 后 SL = open
分批止盈 = [1R: 50%, 2R: 全部]
时间止损 = 30 分钟
动量退出 = 阈值 0.03%
账户级 = 日亏损 4% + 每会话最多 10 笔

12.3 波段(周/日线)

初始止损 = 结构位(目一云图或 N 周高低点)
移动止损 = 长期均线(60 MA)或吊灯(55, 3.0)
保本 = 触发 20 pip 后 SL = open
分批止盈 = [1.5R: 25%, 3R: 25%, 5R: 50%]
账户级 = 净值追踪 15% + 日亏损 2%

12.4 网格/对冲

初始止损 = 上一层开仓价
移动止损 = 无(网格策略本身承担波动)
分批止盈 = 按 RR=2 计算每层 TP
总层数 = 7(达到上限强制止损+止盈)
账户级 = 净值追踪 20% + 日亏损 5%

13. 关键参数速查表

参数 含义 典型值
atr_period ATR 周期 14
atr_mult ATR 倍数 0.8 / 1.5 / 3
trail_start_pips 追踪启动距离 20~50
trail_stop_pips 追踪距离 10~30
trail_step_pips 追踪步长 2~10
be_trigger_pips 保本触发 3~10
be_offset_pips 保本偏移 0~5
risk_percent 单笔风险 % 0.5~3
max_daily_loss_percent 日亏损 % 2~5
equity_trail_percent 净值追踪 % 5~15
max_trades_per_day 日交易次数 5~30
max_spread_points 最大点差 3~10
min_atr / max_atr ATR 过滤 视品种
max_hold_minutes 最大持仓分钟 15~120
chandelier_period 吊灯周期 22
chandelier_mult 吊灯倍数 2~3

14. 常见陷阱与调试清单

  1. SL 移动方向错误:检查单向收紧规则(11.1)。
  2. 止损被经纪商拒绝:检查 StopLevel(11.2)与 FreezeLevel(11.3)。
  3. 手数计算溢出:loss_per_lot 为 0 时会导致 lots = inf,必须先做边界判断。
  4. 精度对齐错误:所有修改前的价格必须 normalize + tick_size 对齐。
  5. 回测过于乐观:回测必须加 StopLevel、FreezeLevel、滑点、tick 精度。
  6. 移动止损被覆盖:多个移动止损同时生效会互相覆盖,需互斥或明确优先级。
  7. 保本后忘记原始风险:分批止盈依赖初始 R,必须开仓时存储。
  8. 新闻窗口恢复错误:事件结束后应显式重置 allow_trade = True。
  9. 会话/日切换丢失计数器:日初/会话初要主动清零计数器。
  10. 持仓数组索引错乱:从后往前遍历。

附:素材来源文件(知识库中)

本文件的方法总结源自以下 MQL5 知识库文件:

文件 主要贡献
Risk.mqh ATR 止损、风险百分比仓位、会话/点差/次数/日亏损/冷却/波动率过滤
TrailingMoving.mqh 均线跟随止损(穿越 MA 平仓)
UsingTrailing.mqh 移动止损开关与类型枚举
Break_Even.mq5 简单保本止损
Trade.mqh 通用交易与修改 API 封装
TradeManager.mqh 综合管理器(移动/保本/分批组合)
Account Protector.mqh 账户净值追踪、日亏损百分比、按风险算手数
E_EntriesManagement.mqh 一目均衡云图结构位止损、订单发送重试
Hedge.mqh 网格/金字塔对冲止损
MQLTA MT5 ATR Trailing Stop ATR 移动止损、tick_size 对齐、StopLevel 检查
DSUPERNATURAL1 UNIVERSAL TRAILING STOP 经典追踪(Start/Stop/Step)、魔术号解析
News Stopper MT5 新闻事件止损(三层时机窗口)
综合 EA(激进模式) 吊灯/激进追踪/分批/动量退出/时间止损的完整组合

文档版本:v1.0 · 2026-09-19