海龟汤和海龟汤升级版的改进(基础篇)
📘

海龟汤和海龟汤升级版的改进(基础篇)

第 1/3 篇

◍ 海龟汤与升级版的改造起点

这套思路脱胎于经典海龟汤与海龟汤升级版,核心是把原系统的通道识别逻辑做参数化重构,让策略能直接跑在 MT5 上验证。原文作者 Alexander Puzanov 在 2016 年 12 月 15 日发布该示例,截至统计节点获得 20 114 次查看与 16 条讨论,说明这类通道型价格行为玩法在外汇与贵金属交易者里一直有刚需。 改造的第一步是重新定义通道参数:不再写死 N 日高低点,而是把通道周期、突破缓冲、回调容差全部外置为输入变量。这样你可以在 EURUSD 或 XAUUSD 的 M15 上,分别试不同的通道长度,观察假突破频率的变化。 外汇与贵金属杠杆高、滑点跳空频繁,通道参数过紧容易在重大数据前被扫损,调参时务必用历史回测先压一遍极端行情。

康纳斯策略的MQL5落地前提

劳伦斯·康纳斯与琳达·瑞斯克合计有34年实盘与机构经验,他们认为单一策略亦可支撑稳定交易,但原书仍给出约20种策略,按市场周期分四组。原书策略基于15至20年市况样本开发,不能直接当作当代行情的圣杯。 本文只取书中第一组里的首个策略,用MQL5实现,并丢进MT5策略测试器,以MetaQuotes模拟服务器近年的价格历史跑效能。读者可自行开MT5照此复现,外汇与贵金属品种波动剧烈,回测顺滑不等于实盘低风险。 代码层面假定你是略高阶的MQL5初学者:不解释标准库函数、不掰扯变量类型选型,这些应在写EA前自补。也不照顾老手——他们自有成熟开发库。 为帮过程式背景的人过渡到面向对象,这里不用class,只用struct。结构能把异类数据和操作函数捆在一起,支持继承,却不必先背类语法,改几处写法就能上手。

「海龟汤与升级版:靠假突破反吃通道」

海龟汤属于‘用价格测试区间边界或支撑阻力’这一类策略,核心假设是:价格不会不经一次反弹就直接突破 20 天区间,任务是从临时假突破里获利。仓位方向永远朝通道内侧,本质是一种反弹策略,和经典海龟都盯 20 天范围但反向操作。 原版买入进场可拆为几条硬规则:距离前一个 20 天低点至少过去 3 天;等价格跌破 20 天低点;在向下突破那根 K 线低点上方 5~10 点挂 buy stop;触发后止损放当日最低价下方 1 点;有浮盈后接跟踪止损;若头一两天被止损,可在原水平重进。卖出对称,看 20 天高点。 原文没说挂单留多久。实操逻辑:价格测试边界时会出新极值,突破后第二天‘距前低已过 3 天’这个条件通常就不成立了,那天无信号,前一天挂单应撤掉。MT5 代码库有现成指标能在历史 K 上画通道边界,手动交易直接加载看边界就行。 升级版只改两点:不突破就挂单,而是等确认——当天 K 线收在 20 天区间外(收在测算的通道边界外也可);初始止损用两根 K 的极值(最高或最低)定位。外汇和贵金属波动大、假突破频繁,这套方法可能提高胜率但仍有连续止损风险,开 MT5 加载指标回测 20 天通道就知真假。

◍ 通道参数怎么塞进结构里

虚拟通道由四个基础变量锁定:范围最高价、最低价,以及它们各自对应的时间边界。再补上最高价、最低价距离当前柱的偏移天数(柱数),这六个量就能完整描述一个通道状态,适合打包成结构体供策略调用。 f_Set 函数负责按指定起点(i_Newest_Bar_Shift)和历史深度(i_Bars_Limit)刷新通道。原始版本仅 13 行,直接用 CopyHigh、CopyLow、CopyTime 拉序列,但 MT5 时间序列首次访问时可能没准备好,返回条数少于请求值是常态,不处理就会读到脏数据。 加错误分支后,发现 WRONG_VALUE 或复制条数不足就打印并 return,让终端先补齐数据,下次 tick 再试。为防止半截数据被别的函數拿去下单,结构里要挂 b_Ready 标志(true 才可用);另加 b_Updated 和 s_Signature,用来识别四个参数是否变动、避免无谓重算。 日志别写死,用枚举 ENUM_LOG_LEVEL 让用户选 NONE/ERR/INFO/DEBUG,主程序开头声明 Log_Level 全局变量,各函数按级别打印,调试时能还原下单当时的通道细节。下面两段代码分别是初版结构和带错误处理的 f_Set 重写。

