海龟汤和海龟汤升级版的改进·综合运用
🐢

海龟汤和海龟汤升级版的改进·综合运用

(3/3)·把康纳斯的反向假突破逻辑落成 MQL5 结构式 EA,并用近年 tick 历史检验它的真实边界

偏理论 第 3/3 篇
不少人把海龟汤当成“假突破必回弹”的收割机,忘记原作者本就基于 15–20 年样本调参。直接拿旧书参数跑近五年贵金属,往往被毛刺止损洗出场。

新开仓位的初始止损怎么摆

Turtle Soup 类策略在仓位刚建立、SL 还为零时,需要先用近期极值算一个初始止损,再校验它离现价是否够远。够远才下,不够远就退到 broker 允许的最小止损距离上,避免挂单直接被拒。 下面这个函数就是「距离校验」的核心:取现价与候选水平的绝对差,必须严格大于止损底线与当前点差中的较大者。gd_Stop_Level 一般是经纪商要求的止损最小点数折算价,go_Tick.ask - go_Tick.bid 则是实时点差,两者取大才保险。 [CODE]bool fb_Is_Acceptable_Distance(double d_Level_To_Check, double d_Current_Price) { return( fabs(d_Current_Price - d_Level_To_Check) > fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid) ); }[/CODE] 买入仓位的初始止损取近期 Low 数组的最小值再减 gd_Exit_Offset;若 Turtle_Soup_Type 是 PLIS_1 变体,则多取一根 K 线(数组长度 1+1)。算完立刻用上面的函数测距,不达标就改成 bid - max(止损底线, 点差)。 卖出仓位对称处理:用 CopyHigh 取近期 High 的最大值加偏移,距离校验对着 ask 做,不达标则 ask + max(止损底线, 点差)。最后 CTrade.PositionModify 只改 SL、TP 原样传回。外汇与贵金属杠杆高,最小止损距离随品种和经纪商浮动,实盘前务必在 MT5 策略测试器里把 gd_Stop_Level 打印出来核对。

MQL5 / C++
class="type">bool fb_Is_Acceptable_Distance(class="type">class="kw">double d_Level_To_Check, class="type">class="kw">double d_Current_Price) {
  class="kw">return(
    fabs(d_Current_Price - d_Level_To_Check)
    >
    fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid)
  );
}

if(PositionSelect(_Symbol)) { class=class="str">"cmt">// 有建立的仓位
  if(PositionGetDouble(POSITION_SL) == class="num">0.) { class=class="str">"cmt">// 新的仓位
    class="type">class="kw">double
      d_SL = WRONG_VALUE, class=class="str">"cmt">// 止损水平
      da_Price_Array[] class=class="str">"cmt">// 辅助数组
    ;
    
    class=class="str">"cmt">// 计算止损水平:
    if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { class=class="str">"cmt">// 对于买入仓位
      if(WRONG_VALUE == CopyLow(_Symbol, PERIOD_CURRENT, class="num">0, class="num">1 + (Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1), da_Price_Array)) {
        class=class="str">"cmt">// 处理 CopyLow 函数的错误
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: 错误 #%u", __FUNCTION__, _LastError);
        class="kw">return;
      }
      d_SL = da_Price_Array[ArrayMinimum(da_Price_Array)] - gd_Exit_Offset;
      
      class=class="str">"cmt">// 距离当前价格是否已经足够?
      if(!fb_Is_Acceptable_Distance(d_SL, go_Tick.bid)) {
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("计算所得的止损水平 %s 由允许的最小值 %s 代替", DoubleToString(d_SL, _Digits), DoubleToString(go_Tick.bid + fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid), _Digits));
        d_SL = go_Tick.bid - fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid);
      }
      
    } else { class=class="str">"cmt">// For a class="type">short position
      if(WRONG_VALUE == CopyHigh(_Symbol, PERIOD_CURRENT, class="num">0, class="num">1 + (Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1), da_Price_Array)) {
        class=class="str">"cmt">// 处理 CopyHigh 函数的错误
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 错误 #%u", __FUNCTION__, _LastError);
        class="kw">return;
      }
      d_SL = da_Price_Array[ArrayMaximum(da_Price_Array)] + gd_Exit_Offset;
      
      class=class="str">"cmt">// 距离当前价格是否已经足够?
      if(!fb_Is_Acceptable_Distance(d_SL, go_Tick.ask)) {
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("计算所得的止损水平 %s 由允许的最小值 %s 代替", DoubleToString(d_SL, _Digits), DoubleToString(go_Tick.ask - fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid), _Digits));
        d_SL = go_Tick.ask + fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid);
      }
    }
    
    CTrade Trade;
    Trade.LogLevel(LOG_LEVEL_ERRORS);
    class=class="str">"cmt">// 设置止损
    Trade.PositionModify(_Symbol, d_SL, PositionGetDouble(POSITION_TP));
    class="kw">return;
  }
  
  class=class="str">"cmt">// 跟踪止损

