依据 Heiken-Ashi 指标的交易系统示例·进阶篇
📊

依据 Heiken-Ashi 指标的交易系统示例·进阶篇

(2/3)· 搞懂 haOpen/haClose 递推逻辑后,才算真正跨过 HA 策略的工程门槛

偏理论进阶 第 2/3 篇
把普通 K 线直接套用均线交叉的人,常误以为 HA 图只是换了个皮肤。实际上 HA 的开盘价是前一柱递推出来的,拿错数据源写条件,回测曲线再漂亮也只是自欺。

连接自定义指标写 EA 的骨架

写一个基于 Heiken-Ashi 的 EA,核心只需一个输入参数(手数)加 OnInit、OnTick 两个事件函数,再配上自定义的 CheckForOpenClose 做开平仓判断。别试图在 EA 里手算 HA 值——公式 haOpen=(haOpen[prev]+haClose[prev])/2 依赖前一刻状态,独立算会很绕,直接用 iCustom 挂 MT5 自带的 Examples\Heiken_Ashi 指标更省事。 OnInit 里用 iCustom 拿指标句柄存进全局变量 hHeiken_Ashi;OnTick 先过两道闸:TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 确认终端允许交易,BarsCalculated(hHeiken_Ashi)>100 确认指标算够了数据,才进 CheckForOpenClose。 新烛开盘用 tick_volume 判定最干脆:CopyRates 取最后一根 MqlRates,若 rt[0].tick_volume>1 说明不是新烛第一笔,直接 return。接着用 #define 定 BAR_COUNT=3 及四个缓冲区编号,CopyBuffer 把 HA 的 OHLC 拉进数组。注意数组零号是“最旧”柱,BAR_COUNT-1 才是当前柱——索引搞反条件全错。 持仓检查走标准库 CPositionInfo:Select(_Symbol) 看本品种有无仓,Type() 判多空。平仓用 CTrade::PositionClose,开仓用 PositionOpen(symbol,order_type,volume,price,sl,tp,comment),改止损盈利用 PositionModify。HA 算出的 stop_loss 记得 NormalizeDouble(...,_Digits) 规范精度,否则下单可能报价偏差错。

MQL5 / C++
class=class="str">"cmt">//--- 输入参数
class="kw">input class="type">class="kw">double Lot=class="num">0.1;    class=class="str">"cmt">// 交易量大小
  hHeiken_Ashi=iCustom(NULL,PERIOD_CURRENT,"Examples\\Heiken_Ashi");
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA订单函数                                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 检查能否交易以及计算的柱数
   if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      if(BarsCalculated(hHeiken_Ashi)>class="num">100)
        {
         CheckForOpenClose();
        }
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//--- 只有在收到新烛形的第一笔订单号时处理订单 
  class="type">MqlRates rt[class="num">1];
   if(CopyRates(_Symbol,_Period,class="num">0,class="num">1,rt)!=class="num">1)
     {
       Print("CopyRates ",_Symbol," 失败,没有历史");
       class="kw">return;
     }
   if(rt[class="num">0].tick_volume>class="num">1) class="kw">return;
class=class="str">"cmt">//--- 为了检查条件我们需要最后3个柱
class="macro">#define  BAR_COUNT   class="num">3
class=class="str">"cmt">//--- 用于保存开盘价的指标缓冲区编号
class="macro">#define  HA_OPEN     class="num">0
class=class="str">"cmt">//--- 用于保存最高价的指标缓冲区编号
class="macro">#define  HA_HIGH     class="num">1
class=class="str">"cmt">//--- 用于保存最低价的指标缓冲区编号
class="macro">#define  HA_LOW      class="num">2
class=class="str">"cmt">//--- 用于保存收盘价的指标缓冲区编号
class="macro">#define  HA_CLOSE    class="num">3

「HA 缓冲读取与反向烛形卖单触发」

