依据 Heiken-Ashi 指标的交易系统示例·进阶篇
(2/3)· 搞懂 haOpen/haClose 递推逻辑后,才算真正跨过 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) 规范精度,否则下单可能报价偏差错。
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 数据回测。
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 第二参数要改你本地文件名,否则句柄返回空。
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 能直接看成交回放。
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