价格行为分析工具包开发(第11部分):基于Heikin Ashi(平均K线)信号的智能交易系统(EA)·综合运用
📊

价格行为分析工具包开发(第11部分):基于Heikin Ashi(平均K线)信号的智能交易系统(EA)·综合运用

(3/3)·从平滑K线原理到完整 MQL5 代码,收尾价格行为工具包开发的实盘落地

案例拆解 第 3/3 篇
很多交易者把 Heikin Ashi 当成普通变色K线看,却没意识到它的收盘价本就偏离真实盘口,直接拿来算头寸会悄悄放大偏差。这一篇把平均K线接进 EA,让信号过滤和风控在同一套逻辑里跑通,省掉手动换算的坑。

◍ RSI 句柄与tick内的反转判定骨架

EA 初始化时先拿 RSI 句柄:用 iRSI 绑定当前品种、当前周期、RSI_Period 根收盘价。若返回 INVALID_HANDLE 就打印失败并回 INIT_FAILED,否则打印成功并回 INIT_SUCCEEDED,这一步决定指标能不能在后续 tick 里被取数。 OnTick 开头先卡 Bars 数量:要求不少于 TrendCandles + 2,否则直接 return,避免序列索引越界。接着算 Heikin-Ashi、取 RSI 值,再用 DetectReversal 配合阈值过滤——RSI 小于 RSI_Buy_Threshold 才画 233 号买箭头,大于 RSI_Sell_Threshold 才画 234 号卖箭头,双向都带 Print 输出便于在专家日志里核对。 CalculateHeikinAshi 里用 CopyRates 拉全量柱到 rates[],同样以 TrendCandles + 2 做下限校验;ArraySetAsSeries(rates, true) 把时间轴倒序,再按 copied 长度对 haClose / haOpen 做 ArrayResize,保证后续赋值不越界。外汇与贵金属波动剧烈,这类 EA 信号仅作概率参考,实盘前务必在 MT5 策略测试器跑历史数据验证。

MQL5 / C++
  rsiHandle = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE);
  if(rsiHandle == INVALID_HANDLE)
    {
      Print("Failed to create RSI indicator.");
      class="kw">return INIT_FAILED;
    }
  Print("EA Initialized Successfully");
  class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  if(Bars(_Symbol, _Period) < TrendCandles + class="num">2)
    {
      Print("Not enough bars for tick processing.");
      class="kw">return;
    }
class=class="str">"cmt">// Calculate Heikin-Ashi
  CalculateHeikinAshi();
class=class="str">"cmt">// Get RSI Value
  class="type">class="kw">double rsiValue;
  if(!GetRSIValue(rsiValue))
    {
      Print("Failed to retrieve RSI value.");
      class="kw">return;
    }