EA 先把 HA 指标的四个缓冲数组一次性拉进本地:开高低收各占一个长度为 BAR_COUNT 的序列。CopyBuffer 对四个缓冲同时取数,只要任意一个返回条数不等于 BAR_COUNT,就直接 Print 报错并 return,避免拿半截数据去下单。 持仓处理上,若当前品种已有多单(POSITION_TYPE_BUY),代码调用 trade.PositionClose(_Symbol,3) 平掉,注释里那行 lot=lot*2 被禁用以表明本逻辑不做加仓翻仓。 止损不是写死价:stop_loss 取倒数第二根 HA 高点 haHigh[BAR_COUNT-2] 归一化后加 2 个点;再跟券商 SYMBOL_TRADE_STOPS_LEVEL 算出的最小止损位 stop_level 比大小,取较大值,防止报单因止损过近被拒。 真正的卖单触发看的是倒数第三根 HA 烛形颜色——haOpen[BAR_COUNT-3] < haClose[BAR_COUNT-3] 代表那根是阳线,此时若没反向持仓就以市价 ORDER_TYPE_SELL 开空,止损用上面算好的 stop_loss,止盈填 0。若条件不满足但已有持仓,则只调止损不改方向。外汇与贵金属杠杆高,这类基于 HA 反转的胜率随周期漂移,实盘前请在 MT5 策略测试器用至少 3 个月 tick 数据回测。

MQL5 / C++
class="type">class="kw">double   haOpen[BAR_COUNT],haHigh[BAR_COUNT],haLow[BAR_COUNT],haClose[BAR_COUNT];
   if(CopyBuffer(hHeiken_Ashi,HA_OPEN,class="num">0,BAR_COUNT,haOpen)!=BAR_COUNT
      || CopyBuffer(hHeiken_Ashi,HA_HIGH,class="num">0,BAR_COUNT,haHigh)!=BAR_COUNT
      || CopyBuffer(hHeiken_Ashi,HA_LOW,class="num">0,BAR_COUNT,haLow)!=BAR_COUNT
      || CopyBuffer(hHeiken_Ashi,HA_CLOSE,class="num">0,BAR_COUNT,haClose)!=BAR_COUNT)
     {
       Print("从 Heiken_Ashi CopyBuffer 失败, 没有数据");
       class="kw">return;
     }
     class=class="str">"cmt">//--- 检查是否有持仓,如果有,则平掉
       if(posinf.Select(_Symbol))
        {
        if(posinf.Type()==POSITION_TYPE_BUY)
          {
           class=class="str">"cmt">//                lot=lot*class="num">2;
           trade.PositionClose(_Symbol,class="num">3);
          }
       }
       class=class="str">"cmt">//--- 检查和设置止损水平
       class="type">class="kw">double stop_loss=NormalizeDouble(haHigh[BAR_COUNT-class="num">2],_Digits)+_Point*class="num">2;
       class="type">class="kw">double stop_level=SymbolInfoDouble(_Symbol,SYMBOL_ASK)+SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point;
       if(stop_loss<stop_level) stop_loss=stop_level;
       class=class="str">"cmt">//--- 检查组合: 相反颜色的烛形已经生成
       if(haOpen[BAR_COUNT-class="num">3]<haClose[BAR_COUNT-class="num">3])
         {
          if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,SymbolInfoDouble(_Symbol,SYMBOL_BID),stop_loss,class="num">0))
             Print(trade.ResultRetcodeDescription());
         }
       else
       if(posinf.Select(_Symbol))
         {
          if(!trade.PositionModify(_Symbol,stop_loss,class="num">0))
             Print(trade.ResultRetcodeDescription());
         }

◍ 用 HA 蜡烛驱动 EA 的建仓判断

把 Heiken Ashi 接进 EA,第一步是在 OnInit 里拿指标句柄:用 iCustom 指向 Examples\Heiken_Ashi,周期取 PERIOD_CURRENT,这样后续所有缓冲读取都走 hHeiken_Ashi 这个 int 句柄。 OnTick 里先卡两道闸:TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 确认终端允许交易,BarsCalculated(hHeiken_Ashi)>100 保证指标至少算满 100 根柱,否则不进场。这两道过滤能避开数据未就绪时的乱单。 CheckForOpenClose 只在「新柱第一跳」跑逻辑:CopyRates 取最近 1 根,若 tick_volume>1 直接 return,相当于用成交量=1 标记新柱开盘。随后用 CopyBuffer 把 HA_OPEN/HA_HIGH/HA_LOW/HA_CLOSE 四个缓冲区各拽 3 根(BAR_COUNT=3)进数组,任一缓冲区长度不对就打印失败并退出。 信号判定只看倒数第二根:haOpen[1]>haClose[1] 即阴线,倾向触发卖出分支;此时 new 出 CPositionInfo 与 CTrade,lot 直接吃全局 input double Lot=0.1。外汇与贵金属杠杆高,这套 0.1 手默认值在迷你账户也需自行核算保证金占用。 让小布替你跑这套 把上面代码贴进 MT5 MQEditor 编译,先在 EURUSD 的 M5 回测 3 个月,观察 haOpen[1]>haClose[1] 触发频率;若你 HA 指标路径不同,iCustom 第二参数要改你本地文件名,否则句柄返回空。

