价格行为分析工具包开发(第11部分):基于Heikin Ashi(平均K线)信号的智能交易系统(EA)(基础篇)
用平均K线过滤噪音做EA信号
Heikin Ashi(平均K线)把开高低收做了平滑处理,相邻K线之间通常留有缺口或连续实体,能让趋势段比普通蜡烛图更干净。把它直接接进 EA 的信号判断,比单纯用收盘价穿越均线更少被毛刺扫掉。 在 MT5 里读平均K线不能用普通 Open[i] 系列,要走 iHA 或自定义缓冲。下面这段从指标句柄取最近一根 HA 收盘与开盘,用来判定是阳线还是阴线: 外汇与贵金属杠杆高,HA 只降噪不预言方向,信号失效时回撤可能很快,任何结论都只是概率倾向。
class="type">int haHandle = iHA(_Symbol, PERIOD_CURRENT); class="type">class="kw">double haOpen[class="num">1], haClose[class="num">1]; CopyBuffer(haHandle, class="num">0, class="num">0, class="num">1, haOpen); CopyBuffer(haHandle, class="num">3, class="num">0, class="num">1, haClose); class="type">bool isBull = haClose[class="num">0] > haOpen[class="num">0];
◍ 从均线到Heikin Ashi的噪音过滤逻辑
数学工具是价格行为分析的客观底座。以南非兰特兑美元(ZAR/USD)为例,有交易者用50日均线(SMA)识别趋势反转,再叠加标准差衡量波动,来确认突破是否值得跟。这类组合的价值不在预测,而在把主观判断压成可复述的规则。 头寸规模公式(风险百分比×账户权益÷止损距离)把仓位和止损绑死,外汇与贵金属的高杠杆下,这一步直接决定存活率。Fibonacci回调则负责把盈利目标画成具体价位,而不是「感觉差不多就走」。 本文主线转向 Heikin Ashi:它用特殊开盘收盘算法重画K线,核心是把市场噪音洗掉。普通K线容易被单根长影线带偏,HA通过平滑处理让趋势本身更突出,适合和量化信号叠加。 接下来不绕弯,直接拆 HA 的定义、策略内核与完整 MQL5 实现,最后回看实测结果。目录依次是 Heikin Ashi、策略、其他函数、代码、成果、结论。
「Heikin Ashi 到底平滑了什么」
Heikin Ashi 这套画法可追溯到 18 世纪日本米市交易员 Munehisa Homma,也就是 K 线图的早期推手之一。日语里 Heikin 是“平均”、Ashi 是“柱”,合起来就是用平均价重画 K 线,把单根波动里的噪音压下去,让趋势看起来更连贯。 传统 K 线直接采用真实的开、高、低、收,所以缺口和毛刺都原样保留;Heikin Ashi 改用平均公式,红绿交替明显减少,但代价是收盘价只是“平均价”,不一定等于真实成交价。 从对照表能看出关键差异:传统 K 线在趋势里常出现反复红绿,Heikin Ashi 颜色变化少、反转信号慢半拍但一旦确认更稳;传统图展示 K 线间缺口,Heikin Ashi 因平均处理几乎不画缺口。外汇与贵金属波动大、跳空频繁,用 HA 看图能降噪,但下单仍要以真实报价为准,别把平均价当盘口价。
四步过滤的 HA 反转 EA 怎么搭
这套 EA 的核心不是单看某根 K 线,而是用四道关卡依次筛信号:先算 Heikin Ashi(HA)平滑值,再确认趋势已连续走出,接着找长影线拒动量的反转雏形,最后用 RSI 超买超卖做交叉验证。任何一步不过,就直接 return,不画箭头。 HA 计算部分把传统 OHLC 改写成平均序列:haClose 取四价均值,haOpen 用前一根的开收均值承继,haHigh/haLow 包住真实波动区间。这样连成一串后,突发毛刺被削掉,实体变小、影线相对拉长,肉眼看趋势比裸 K 干净。下面这段 MQL5 就是 HA 数组填充和趋势计数、影线比判定的主干。 趋势确认靠输入参数 TrendCandles 控连续根数。代码里从 i=2 往后数,看涨时要求 haClose[i] 严格大于前一根,看跌反之;累计数 consecutive 达不到 ConsecutiveCandles 就退出。这一步把「随机波动假趋势」挡在门外,只在定向走势充分确立后才放行。 反转识别量的是影线实体比。body 取 haClose 与 haOpen 差的绝对值,shadow 按方向取下影或上影;若 body 为 0 或 shadow/body 小于 ShadowToBodyRatio,信号作废。高比值说明价格强烈拒绝当前方向,是小实体长影线的典型反转相。 末道 RSI 确认没贴在代码截段里,但逻辑固定:HA 给了形态、RSI 给动量——看涨反转要 RSI 低于买入阈值(超卖),看跌要高于卖出阈值(超买)。两者共振才在图表标箭头。外汇与贵金属杠杆高,这类多指标过滤只降低虚警概率,不消除方向误判风险,上 MT5 回测时建议先把 ConsecutiveCandles 与 ShadowToBodyRatio 拆开调参。
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); ArrayResize(haHigh, copied); ArrayResize(haLow, copied); class=class="str">"cmt">// Calculate Heikin-Ashi values for each bar 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="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">// Get RSI Value