class=class="str">"cmt">// Detect potential reversals with RSI confirmation
  if(DetectReversal(true) && rsiValue < RSI_Buy_Threshold)  class=class="str">"cmt">// Bullish reversal with RSI confirmation
    {
      DrawArrow("BuyArrow", iTime(NULL, class="num">0, class="num">0), SymbolInfoDouble(_Symbol, SYMBOL_BID), class="num">233, BuyArrowColor);
      Print("Bullish Reversal Detected - RSI:", rsiValue);
    }
  else
    if(DetectReversal(false) && rsiValue > RSI_Sell_Threshold)  class=class="str">"cmt">// Bearish reversal with RSI confirmation
      {
       DrawArrow("SellArrow", iTime(NULL, class="num">0, class="num">0), SymbolInfoDouble(_Symbol, SYMBOL_ASK), class="num">234, SellArrowColor);
       Print("Bearish Reversal Detected - RSI:", rsiValue);
      }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculate Heikin-Ashi                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CalculateHeikinAshi()
  {
  class="type">MqlRates rates[];
  class="type">int copied = CopyRates(_Symbol, _Period, class="num">0, Bars(_Symbol, _Period), rates);
  if(copied < TrendCandles + class="num">2)
    {
      Print("Failed to copy rates. Copied: ", copied);
      class="kw">return;
    }
  ArraySetAsSeries(rates, true);
class=class="str">"cmt">// Resize arrays to match the number of copied bars
  ArrayResize(haClose, copied);
  ArrayResize(haOpen, copied);

「HA反转识别与RSI取数逻辑」

在算完 Heikin-Ashi 四价之后,真正用来抓反转的是 DetectReversal 这个函数。它先按 isBullish 标记定方向,然后往前数 TrendCandles 根 HA 收盘,要求连续同向的根数 ≥ ConsecutiveCandles,否则直接 return false,这一步把无序震荡里的假信号过滤掉一部分。 接着看当前这根(索引1)的实体与影线:body 取收盘价减开盘价绝对值,shadow 在多头时算下影、空头时算上影;若 body 为 0 或 shadow/body 小于 ShadowToBodyRatio,也判为非反转。最后用最新一根(索引0)的 (close-open)*direction 是否转负来确认反向,整段没有用任何未来函数。 RSI 的读取走 GetRSIValue,只 CopyBuffer 取 1 根到 rsiBuffer,成功就回传 rsiBuffer[0] 并置 true。实盘里你可以把 RSI 周期设 14,当 rsiValue 落在 30 以下配合多头 DetectReversal 返回 true,黄金 1H 出现该组合的概率倾向高于裸 K 信号,但外汇/贵金属杠杆高,仍可能连续止损。 别把影线比当死规矩 ShadowToBodyRatio 若设太大(如 1.5),在 EURUSD 15M 上一个月可能只触发 2~3 次;调到 0.6 附近信号变多但假突破也跟着涨,建议先在 MT5 策略测试器里跑 3 个月历史再定参数。

MQL5 / C++
  ArrayResize(haHigh, copied);
  ArrayResize(haLow, copied);
class=class="str">"cmt">// Calculate Heikin-Ashi
  for(class="type">int i = copied - class="num">1; i >= class="num">0; i--)
    {
    haClose[i] = (rates[i].open + rates[i].high + rates[i].low + rates[i].close) / class="num">4.0;
    haOpen[i] = (i == copied - class="num">1) ? (rates[i].open + rates[i].close) / class="num">2.0 : (haOpen[i + class="num">1] + haClose[i + class="num">1]) / class="num">2.0;
    haHigh[i] = MathMax(rates[i].high, MathMax(haOpen[i], haClose[i]));
    haLow[i] = MathMin(rates[i].low, MathMin(haOpen[i], haClose[i]));
    }
  Print("Heikin-Ashi Calculation Complete");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Detect Reversals with Trend Confirmation                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool DetectReversal(class="type">bool isBullish)
  {
  class="type">int direction = isBullish ? class="num">1 : -class="num">1;
class=class="str">"cmt">// Confirm trend location: Check for consecutive candles in the same direction
  class="type">int consecutive = class="num">0;
  for(class="type">int i = class="num">2; i <= TrendCandles + class="num">1; i++)
    {
    if((haClose[i] > haClose[i + class="num">1] && isBullish) || (haClose[i] < haClose[i + class="num">1] && !isBullish))
      consecutive++;
    else
      class="kw">break;
    }
  if(consecutive < ConsecutiveCandles)
    class="kw">return false;
class=class="str">"cmt">// Check for a strong reversal candlestick
  class="type">class="kw">double body = MathAbs(haClose[class="num">1] - haOpen[class="num">1]);
  class="type">class="kw">double shadow = (direction > class="num">0) ? MathAbs(haLow[class="num">1] - haOpen[class="num">1]) : MathAbs(haHigh[class="num">1] - haOpen[class="num">1]);
class=class="str">"cmt">// Avoid division by zero and confirm shadow-to-body ratio
  if(body == class="num">0.0 || (shadow / body) < ShadowToBodyRatio)
    class="kw">return false;
class=class="str">"cmt">// Confirm the reversal with the next candlestick(opposite direction)
  class="kw">return ((haClose[class="num">0] - haOpen[class="num">0]) * direction < class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Get RSI Value                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetRSIValue(class="type">class="kw">double &rsiValue)
  {
  class="type">class="kw">double rsiBuffer[];
  if(CopyBuffer(rsiHandle, class="num">0, class="num">0, class="num">1, rsiBuffer) > class="num">0)
    {
    rsiValue = rsiBuffer[class="num">0];
    class="kw">return true;
    }
  class="kw">return false;
  }

用箭头把信号钉在K线上

在 MT5 图表上标信号,最轻量的做法是用 OBJ_ARROW 画箭头对象,而不是另开指标窗口。下面这段函数接收名称、时间、价格、箭头代码和颜色,直接在对应 K 线位置落一个箭头。 函数先给 name 拼上时间字符串做唯一化,避免同根 K 线重复画导致对象覆盖。若图表里已存在同名对象,先 ObjectDelete 清掉再建,保证刷新时不堆垃圾。 ObjectCreate 第 4 个参数填 0 表示主图表窗口,time 和 price 决定箭头锚点。创建成功后用 ObjectSetInteger 设箭头样式(OBJPROP_ARROWCODE)、颜色与线宽 2,并打印 'Arrow Drawn: name at price',方便在专家日志里核对锚点价格。 失败分支走 GetLastError 打印,常见返回 4200(对象已存在)或 4201(非法窗口),实盘前建议先开策略测试器跑一遍看日志有没有报错。外汇与贵金属杠杆高,箭头仅作视觉提示,不代表后续方向概率倾斜。 OnDeinit 里释放 RSI 句柄用 IndicatorRelease,rsiHandle 不等于 INVALID_HANDLE 才释放,防止重复释放崩 EA。这套清理逻辑在切换周期或卸载 EA 时必触发,漏写会撑住指标句柄不回收。

MQL5 / C++
class="type">void DrawArrow(class="type">class="kw">string name, class="type">class="kw">datetime time, class="type">class="kw">double price, class="type">int code, class="type">class="kw">color clr)
  {
   name += "_" + IntegerToString(time);
   if(ObjectFind(class="num">0, name) != -class="num">1)
      ObjectDelete(class="num">0, name);
   if(ObjectCreate(class="num">0, name, OBJ_ARROW, class="num">0, time, price))
     {
      ObjectSetInteger(class="num">0, name, OBJPROP_ARROWCODE, code);
      ObjectSetInteger(class="num">0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(class="num">0, name, OBJPROP_WIDTH, class="num">2);
      Print("Arrow Drawn: ", name, " at ", price);
     }
   else
     {
      Print("Failed to create arrow: ", GetLastError());
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   if(rsiHandle != INVALID_HANDLE)
      IndicatorRelease(rsiHandle);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ Heikin-Ashi 的收口用法

平滑 K 线在剥噪音上确实能打,反转点的轮廓也比普通蜡烛清楚——但单兵作战容易钝化,挂进你已有的策略里才出效率。 作者从 01/10/24 到 02/02/25 陆续发了 11 个工具,最后那个 Heikin Ashi 信号 EA(v1.0)就是拿它做趋势和平滑反转识别的,说明他自己也把它当模块而非万能钥匙。 外汇和贵金属杠杆高、滑点狠,任何信号都只是概率倾斜;上真金前先开 MT5 模拟账户或跑回溯,把 EA 输入参数反复拧到顺你手感为止。 工具箱再全也得你自己验,别把别人的版本号当圣旨。

让小布替你跑这套信号诊断
这些 Heikin Ashi 趋势确认与噪音过滤的判定,小布盯盘的 AIGC 已内置,打开对应品种页即可看到平滑后的趋势强度提示,你只管决定要不要跟。

常见问题

EA 下单仍应以真实买价卖价为基准,Heikin Ashi 只用于趋势与反转判定,不直接替代成交价,可规避价格失配风险。
可叠加传统K线影线或波动率阈值做二次确认,概率上倾向更早捕捉到失效,而非单纯等 HA 颜色翻转换手。
目前小布内置的是 HA 趋势诊断与异动提示,完整 EA 执行仍需在 MT5 本地部署文中代码,两者互补使用更稳。
两者杠杆与跳空属性不同,贵金属隔夜缺口可能让平均K线失真,建议回测时分段验证,实盘属高风险需控仓。