「隔夜挂单清理与通道刷新逻辑」

EA 在每根新柱(按 PeriodSeconds() 折算的日期边界)生成时,先比对静态变量 si_Last_Tick_Bar_Num 与 floor(go_Tick.time / PeriodSeconds()),若前者更小说明跨入了新交易日,随即把 si_Last_Tick_Bar_Num 更新为当前值。这一步是日更类策略的节拍器,漏掉就会在 MT5 上重复处理同一根柱。 跨日之后先查当前品种是否残留 STOP 类挂单。fi_Get_Pending_Type 会遍历 OrdersTotal() 返回的订单数,用 OrderGetString(ORDER_SYMBOL) 与 _Symbol 做不区分大小写比对,只认 BUY_LIMIT / BUY_STOP / SELL_LIMIT / SELL_STOP 四种挂单,命中即返回类型并带出订单 ticket。若类型是 SELL_STOP 或 BUY_STOP,就进入删除分支。 gi_Try_To_Trade 控制删除重试次数,每次失败 Sleep(gi_Connect_Wait) 再试,成功则把 i_Try 置为 -10 跳出。若最终 i_Try 仍等于 WRONG_VALUE,说明删单失败,直接 return 等下一根柱。外汇与贵金属隔夜跳空频繁,这种旧 STOP 单不清理,次日可能以不利价格成交,属典型高风险点。 删单完成后调用 go_Channel.f_Set 按 Turtle_Soup_Period_Length 重算通道,第二参数随 Turtle_Soup_Type 是否为 TS_TURTLE_SOUP_PLIS_1 在 1 与 2 间切换。随后 fe_Get_Entry_Signal 取信号状态,整段构成「日界→清场→重算→取信号」的闭环。

MQL5 / C++
  fb_Trailing_Stop(gd_Trail_Trigger, gd_Trail_Step, gd_Trail_Distance);
  class="kw">return;
}
class="type">int
  i_Order_Ticket = WRONG_VALUE, class=class="str">"cmt">// 挂单的订单编号
  i_Try = gi_Try_To_Trade, class=class="str">"cmt">// 进行操作的尝试次数
  i_Pending_Type = -class="num">10 class=class="str">"cmt">// 已存在挂单的类型