MQL5 / C++
class="kw">struct CHANNEL {
  class="type">class="kw">double    d_High;          class=class="str">"cmt">// 范围上方边界的价格
  class="type">class="kw">double    d_Low;           class=class="str">"cmt">// 范围下方边界的价格
  class="type">class="kw">datetime  t_From;          class=class="str">"cmt">// 通道中第一个(最早的)柱的日期/时间
  class="type">class="kw">datetime  t_To;            class=class="str">"cmt">// 通道中最后一个柱的日期/时间
  class="type">int       i_Highest_Offset;class=class="str">"cmt">// 最高价右边的柱数
  class="type">int       i_Lowest_Offset; class=class="str">"cmt">// 最低价右边的柱数
};
class="type">void f_Set(class="type">int i_Bars_Limit, class="type">int i_Newest_Bar_Shift = class="num">1) {
  class="type">class="kw">double da_Price_Array[]; class=class="str">"cmt">// 用于保存通道中所有柱的最高/最低价格的辅助数组
  
  class=class="str">"cmt">// 确定通道的上方边界:
  
  class="type">int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
  class="type">int i_Bar = ArrayMaximum(da_Price_Array);
  d_High = da_Price_Array[i_Bar]; class=class="str">"cmt">// 确定通道的上方边界
  i_Highest_Offset = i_Price_Bars - i_Bar; class=class="str">"cmt">// 距离最高价的时间(柱数)
  
  class=class="str">"cmt">// 确定范围的最低边界:
  
  i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
  i_Bar = ArrayMinimum(da_Price_Array);
  d_Low = da_Price_Array[i_Bar]; class=class="str">"cmt">// 确定通道的最低边界
  i_Lowest_Offset = i_Price_Bars - i_Bar; class=class="str">"cmt">// 距离最低点的时间(柱数)
  
  class="type">class="kw">datetime ta_Time_Array[];
  i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array);
  t_From = ta_Time_Array[class="num">0];
  t_To = ta_Time_Array[i_Price_Bars - class="num">1];
}
enum ENUM_LOG_LEVEL { class=class="str">"cmt">// 记录级别
  LOG_LEVEL_NONE,     class=class="str">"cmt">// 禁用记录
  LOG_LEVEL_ERR,      class=class="str">"cmt">// 只含有错误信息
  LOG_LEVEL_INFO,     class=class="str">"cmt">// 错误 + EA的注释
  LOG_LEVEL_DEBUG     class=class="str">"cmt">// 所有内容
};
class="type">void f_Set(class="type">int i_Bars_Limit, class="type">int i_Newest_Bar_Shift = class="num">1) {
  class="type">class="kw">double da_Price_Array[]; class=class="str">"cmt">// 用于保存通道中所有柱的最高/最低价格的辅助数组
  
  class=class="str">"cmt">// 确定通道的上方边界:
  
  class="type">int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
  
  if(i_Price_Bars == WRONG_VALUE) {
    class=class="str">"cmt">// 处理 CopyHigh 函数的错误
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 错误 #%u", __FUNCSIG__, _LastError);
    class="kw">return;
  }
  
  if(i_Price_Bars < i_Bars_Limit) {
    class=class="str">"cmt">// CopyHigh 函数未能接收到请求数量的数据
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 已经复制了 %u 个柱,共需 %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit);

通道高低边界与结构体的落地写法

找通道上沿别自己写循环比大小,直接让 ArrayMaximum 吐出最高价所在下标,再用这个下标回数组取价格。下方边界同理用 ArrayMinimum,两者都返回 WRONG_VALUE 时必须立刻 return,否则后续拿到的 d_High / d_Low 可能是脏值。 CopyLow 拿不到足够柱数也要拦住:若 i_Price_Bars < i_Bars_Limit,说明历史数据没加载全,此时硬算通道会失真。外汇和贵金属市场跳空频繁,这种数据缺失在周一并盘时尤其常见,属于高风险场景下的必检项。 时间边界用 CopyTime 填,取 ta_Time_Array[0] 为起始、ta_Time_Array[i_Price_Bars-1] 为结束;若返回值小于 1 则把 t_From、t_To 都置 0,避免调用方误用无效时间。 把上述字段收进一个 CHANNEL 结构体最省事:d_High、d_Low 存边界,i_Highest_Offset / i_Lowest_Offset 记最高最低距当前的柱数,b_Ready 标是否算完,s_Signature 留作去重比对。构造函数里先全部清零,b_Ready 和 b_Updated 初始 false,签名给个 '-' 占位。

MQL5 / C++
  CHANNEL() {
    d_High = d_Low = class="num">0;
    t_From = t_To = class="num">0;
    b_Ready = b_Updated = class="kw">false;
    s_Signature = "-";

「通道边界的刷新与容错」

把价格通道的上沿和下沿算准,核心在 f_Set 这个函数:先拉一段 High 数组找最大值,再拉一段 Low 数组找最小值,两者共同界定当前观察窗口的波动边界。 进场前先把 b_Ready 置 false,相当于给后续计算加了个‘暂停服务’标志,避免旧通道值在被覆盖前就被别的模块误用。 CopyHigh / CopyLow 返回 WRONG_VALUE 或实际复制柱数少于请求值,都必须直接 return 并打印错误——历史数据不足时硬算通道,得到的 d_High / d_Low 会严重失真。 ArrayMaximum 拿到的是数组下标,转成距当前柱的偏移要用 i_Price_Bars - i_Bar;比如窗口取 100 根、最高价落在第 30 根(从旧到新排),i_Highest_Offset 就是 70,意味着 70 根前见到上沿。 CopyTime 若连 1 根时间都拿不到,t_From 和 t_To 直接清零,下游若依赖时间区间做绘制就得自行判断空值,别拿 0 时间去算跨度。

MQL5 / C++
i_Highest_Offset = i_Lowest_Offset = WRONG_VALUE;
  }
  