MQL5 / C++
class="kw">input class="type">class="kw">double Lot=class="num">0.1;      class=class="str">"cmt">// 手数大小
class=class="str">"cmt">//--- 指标句柄
class="type">int      hHeiken_Ashi;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   hHeiken_Ashi=iCustom(NULL,PERIOD_CURRENT,"Examples\\Heiken_Ashi");
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA订单函数                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 应该允许交易并且计算的柱数>class="num">100
   if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      if(BarsCalculated(hHeiken_Ashi)>class="num">100)
         {
          CheckForOpenClose();
         }
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 检查建仓条件                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckForOpenClose()
  {
class=class="str">"cmt">//--- 只在新柱产生时处理订单
   class="type">MqlRates rt[class="num">1];
   if(CopyRates(_Symbol,_Period,class="num">0,class="num">1,rt)!=class="num">1)
     {
      Print("CopyRates ",_Symbol," 失败,没有历史");
      class="kw">return;
     }
   if(rt[class="num">0].tick_volume>class="num">1) class="kw">return;
class=class="str">"cmt">//--- 为了检查条件我们需要最后3个柱
class="macro">#define   BAR_COUNT    class="num">3
class=class="str">"cmt">//--- 保存开盘价的指标缓冲区索引
class="macro">#define   HA_OPEN      class="num">0
class=class="str">"cmt">//--- 保存最高价的指标缓冲区索引
class="macro">#define   HA_HIGH      class="num">1
class=class="str">"cmt">//--- 保存最低价的指标缓冲区索引
class="macro">#define   HA_LOW       class="num">2
class=class="str">"cmt">//--- 保存收盘价的指标缓冲区索引
class="macro">#define   HA_CLOSE     class="num">3
   class="type">class="kw">double   haOpen[BAR_COUNT],haHigh[BAR_COUNT],haLow[BAR_COUNT],haClose[BAR_COUNT];
   if(CopyBuffer(hHeiken_Ashi,HA_OPEN,class="num">0,BAR_COUNT,haOpen)!=BAR_COUNT
      || CopyBuffer(hHeiken_Ashi,HA_HIGH,class="num">0,BAR_COUNT,haHigh)!=BAR_COUNT
      || CopyBuffer(hHeiken_Ashi,HA_LOW,class="num">0,BAR_COUNT,haLow)!=BAR_COUNT
      || CopyBuffer(hHeiken_Ashi,HA_CLOSE,class="num">0,BAR_COUNT,haClose)!=BAR_COUNT)
     {
      Print("从 Heiken_Ashi CopyBuffer 失败, 没有数据");
      class="kw">return;
     }
class=class="str">"cmt">//---- 检查卖出信号
   if(haOpen[BAR_COUNT-class="num">2]>haClose[BAR_COUNT-class="num">2])class=class="str">"cmt">// bear candlestick 
     {
      CPositionInfo posinf;
      CTrade trade;
      class="type">class="kw">double lot=Lot;
   class=class="str">"cmt">//--- 检查是否有持仓,如果有,则平掉

平仓反手与止损的动态校正

这段逻辑处理的是 HA 均线反向后的持仓切换:先查当前品种有无持仓,若是多头且出现卖出信号,直接用 PositionClose(_Symbol,3) 平掉,注释里那行 lot*2 被刻意关掉,说明作者没采用马丁加仓,只做等量反手。 止损不是写死的。卖出时取 haHigh[BAR_COUNT-2] 加 2 个点再 NormalizeDouble 到报价精度,随后跟 SYMBOL_TRADE_STOPS_LEVEL 算出的 stop_level 比大小,低于券商底线就拉到底线——这一行决定了你的止损在原油这种点差跳变品种上不会报 4756 错误。 开仓条件卡在 haOpen[BAR_COUNT-3] < haClose[BAR_COUNT-3],即前第三根 HA 收阳才允许卖开;不满足就走 PositionModify 只改止损不新建。买入分支完全对称:haLow[BAR_COUNT-2] 减 2 点作止损,前第三根收阴才 ORDER_TYPE_BUY。外汇和贵金属杠杆高,这种反手节奏若碰上数据行情滑点,可能连错两单,上 MT5 用策略测试器跑 EURUSD 的 M15 能直接看成交回放。

MQL5 / C++
if(posinf.Select(_Symbol))
  {
   if(posinf.Type()==POSITION_TYPE_BUY)
     {
      class=class="str">"cmt">//       lot=lot*class="num">2;
      trade.PositionClose(_Symbol,class="num">3);
     }
  }
class=class="str">"cmt">//--- 检查和设置止损水平
class="type">class="kw">double stop_loss=NormalizeDouble(haHigh[BAR_COUNT-class="num">2],_Digits)+_Point*class="num">2;
class="type">class="kw">double stop_level=SymbolInfoDouble(_Symbol,SYMBOL_ASK)+SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point;
if(stop_loss<stop_level) stop_loss=stop_level;
class=class="str">"cmt">//--- 检查组合: 相反颜色的烛形已经生成
if(haOpen[BAR_COUNT-class="num">3]<haClose[BAR_COUNT-class="num">3])
  {
   if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,SymbolInfoDouble(_Symbol,SYMBOL_BID),stop_loss,class="num">0))
     Print(trade.ResultRetcodeDescription());
  }