;
class="kw">static class="type">int si_Last_Tick_Bar_Num = class="num">0; class=class="str">"cmt">// 前一个订单时刻柱的编号 (class="num">0 = MQL中的计算起点)
class=class="str">"cmt">// 处理与新的一天(柱)开始的事件:
if(si_Last_Tick_Bar_Num < class="type">int(floor(go_Tick.time / PeriodSeconds()))) {
  class=class="str">"cmt">// 向新的一天打个招呼 :)
  si_Last_Tick_Bar_Num = class="type">int(floor(go_Tick.time / PeriodSeconds()));
  
  class=class="str">"cmt">// 是否有过时的挂单?
  i_Pending_Type = fi_Get_Pending_Type(i_Order_Ticket);
  if(i_Pending_Type == ORDER_TYPE_SELL_STOP || i_Pending_Type == ORDER_TYPE_BUY_STOP) {
    class=class="str">"cmt">// 删除旧的订单:
    if(Log_Level > LOG_LEVEL_ERR) Print("删除昨天的挂单");
    
    CTrade o_Trade;
    o_Trade.LogLevel(LOG_LEVEL_ERRORS);
    class="kw">while(i_Try-- > class="num">0) { class=class="str">"cmt">// 尝试删除
      if(o_Trade.OrderDelete(i_Order_Ticket)) { class=class="str">"cmt">// 尝试成功
        i_Try = -class="num">10; class=class="str">"cmt">// 成功操作的标志
        class="kw">break;
      }
      class=class="str">"cmt">// 尝试失败
      Sleep(gi_Connect_Wait); class=class="str">"cmt">// 在下一次尝试前暂停
    }
    
    if(i_Try == WRONG_VALUE) { class=class="str">"cmt">// 删除挂单失败
      if(Log_Level > LOG_LEVEL_NONE) Print("删除挂单错误");
      class="kw">return; class=class="str">"cmt">// 等待下一个订单时刻
    }
  }
  
  class=class="str">"cmt">// 更新通道参数:
  go_Channel.f_Set(Turtle_Soup_Period_Length, class="num">1 + (Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1));
}
class="type">int fi_Get_Pending_Type( class=class="str">"cmt">// 侦测当前交易品种是否有挂单
  class="type">int& i_Order_Ticket class=class="str">"cmt">// 所选挂单单号的引用
) {
  class="type">int
    i_Order = OrdersTotal(), class=class="str">"cmt">// 订单总数
    i_Order_Type = WRONG_VALUE class=class="str">"cmt">// 订单类型的变量
  ;
  i_Order_Ticket = WRONG_VALUE; class=class="str">"cmt">// 默认返回的订单编号
  
  if(i_Order < class="num">1) class="kw">return(i_Order_Ticket); class=class="str">"cmt">// 没有订单
  
  class="kw">while(i_Order-- > class="num">0) { class=class="str">"cmt">// 检查已有的订单
    i_Order_Ticket = class="type">int(OrderGetTicket(i_Order)); class=class="str">"cmt">// 读取订单编号
   if(i_Order_Ticket > class="num">0)
      if(StringCompare(OrderGetString(ORDER_SYMBOL), _Symbol, class="kw">false) == class="num">0) {
        i_Order_Type = class="type">int(OrderGetInteger(ORDER_TYPE));
        class=class="str">"cmt">// 我们只需要挂单:
        if(i_Order_Type == ORDER_TYPE_BUY_LIMIT || i_Order_Type == ORDER_TYPE_BUY_STOP || i_Order_Type == ORDER_TYPE_SELL_LIMIT || i_Order_Type == ORDER_TYPE_SELL_STOP)
          class="kw">break; class=class="str">"cmt">// 找到了一个挂单
      }
    i_Order_Ticket = WRONG_VALUE; class=class="str">"cmt">// 还没有找到
  }
  
  class="kw">return(i_Order_Type);
}
class=class="str">"cmt">// 取得信号状态:
ENUM_ENTRY_SIGNAL e_Signal = fe_Get_Entry_Signal(Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1, Turtle_Soup_Extremum_Offset);

◍ 挂单方向冲突时的删除与重设逻辑

EA 在每次订单时刻先判断信号强度,若 e_Signal 大于 1 直接 return,视作无信号不动作。随后通过 fi_Get_Pending_Type 补查挂单类型,若已有与信号同向的 BUY_STOP 或 SELL_STOP 则跳过,避免重复挂单。 当检测到挂单方向与信号相反(例如信号 ENTRY_SELL 却存在 BUY_STOP),代码进入删除分支:用 gi_Try_To_Trade 次循环调用 OrderDelete,每次失败 Sleep(gi_Connect_Wait) 再试;若 i_Try 最终等于 WRONG_VALUE 说明删除失败,打印日志并退出本次处理。 删除成功后计算入场位:买入用通道下轨 go_Channel.d_Low 加 gd_Entry_Offset,卖出用通道上轨 go_Channel.d_High 减 gd_Entry_Offset。fb_Is_Acceptable_Distance 会拿该价位与 go_Tick.ask/bid 比对,距离不足就 PrintFormat 输出 Bid/Ask/StopLevel 并 return,等价格走动。 手数经 fd_Normalize_Lot 按服务器要求规范化,最后对 ENTRY_BUY 走 o_Trade.BuyStop 分支尝试挂单,同样套用 i_Try 重试机制。外汇与贵金属杠杆高,挂单被拒或滑点可能引发非预期持仓,实盘前建议在 MT5 策略测试器用历史数据验证 StopLevel 与通道偏移的兼容性。

MQL5 / C++
if(e_Signal > class="num">1) class="kw">return; class=class="str">"cmt">// No signal
class=class="str">"cmt">// 寻找挂单的类型和它的订单编号:
if(i_Pending_Type == -class="num">10)
  i_Pending_Type = fi_Get_Pending_Type(i_Order_Ticket);