  class="type">void f_Set(class="type">int i_Bars_Limit, class="type">int i_Newest_Bar_Shift = class="num">1) {
    b_Ready = class="kw">false; class=class="str">"cmt">// Pitstop: 设置一个服务标志
    
    class="type">class="kw">double da_Price_Array[]; class=class="str">"cmt">// 用于保存通道中所有柱的最高/最低价格的辅助数组
    
    class=class="str">"cmt">// 确定通道的上方边界:
    
    class="type">int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
    if(i_Price_Bars == WRONG_VALUE) {
      class=class="str">"cmt">// 处理 CopyHigh 函数的错误
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 错误 #%u", __FUNCSIG__, _LastError);
      class="kw">return;
    }
    
    if(i_Price_Bars < i_Bars_Limit) {
      class=class="str">"cmt">// CopyHigh 函数未能接收到请求数量的数据
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 已经复制了 %u 个柱,共需 %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit);
      class="kw">return;
    }
    
    class="type">int i_Bar = ArrayMaximum(da_Price_Array);
    if(i_Bar == WRONG_VALUE) {
      class=class="str">"cmt">// 处理 ArrayMaximum 函数的错误
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMaximum: 错误 #%u", __FUNCSIG__, _LastError);
      class="kw">return;
    }
    
    d_High = da_Price_Array[i_Bar]; class=class="str">"cmt">// 确定通道的上方边界
    i_Highest_Offset = i_Price_Bars - i_Bar; class=class="str">"cmt">// 距离最高价的时间(柱数)
    
    class=class="str">"cmt">// 确定范围的最低边界:
    
    i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
    
    if(i_Price_Bars == WRONG_VALUE) {
      class=class="str">"cmt">// 处理 CopyLow 函数的错误
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: 错误 #%u", __FUNCSIG__, _LastError);
      class="kw">return;
    }
    
    if(i_Price_Bars < i_Bars_Limit) {
      class=class="str">"cmt">// CopyLow 函数未能收到所请求数量的数据
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: 已复制 %u 个柱,共需 %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit);
      class="kw">return;
    }
    
    i_Bar = ArrayMinimum(da_Price_Array);
    if(i_Bar == WRONG_VALUE) {
      class=class="str">"cmt">// 处理 ArrayMinimum 函数的错误
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMinimum: 错误 #%u", __FUNCSIG__, _LastError);
      class="kw">return;
    }
    d_Low = da_Price_Array[i_Bar]; class=class="str">"cmt">// 确定通道的最低边界
    i_Lowest_Offset = i_Price_Bars - i_Bar; class=class="str">"cmt">// 距离最低点的时间(柱数)
    
    class="type">class="kw">datetime ta_Time_Array[];
    i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array);
    if(i_Price_Bars < class="num">1) t_From = t_To = class="num">0;

常见问题

海龟汤靠反向吃通道假突破,升级版在通道参数与边界容错上做了改造,比裸假突破多一层结构过滤,实战中假信号概率更低。
先确认能取到通道高低边界与结构体数据,并定义好刷新逻辑,否则策略落地后边界漂移会直接误触发。
可以,小布能按你设的通道参数实时标注假突破反吃结构,并提示边界刷新异常,省去手动刷图。
边界刷新加容错阈值,仅当价格实破超过设定点数才重算,可有效抗跳空噪音。
日线通道假突破更稳但信号少,四小时敏感度高,建议先日线验证再降周期做过滤。