在图表上快速检验交易理念·进阶篇
(2/3)· 从 451 个 EA 说明里扒出真实策略分布,教你把脑中念头直接丢进图表验尸
◍ 新K线入列前的数组与计数门槛
处理新蜡烛的第一步,是把当前这根bar的字段逐个写进 m_candles[0]。时间、开高低收、tick_volume、real_volume、spread 一共 8 个 double/long/int 字段,必须对齐结构体成员,否则后续分析会读到脏数据。 另一种重载走 MqlRates 引用直接整体赋值:m_candles[0] = candle,比逐字段写更省事,但前提是调用方已经把 spread 等字段填好。两种入口都先跑 PrepareArrayForNewCandle() 做位移或扩容,再判计数。 关键门槛在 m_handled_candles_count 与 m_history_depth 的比较:前者小于后者时函数直接 return 0,不触发 DoAnalizeNewCandle()。也就是说,历史深度没攒够,枢轴判定逻辑一律不跑——在 MT5 里把 m_history_depth 设成 20,就意味着前 19 根新烛都是空转。 外汇与贵金属波动受杠杆与消息面影响大,这类计数门槛只是数据准备环节,不代表任何方向概率,实盘前请在策略测试器里用真实点差验证 return 0 的过渡期长度。
class="kw">const class="type">class="kw">double low, class="kw">const class="type">class="kw">double close, class="kw">const class="type">long tick_volume, class="kw">const class="type">long volume, class="kw">const class="type">int spread ) { class=class="str">"cmt">// Prepare the array for the new candle PrepareArrayForNewCandle(); class=class="str">"cmt">// Fill out the current value of the candle m_candles[class="num">0].time = time; m_candles[class="num">0].open = open; m_candles[class="num">0].high = high; m_candles[class="num">0].low = low; m_candles[class="num">0].close = close; m_candles[class="num">0].tick_volume = tick_volume; m_candles[class="num">0].real_volume = volume; m_candles[class="num">0].spread = spread; class=class="str">"cmt">// Check if there is enough data for calculation if (m_handled_candles_count < m_history_depth) class="kw">return class="num">0; else class="kw">return DoAnalizeNewCandle(); } class="type">int CPivotCandlesClass::AnalizeNewCandle(class="type">MqlRates& candle) { class=class="str">"cmt">// Prepare the array for the new candle PrepareArrayForNewCandle(); class=class="str">"cmt">// Add the candle m_candles[class="num">0] = candle; class=class="str">"cmt">// Check if there is enough data for calculation if (m_handled_candles_count < m_history_depth) class="kw">return class="num">0; else class="kw">return DoAnalizeNewCandle(); } class="type">void CPivotCandlesClass::PrepareArrayForNewCandle() {
均值与极值判定的代码落地
在 PivotCandles 类里,PrepareCalculation 负责把最新一根蜡烛塞进计算前的状态准备。它先备份上一根 MA 值到 m_prev_ma_value,再把 m_ma_value、m_volatility 归零,并将 m_is_highest / m_is_lowest 预设为 true,等待循环里被证伪。 循环按 m_history_depth 跑一遍:前 iMAPeriod 根收盘价累加进 price_sum 用于算均线;前 iVolatilityCandlesCount 根的高低价差累加进 m_volatility;前 iPrevCandlesCount 根则逐根比对,只要当前 candle[0] 的 high 不高于某历史根 high,或 low 不低于某历史根 low,对应布尔位就被翻成 false。 最终 m_ma_value = price_sum / iMAPeriod,m_volatility 除以 iVolatilityCandlesCount 得到平均波动,再调 CheckCandleSize 给 m_candle_pattern 赋值。外汇与贵金属波动剧烈,这类极值判断只代表历史窗口内的相对位置,转向概率而非确定性。 CheckCandleSize 用 full height 减 body height 的视角去识别锤子线与流星线,返回 0/1/-1 三态。你可以直接把下面这段拷进 MT5 的 EA 或指标里,改 iMAPeriod 和 iVolatilityCandlesCount 看信号密度变化。
class=class="str">"cmt">// Shift the array by one position to write the new value there ArrayCopy(m_candles, m_candles, class="num">1, class="num">0, m_history_depth-class="num">1); class=class="str">"cmt">// Increase the counter of added candles m_handled_candles_count++; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CalcMAValue | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate the current values of the Moving Average, volatility | class=class="str">"cmt">//| and the value extremality | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CPivotCandlesClass::PrepareCalculation() { class=class="str">"cmt">// Store the previous value m_prev_ma_value = m_ma_value; m_ma_value = class="num">0; m_is_highest = true; class=class="str">"cmt">// check if the current candle is the highest one m_is_lowest = true; class=class="str">"cmt">// check if the current candle is the lowest one m_volatility = class="num">0; class=class="str">"cmt">// average volatility class="type">class="kw">double price_sum = class="num">0; class=class="str">"cmt">// Variable for storing the sum for (class="type">int i=class="num">0; i<m_history_depth; i++) { if (i<iMAPeriod) price_sum += m_candles[i].close; if (i>class="num">0 && i<=iVolatilityCandlesCount) m_volatility += m_candles[i].high - m_candles[i].low; if (i>class="num">0 && i<=iPrevCandlesCount) { m_is_highest = m_is_highest && (m_candles[class="num">0].high > m_candles[i].high); m_is_lowest = m_is_lowest && (m_candles[class="num">0].low < m_candles[i].low); } } m_ma_value = price_sum / iMAPeriod; m_volatility /= iVolatilityCandlesCount; m_candle_pattern = CheckCandleSize(m_candles[class="num">0]); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CheckCandleSize | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if the candle sizes comply with the patterns | class=class="str">"cmt">//| The function returns: | class=class="str">"cmt">//| class="num">0 - if the candle does not comply with the patterns | class=class="str">"cmt">//| class="num">1 - if "hammer" pattern is detected | class=class="str">"cmt">//| -class="num">1 - if "shooting star" pattern is detected | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CPivotCandlesClass::CheckCandleSize(class="type">MqlRates &candle) { class="type">class="kw">double candle_height=candle.high-candle.low; class=class="str">"cmt">// candle&class="macro">#x27;s full height class="type">class="kw">double candle_body=MathAbs(candle.close-candle.open); class=class="str">"cmt">// candle&class="macro">#x27;s body height
「锤子线与流星线的代码判定逻辑」
识别单根 K 线形态时,先用实体占整根高度的比例过滤噪音:若 candle_body/candle_height*100.0 大于 iMaxBodySize,直接返回 0,说明实体过大不纳入 pivot 候选。
随后分别算上影线和下影线高度。上影 = 最高价减去开收二者最大值,下影 = 开收二者最小值减去最低价。若上影占比 ≤ iMaxShadowSize,判定为锤子线返回 1;下影占比 ≤ 该阈值则判流星线返回 -1,其余情况返回 0。
真正下单信号在 DoAnalizeNewCandle 里合成。离场看均线穿越:前一根收在均线上方、当前收在下方,给 signal=2 平空;反向穿越给 -2 平多。
入场需先满足波动门槛 high-low >= iVolatilityPercent/100.0 * m_volatility。在此基础上,流星线且为区间最高、收在均线上方,给 -1 做空倾向;锤子线且为区间最低、收在均线下方,给 1 做多倾向。外汇与贵金属杠杆高,信号仅为概率倾向,实盘前务必在 MT5 用历史数据跑一遍验证参数敏感性。
class=class="str">"cmt">// Check if the candle has a small body if(candle_body/candle_height*class="num">100.0>iMaxBodySize) class="kw">return class="num">0; class="type">class="kw">double candle_top_shadow=candle.high-MathMax(candle.open,candle.close); class=class="str">"cmt">// candle upper shadow height class="type">class="kw">double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; class=class="str">"cmt">// candle bottom shadow height class=class="str">"cmt">// If the upper shadow is very small, that indicates the "hammer" pattern if(candle_top_shadow/candle_height*class="num">100.0<=iMaxShadowSize) class="kw">return class="num">1; class=class="str">"cmt">// If the bottom shadow is very small, that indicates the "shooting star" pattern else if(candle_bottom_shadow/candle_height*class="num">100.0<=iMaxShadowSize) class="kw">return -class="num">1; else class="kw">return class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DoAnalizeNewCandle | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Real analysis of compliance with the patterns | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CPivotCandlesClass::DoAnalizeNewCandle() { class=class="str">"cmt">// Prepare data for analyzing the current situation PrepareCalculation(); class=class="str">"cmt">// Process prepared data and set the exit signal class="type">int signal = class="num">0; class=class="str">"cmt">/////////////////////////////////////////////////////////////////// class=class="str">"cmt">// EXIT SIGNALS // class=class="str">"cmt">/////////////////////////////////////////////////////////////////// class=class="str">"cmt">// If price crosses the moving average downwards, class="type">short position is closed if(m_candles[class="num">1].close > m_prev_ma_value && m_candles[class="num">0].close < m_ma_value) signal = class="num">2; class=class="str">"cmt">// If price crosses the moving average upwards, class="type">long position is closed else if (m_candles[class="num">1].close < m_prev_ma_value && m_candles[class="num">0].close > m_ma_value) signal = -class="num">2; class=class="str">"cmt">/////////////////////////////////////////////////////////////////// class=class="str">"cmt">// ENTRY SIGNALS // class=class="str">"cmt">/////////////////////////////////////////////////////////////////// class=class="str">"cmt">// Check if the minimum volatility condition is met if (m_candles[class="num">0].high - m_candles[class="num">0].low >= iVolatilityPercent / class="num">100.0 * m_volatility) { class=class="str">"cmt">// Checks for "shooting star" pattern if (m_candle_pattern < class="num">0 && m_is_highest && m_candles[class="num">0].close > m_ma_value) signal = -class="num">1; class=class="str">"cmt">// Checks for "hammer" pattern else if (m_candle_pattern > class="num">0 && m_is_lowest && m_candles[class="num">0].close < m_ma_value) signal = class="num">1; } class="kw">return signal; }
◍ 指标缓冲区与绘图层的绑定细节
这套枢轴蜡烛指标在主图窗口跑了 4 个缓冲区,但只对外画 2 条图:一条慢均线 SlowMA(实线 AliceBlue),一条带颜色的箭头信号 ChartSignal(宽度 3,配 5 色)。
代码里 #property indicator_buffers 4 和 #property indicator_plots 2 直接定死了这个结构——多出来的两个缓冲不画图,只给内部计算或着色用。
OnInit 里把 0 号缓冲绑给 SMA、1 号绑给 ChartSignal 作图数据;2 号 SignalColor 是颜色索引缓冲,3 号 Signal 是计算缓冲不显示。最后 PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0) 把箭头图的空值设为 0,避免 0 价位点被误画。
开 MT5 把这段贴进自定义指标,改 indicator_color2 的五色顺序,就能直观区分不同方向的枢轴信号。外汇和贵金属波动剧烈,这类信号仅作概率参考,实盘须自担高风险。
class="macro">#class="kw">property copyright "Copyright class="num">2012, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class=class="str">"cmt">// Use four buffers, while drawing two class="macro">#class="kw">property indicator_buffers class="num">4 class="macro">#class="kw">property indicator_plots class="num">2 class=class="str">"cmt">//--- plot SlowMA class="macro">#class="kw">property indicator_label1 "SlowMA" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrAliceBlue class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- plot ChartSignal class="macro">#class="kw">property indicator_label2 "ChartSignal" class="macro">#class="kw">property indicator_type2 DRAW_COLOR_ARROW class="macro">#class="kw">property indicator_color2 clrLightSalmon,clrOrangeRed,clrBlack,clrSteelBlue,clrLightBlue class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">3 class="macro">#include <PivotCandlesClass.mqh> class=class="str">"cmt">//--- Indicator buffers class="type">class="kw">double SMA[]; class=class="str">"cmt">// Values of the Moving Average class="type">class="kw">double Signal[]; class=class="str">"cmt">// Signal values class="type">class="kw">double ChartSignal[]; class=class="str">"cmt">// Location of signals on the chart class="type">class="kw">double SignalColor[]; class=class="str">"cmt">// Signal class="type">color array class=class="str">"cmt">//--- Calculation class CPivotCandlesClass PivotCandlesClass; class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,SMA,INDICATOR_DATA); SetIndexBuffer(class="num">1,ChartSignal,INDICATOR_DATA); SetIndexBuffer(class="num">2,SignalColor,INDICATOR_COLOR_INDEX); SetIndexBuffer(class="num">3,Signal,INDICATOR_CALCULATIONS); class=class="str">"cmt">//--- set class="num">0 as an empty value PlotIndexSetDouble(class="num">1,PLOT_EMPTY_VALUE,class="num">0); class="kw">return(class="num">0); }
逐根 K 线里的信号与均线回填
指标的核心计算落在 OnCalculate 里,MT5 每来一根新柱或重算历史都会调它。prev_calculated 为 0 代表首次运行或历史重载,这时必须清掉 PivotCandlesClass 里的旧状态,否则会拿上一轮行情的残留数据去判信号。 循环从 prev_calculated 跑到 rates_total-2(end_calc_edge),对每根 K 线调 AnalizeNewCandle 拿到 signal,值域是 -2 到 2。signal 写进 Signal 缓冲区,同时把 PivotCandlesClass.MAValue() 的均线值写进 SMA。 图表上的箭头位置按信号正负挂:signal<0 贴在 high[i] 上方,signal>0 贴在 low[i] 下方,否则归 0 不画。颜色索引是 0..4,所以用 signal+2 把 [-2..2] 平移对齐,负号信号对应冷色、正号对应暖色。 末根未闭合 K 线(end_calc_edge)的 SMA 直接复制前一根,避免均线在实时 Tick 上跳变。外汇与贵金属杠杆高、滑点大,这类信号仅作概率参考,实盘前请在 MT5 策略测试器用至少 3 个月 tick 数据验证。
class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime &time[], class="kw">const class="type">class="kw">double &open[], class="kw">const class="type">class="kw">double &high[], class="kw">const class="type">class="kw">double &low[], class="kw">const class="type">class="kw">double &close[], class="kw">const class="type">long &tick_volume[], class="kw">const class="type">long &volume[], class="kw">const class="type">int &spread[]) { class=class="str">"cmt">// 首次计算或历史更新时,清理计算对象 if (prev_calculated == class="num">0) PivotCandlesClass.CleanupHistory(); class="type">int end_calc_edge = rates_total-class="num">1; if (prev_calculated >= end_calc_edge) class="kw">return end_calc_edge; for(class="type">int i=prev_calculated; i<end_calc_edge; i++) { class=class="str">"cmt">// 分析新K线,返回-class="num">2..2的信号 class="type">int signal = PivotCandlesClass.AnalizeNewCandle(time[i],open[i],high[i],low[i],close[i],tick_volume[i],volume[i],spread[i]); Signal[i] = signal; SMA[i] = PivotCandlesClass.MAValue(); class=class="str">"cmt">// 信号处理完,往图表上摆 class=class="str">"cmt">// 设定信号显示位置... if (signal < class="num">0) ChartSignal[i]=high[i]; else if (signal > class="num">0) ChartSignal[i]=low[i]; else ChartSignal[i]=class="num">0; class=class="str">"cmt">// ..以及颜色 class=class="str">"cmt">// 信号范围[-class="num">2..class="num">2],颜色索引[class="num">0..class="num">4],做对齐 SignalColor[i]=signal+class="num">2; } class=class="str">"cmt">// 末根均线沿用前一根,防止陡降 SMA[end_calc_edge] = SMA[end_calc_edge-class="num">1]; class=class="str">"cmt">//--- 返回下次调用的 prev_calculated class="kw">return(end_calc_edge); }
「用结构体把账户浮盈算清楚」
想在 EA 里一次返回余额和净值,又不想用一堆出参,可以直接定义一个轻量结构体。下面这段把 balance 和 equity 打包进 BalanceResults,函数末尾一句 return 就能把两个值都丢出来,比分别传引用干净。 实际跑起来,FindIndicatorHandle 会先抓主图窗口总数:ChartGetInteger(0,CHART_WINDOWS_TOTAL) 在多数 MT5 配置下返回 1~多个子窗口。它从最后一个窗口倒序扫,用 ChartIndicatorsTotal 拿每个窗口挂的指标数,再靠 ChartIndicatorName 比对名字,命中就 ChartIndicatorGet 返回句柄,没找到统一回 -1。 基类 CBaseBalanceCalculator 把当前仓位量、开仓价、点值、计算结果都收进私有成员。m_symbol_points 这个点值在外汇和贵金属上随品种跳动,XAUUSD 一个点通常 0.01 美元/盎司量级,杠杆和高波动下净值回撤可能很快,账户风险始终不低。 把这段贴进 MT5 的 MQ5 文件,编译后挂到 EURUSD 或 XAUUSD 图表,改 _name 传你自己的指标名,就能在专家日志里验证句柄是否抓对。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Common structures | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// Structure for returning calculation results class=class="str">"cmt">// using only class="kw">return command; class="kw">struct BalanceResults { class="type">class="kw">double balance; class="type">class="kw">double equity; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Common function | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// Function for searching for the indicator handle by its name class="type">int FindIndicatorHandle(class="type">class="kw">string _name) { class=class="str">"cmt">// Receive the number of open charts class="type">int windowsCount = (class="type">int)ChartGetInteger(class="num">0,CHART_WINDOWS_TOTAL); class=class="str">"cmt">// Search all of them for(class="type">int w=windowsCount-class="num">1; w>=class="num">0; w--) { class=class="str">"cmt">// How many indicators are attached to the current chart class="type">int indicatorsCount = ChartIndicatorsTotal(class="num">0,w); class=class="str">"cmt">// Search by all chart indicators for(class="type">int i=class="num">0;i<indicatorsCount;i++) { class="type">class="kw">string name = ChartIndicatorName(class="num">0,w,i); class=class="str">"cmt">// If such an indicator is found, class="kw">return its handle if (name == _name) class="kw">return ChartIndicatorGet(class="num">0,w,name); } } class=class="str">"cmt">// If there is no such an indicator, class="kw">return the incorrect handle class="kw">return -class="num">1; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Base calculation class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBaseBalanceCalculator { class="kw">private: class="type">class="kw">double m_position_volume; class=class="str">"cmt">// Current open position volume class="type">class="kw">double m_position_price; class=class="str">"cmt">// Position opening price class="type">class="kw">double m_symbol_points; class=class="str">"cmt">// Value of one point for the current symbol BalanceResults m_results; class=class="str">"cmt">// Calculation results class="kw">public: class="type">void CBaseBalanceCalculator(class="type">class="kw">string symbol_name = ""); class="type">void Cleanup(); BalanceResults Calculate( class="kw">const class="type">class="kw">double _prev_balance,