class=class="str">"cmt">// 我们是否需要一个新的挂单?
if(
  (e_Signal == ENTRY_SELL && i_Pending_Type == ORDER_TYPE_SELL_STOP)
  ||
  (e_Signal == ENTRY_BUY && i_Pending_Type == ORDER_TYPE_BUY_STOP)
) class="kw">return; class=class="str">"cmt">// 在信号方向上有一个挂单
class=class="str">"cmt">// 我们是否需要删除挂单?
if(
  (e_Signal == ENTRY_SELL && i_Pending_Type == ORDER_TYPE_BUY_STOP)
  ||
  (e_Signal == ENTRY_BUY && i_Pending_Type == ORDER_TYPE_SELL_STOP)
) { class=class="str">"cmt">// 挂单的方向与信号方向不匹配
  if(Log_Level > LOG_LEVEL_ERR) Print("挂单的方向和信号的方向不符合");

  i_Try = gi_Try_To_Trade;
  class="kw">while(i_Try-- > class="num">0) { class=class="str">"cmt">// 尝试删除
    if(o_Trade.OrderDelete(i_Order_Ticket)) { class=class="str">"cmt">// 尝试成功
      i_Try = -class="num">10; class=class="str">"cmt">// 成功操作的标志
      class="kw">break;
    }
    class=class="str">"cmt">// 尝试失败
    Sleep(gi_Connect_Wait); class=class="str">"cmt">// 在下一次尝试前暂停
  }

  if(i_Try == WRONG_VALUE) { class=class="str">"cmt">// 删除挂单失败
    if(Log_Level > LOG_LEVEL_NONE) Print("删除挂单错误");
    class="kw">return; class=class="str">"cmt">// 等待下一个订单时刻
  }
}
class="type">class="kw">double d_Entry_Level = WRONG_VALUE; class=class="str">"cmt">// 设置挂单的水平
if(e_Signal == ENTRY_BUY) { class=class="str">"cmt">// 对于买入挂单
  class=class="str">"cmt">// 检查是否可以设置订单:
  d_Entry_Level = go_Channel.d_Low + gd_Entry_Offset; class=class="str">"cmt">// 订单设置水平
  if(!fb_Is_Acceptable_Distance(d_Entry_Level, go_Tick.ask)) {
    class=class="str">"cmt">// 与当前价格的距离不够 
    if(Log_Level > LOG_LEVEL_ERR)
      PrintFormat("无法在 %s 水平设置止损买入挂单. Bid: %s Ask: %s StopLevel: %s",
        DoubleToString(d_Entry_Level, _Digits),
        DoubleToString(go_Tick.bid, _Digits),
        DoubleToString(go_Tick.ask, _Digits),
        DoubleToString(gd_Stop_Level, _Digits)
      );

    class="kw">return; class=class="str">"cmt">// 等待当前价格改变
  }
} else {
  class=class="str">"cmt">// 检查是否可以设置订单:
  d_Entry_Level = go_Channel.d_High - gd_Entry_Offset; class=class="str">"cmt">// 订单设置水平
  if(!fb_Is_Acceptable_Distance(d_Entry_Level, go_Tick.bid)) {
    class=class="str">"cmt">// 与当前价格的距离不够 
    if(Log_Level > LOG_LEVEL_ERR)
      PrintFormat("无法在 %s 水平设置止损卖出挂单,Bid: %s Ask: %s StopLevel: %s",
        DoubleToString(d_Entry_Level, _Digits),
        DoubleToString(go_Tick.bid, _Digits),
        DoubleToString(go_Tick.ask, _Digits),
        DoubleToString(gd_Stop_Level, _Digits)
      );

    class="kw">return; class=class="str">"cmt">// 等待当前价格改变
  }
}
class=class="str">"cmt">// 符合服务器要求的手数:
class="type">class="kw">double d_Volume = fd_Normalize_Lot(Trade_Volume);
class=class="str">"cmt">// 设置挂单:
i_Try = gi_Try_To_Trade;
if(e_Signal == ENTRY_BUY) {
  class="kw">while(i_Try-- > class="num">0) { class=class="str">"cmt">// 尝试设置止损买入挂单
    if(o_Trade.BuyStop(
      d_Volume,
      d_Entry_Level,
      _Symbol

挂单失败重试与状态标记

这段逻辑处理的是买入止损与卖出止损两类挂单的提交。若方向变量走 else 分支,就循环调用 SellStop 尝试在 d_Entry_Level 挂卖单,每次失败后用 Sleep(gi_Connect_Wait) 暂停再试,最多重试 i_Try 次。 成功时 Alert 弹窗提示,并把 i_Try 写成 -10 作为脱离循环的成功标志,随后 break 退出。这个 -10 不是重试计数,只是脱离 while 的硬标记,读代码时容易误判。 循环结束若 i_Try 仍等于 WRONG_VALUE,说明所有尝试均失败,按 Log_Level 设置输出“设置挂单错误”。外汇与贵金属挂单受点差和报价冻结影响,这类重试机制在实盘 MT5 上能明显降低因瞬时连接抖动导致的丢单概率。

MQL5 / C++
)) { class=class="str">"cmt">// 尝试成功
      Alert("已经设置了买入挂单!");
      i_Try = -class="num">10; class=class="str">"cmt">// 成功操作的标志
      class="kw">break;
   }
   class=class="str">"cmt">// 失败
   Sleep(gi_Connect_Wait); class=class="str">"cmt">// 在下一次尝试前暂停
   }
} else {
   class="kw">while(i_Try-- > class="num">0) { class=class="str">"cmt">// 尝试设置止损卖出挂单
   if(o_Trade.SellStop(
      d_Volume,
      d_Entry_Level,
      _Symbol
   )) { class=class="str">"cmt">// 尝试成功
      Alert("已经设置了卖出挂单!");
      i_Try = -class="num">10; class=class="str">"cmt">// 成功操作的标志
      class="kw">break;
   }
   class=class="str">"cmt">// 失败
   Sleep(gi_Connect_Wait); class=class="str">"cmt">// 在下一次尝试前暂停
   }
}
if(i_Try == WRONG_VALUE) class=class="str">"cmt">// 设置挂单失败
   if(Log_Level > LOG_LEVEL_NONE) Print("设置挂单错误");