else
if(posinf.Select(_Symbol))
  {
   if(!trade.PositionModify(_Symbol,stop_loss,class="num">0))
     Print(trade.ResultRetcodeDescription());
  }
class=class="str">"cmt">//---- 检查买入信号
if(haOpen[BAR_COUNT-class="num">2]<haClose[BAR_COUNT-class="num">2]) class=class="str">"cmt">// bull candle
  {
   CPositionInfo posinf;
   CTrade trade;
   class="type">class="kw">double lot=Lot;
 class=class="str">"cmt">//--- 检查是否有持仓,如果有,则平掉
   if(posinf.Select(_Symbol))
     {
      if(posinf.Type()==POSITION_TYPE_SELL)
        {
         class=class="str">"cmt">//       lot=lot*class="num">2;
         trade.PositionClose(_Symbol,class="num">3);
        }
     }
class=class="str">"cmt">//--- 检查和设置止损水平
   class="type">class="kw">double stop_loss=NormalizeDouble(haLow[BAR_COUNT-class="num">2],_Digits)-_Point*class="num">2;
   class="type">class="kw">double stop_level=SymbolInfoDouble(_Symbol,SYMBOL_BID)-SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point;
   if(stop_loss>stop_level) stop_loss=stop_level;
class=class="str">"cmt">//--- 检查组合: 相反颜色的烛形已经生成
   if(haOpen[BAR_COUNT-class="num">3]>haClose[BAR_COUNT-class="num">3])
     {
      if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,SymbolInfoDouble(_Symbol,SYMBOL_ASK),stop_loss,class="num">0))
        Print(trade.ResultRetcodeDescription());
     }
   else
把 HA 重绘逻辑交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 HA 趋势强度与影线缺失区的实时标注,你只管判断入场节奏。

常见问题

haOpen 采用 (前一根 haOpen + 前一根 haClose)/2 递推,目的是平滑噪声;若直接取普通 Open,合成烛就失去了去毛刺的意义,趋势结构会失真。
iCustom 依赖第三方指标文件且存在调用开销;自行按 OHLC 四式计算可写入 EA 缓冲区,便于做跨周期引用与零延迟判断,更适合进阶系统化。
可以,小布盯盘对主流外汇贵金属品种内置了 HA 合成烛诊断,会标注无影线白烛连续区的概率倾向,但仍需交易者结合风控决策,外汇贵金属属高风险。
概率上 HA 在平台整理时会压缩影线、制造假顺畅感,可能诱导过早顺势单;建议叠加波动率过滤,贵金属跳空时段尤需谨慎。