「用近五年数据重跑海龟汤」

康纳斯和瑞斯克当年用超过20年图表演示策略,本测试改用更近年份数据验证效能,参数与每日时段均按作者原指定值。20年前主流是4位数报价,而 MetaQuotes 模拟服务器跑的是5位数,所以原文里的1点和10点要换算成10点和100点。原策略没给跟踪止损参数,我按每日时段挑了组最顺手的补上。 近5年 USDJPY 上,海龟汤原版与升级版同参数同历史对照测试已出图;黄金近5年、原油近4年也都分别跑了原版和升级版。完整数值在附件,不在这里列。 两位作者明确警告过:别纯机械跟书里的信号。他们强调得看价格怎么逼近通道边界、以及触边界时的测试动作,只是没写细。外汇和贵金属杠杆高、滑点跳空频繁,回测好看不等于实盘能复制。 想自己动手的话,开 MT5 用每日周期加载 USDJPY,把止损参数从原书点数乘10,先跑一遍原版再调升级版参数,比对最近5年权益曲线。

◍ 最后一句大实话

海龟汤与海龟汤升级版这两套策略已经按书里规则写进EA和信号库,但作者顺带提的交易间隙、每日仅一次进场或仅一次盈利进场、挂单保留过日这些细节,代码层并没全包进去。 想让回测更贴近实盘,得自己补这几条限制:比如给OrderSend加时间窗判断,或用一个bool变量锁死当日已盈利仓位。MT5里打开附带的MQL5.zip改两行,跑出来的曲线和原文报告就会有肉眼可见的偏差。 外汇和贵金属杠杆高、滑点凶,这类短线策略在历史数据上盈亏比飘忽,真上线前请用策略测试器多品种验证,别直接丢实盘。

把回测巡检交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多周期假突破密度与通道失效提示,你只需判断当下处于哪种市场周期。

常见问题

前者只抓 20 日通道内侧的临时假突破反弹,升级版在入场过滤与止损逻辑上做了放宽与再确认,倾向减少震荡市中的连续毛刺损。
结构拥有近似类的继承与数据聚合能力,但语法更接近过程式,适合稍高阶初学者平滑迁移到面向对象,不强制先背类规则。
它提供较新 tick 环境,但点差与滑点在极端行情仍可能偏离经纪商,结论只能作为概率参考而非实盘保证。
可以,小布盯盘的品种页内置了通道边界与假突破统计,你不必自己每根 K 线手算 20 日范围,把重复劳动交给小布,你专注决策。
可能需重新定义通道周期或加入波动率门槛,外汇贵金属属高风险,任何参数失效都应先缩仓观察而非硬扛。