为EA交易提供指标的现成模板(第3部分):趋势指标·进阶篇
(2/3)·从AMA到抛物线转向,复制粘贴即可在EA里拉出指标句柄与数据
◍ 枚举线与输入参数的骨架
这段声明把价格相对参考线的状态拆成了 7 种:上方、下方、向上穿越、向下穿越、从下触碰、从上触碰、等于。实际写 EA 时,靠这套枚举就能区分「碰线」和「穿线」,避免把回调触碰误判成趋势反转。 输入参数里 InpPeriod=9 是 AMA 默认周期,快线 InpPeriodFast=2、慢线 InpPeriodSlow=30,三者拉开差距才能看出自适应均线的斜率变化。InpShift=0 表示不对齐偏移,InpPrice 锁定 PRICE_CLOSE,用收盘价算更抗影线噪声。 全局变量里 handle 存指标句柄,period_fast / period_slow 分开记录两条 EMA 周期,ind_digits 抓小数位是为了面板显示不串位。最后 mouse_bar_index 和 CDashboard 指针是给悬浮面板用的,鼠标指哪根 K 线就取哪根的状态。外汇和贵金属波动大,这类状态判断只是信号过滤的一环,实盘仍属高风险。
LINE_STATE_ABOVE, class=class="str">"cmt">// Above value LINE_STATE_UNDER, class=class="str">"cmt">// Below value LINE_STATE_CROSS_UP, class=class="str">"cmt">// Crossing value upwards LINE_STATE_CROSS_DOWN, class=class="str">"cmt">// Crossing value downwards LINE_STATE_TOUCH_BELOW, class=class="str">"cmt">// Touching value from below LINE_STATE_TOUCH_ABOVE, class=class="str">"cmt">// Touch value from above LINE_STATE_EQUALS, class=class="str">"cmt">// Equal to value }; class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">uint InpPeriod = class="num">9; class=class="str">"cmt">/* Period */ class="kw">input class="type">uint InpPeriodFast = class="num">2; class=class="str">"cmt">/* Fast EMA Period*/ class="kw">input class="type">uint InpPeriodSlow = class="num">30; class=class="str">"cmt">/* Slow EMA Period*/ class="kw">input class="type">int InpShift = class="num">0; class=class="str">"cmt">/* AMA Shift */ class="kw">input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; class=class="str">"cmt">/* Applied Price */ class=class="str">"cmt">//--- global variables class="type">int handle=INVALID_HANDLE; class=class="str">"cmt">// Indicator handle class="type">int period=class="num">0; class=class="str">"cmt">// AMA calculation period class="type">int period_fast=class="num">0; class=class="str">"cmt">// Fast EMA calculation period class="type">int period_slow=class="num">0; class=class="str">"cmt">// Slow EMA calculation period class="type">int ind_digits=class="num">0; class=class="str">"cmt">// Number of decimal places in the indicator values class="type">class="kw">string ind_title; class=class="str">"cmt">// Indicator description class=class="str">"cmt">//--- variables for the panel class="type">int mouse_bar_index; class=class="str">"cmt">// Index of the bar the data is taken from CDashboard *panel=NULL; class=class="str">"cmt">// Pointer to the panel object
「EA 启动时的句柄与面板挂载」
MT5 的 OnInit 是 EA 加载后第一个被执行的函数,所有指标句柄和图形面板都得在这里一次性建好,否则后续 OnTick 里取数会直接报 INVALID_HANDLE。 先用 EventSetTimer(60) 挂一个 60 秒心跳,保证即便行情静止也能周期性刷新仪表板;周期参数做了下限保护,InpPeriod 小于 1 时回退到 9,快线回退 2、慢线回退 30,避免用户输入 0 把 AMA 算崩。 iAMA 创建句柄后必须判空,失败就打印错误码并返回 INIT_FAILED,这一步能让你在 MT5 终端日志里直接看到是哪一行参数不合法。 如果策略带可视化面板,OnInit 里用 new CDashboard(1,20,20,197,225) 在坐标 (20,20) 拉出 197×225 的悬浮窗,字体固定 Calibri 9 号;面板对象创建失败同样返回 INIT_FAILED,终端会拒绝加载 EA。外汇与贵金属杠杆高,EA 初始化失败可能让你错过风控触发,建议每次改完参数先在策略测试器跑一遍 OnInit 日志。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- create timer EventSetTimer(class="num">60); class=class="str">"cmt">//--- Indicator class=class="str">"cmt">//--- Set and adjust the calculation period and levels if necessary period=class="type">int(InpPeriod<class="num">1 ? class="num">9 : InpPeriod); period_fast=class="type">int(InpPeriodFast<class="num">1 ? class="num">2 : InpPeriodFast); period_slow=class="type">int(InpPeriodSlow<class="num">1 ? class="num">30 : InpPeriodSlow); class=class="str">"cmt">//--- Set the indicator name and the number of decimal places ind_title=StringFormat("AMA(%lu,%lu,%lu)",period,period_fast,period_slow); ind_digits=Digits()+class="num">1; class=class="str">"cmt">//--- Create indicator handle ResetLastError(); handle=iAMA(Symbol(),PERIOD_CURRENT,period,period_fast,period_slow,InpShift,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); class="kw">return INIT_FAILED; } class=class="str">"cmt">//--- Dashboard class=class="str">"cmt">//--- Create the panel panel=new CDashboard(class="num">1,class="num">20,class="num">20,class="num">197,class="num">225); if(panel==NULL) { Print("Error. Failed to create panel object"); class="kw">return INIT_FAILED; } class=class="str">"cmt">//--- Set font parameters panel.SetFontParams("Calibri",class="num">9);
面板与双表格的初始化落点
这段初始化逻辑把信息分层做得很直接:先用 Symbol() 拼接 Period() 的枚举字符串(从第7个字符截起)作为面板抬头,能一眼看清当前品种和周期。 创建两张表,ID 0 放 K 线数据、ID 1 放指标数据,DrawGrid 的参数里藏着布局细节——ID 0 的 Y 起点是 20、行高 6、列宽 18,ID 1 则靠 TableY2(0)+22 算出 Y1,避免和上面表格叠在一起。 最后把两张表用 GridPrint 推到日志,mouse_bar_index 置 0 并调 DrawData 画当前柱,返回 INIT_SUCCEEDED。开 MT5 把这段塞进 OnInit,能立刻看到带品种周期抬头的双层面板,外汇和贵金属品种的高波动下,这种分层比堆在一个框里更不容易看错行。
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7)); class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it panel.CreateNewTable(class="num">0); class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,class="num">96); class=class="str">"cmt">//--- Create a table with ID class="num">1 to display indicator data in it panel.CreateNewTable(class="num">1); class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1 class="type">int y1=panel.TableY2(class="num">0)+class="num">22; class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background panel.DrawGrid(class="num">1,class="num">2,y1,class="num">3,class="num">2,class="num">18,class="num">96); class=class="str">"cmt">//--- Display tabular data in the journal panel.GridPrint(class="num">0,class="num">2); panel.GridPrint(class="num">1,class="num">2); class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=class="num">0; class=class="str">"cmt">//--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); class=class="str">"cmt">//--- Successful initialization class="kw">return(INIT_SUCCEEDED); }
◍ EA退出时把指标句柄和面板清干净
MT5 的 EA 在卸载或图表关闭时会走进 OnDeinit(),这一步最容易被忽略的是指标句柄没释放。句柄不回收,重复加载 EA 可能让终端里残留无主指标,累积到几十个后会拖慢报价刷新。 先杀掉定时器,再调用 IndicatorRelease(handle) 交还句柄。释放前用 ResetLastError() 清掉旧错误码,失败时打印具体错误号,方便在「专家日志」里定位是哪次加载出的问题。 如果 EA 自己画了仪表板面板对象,得在析构里判空后 delete。panel 指针不为 NULL 才删,否则对空指针 delete 会抛运行时异常。下面两段代码分别对应「只释放指标」和「释放指标+删面板」两种写法,直接贴进 MT5 的 EA 模板就能编译。 外汇和贵金属品种波动大、滑点频繁,EA 被强制重加载的概率不低,析构写得稳一点,能少很多莫名其妙的卡顿。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- destroy timer EventKillTimer(); class=class="str">"cmt">//--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); class=class="str">"cmt">//--- Clear all comments on the chart Comment(""); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- destroy timer EventKillTimer(); class=class="str">"cmt">//--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); class=class="str">"cmt">//--- Clear all comments on the chart Comment(""); class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it if(panel!=NULL) class="kw">delete panel; }
「把指标缓冲读进EA的正确姿势」
做价格行为辅助判定时,EA 必须先把指标缓冲区的数值抓到本地数组里,否则面板上的线状态全是空谈。下面这套取数函数可以直接塞进自定义程序里用,不需要改结构。 核心入口是 IndicatorValue(),它用 CopyBuffer 按指定句柄、偏移量和缓冲区序号取 1 个值。若返回值等于 EMPTY_VALUE,说明这一根柱子的数据没拿全,调用方要自己兜底。 LineState() 则连续取 index、index+1、index+2 三根柱的值,用相邻差值的符号给线段打状态:向上反转、向上运行、向上停顿、向下反转、向下运行、向下停顿共六类。注意它用 NormalizeDouble 按 ind_digits 精度对齐,避免浮点误差把状态判错。 编译挂到图表后,面板会实时刷这些状态和数值;外汇与贵金属波动大、滑点频繁,这类读取在跳空时可能返回异常,需在实盘前用 MT5 策略测试器跑几组极端行情验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the indicator data on the specified bar | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double IndicatorValue(class="kw">const class="type">int ind_handle,class="kw">const class="type">int index,class="kw">const class="type">int buffer_num) { class="type">class="kw">double array[class="num">1]={class="num">0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,class="num">1,array)!=class="num">1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); class="kw">return EMPTY_VALUE; } class="kw">return array[class="num">0]; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the state of the indicator line | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(class="kw">const class="type">int ind_handle,class="kw">const class="type">int index,class="kw">const class="type">int buffer_num) { class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1,class="num">2) relative to the passed index class="kw">const class="type">class="kw">double value0=IndicatorValue(ind_handle,index, buffer_num); class="kw">const class="type">class="kw">double value1=IndicatorValue(ind_handle,index+class="num">1,buffer_num); class="kw">const class="type">class="kw">double value2=IndicatorValue(ind_handle,index+class="num">2,buffer_num); class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) class="kw">return LINE_STATE_NONE; class=class="str">"cmt">//--- Line upward reversal(value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>class="num">0 && NormalizeDouble(value0-value1,ind_digits)>class="num">0) class="kw">return LINE_STATE_TURN_UP; class=class="str">"cmt">//--- Line upward direction(value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=class="num">0 && NormalizeDouble(value0-value1,ind_digits)>class="num">0) class="kw">return LINE_STATE_UP; class=class="str">"cmt">//--- Line upward stop(value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=class="num">0 && NormalizeDouble(value0-value1,ind_digits)==class="num">0) class="kw">return LINE_STATE_STOP_UP; class=class="str">"cmt">//--- Line downward reversal(value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<class="num">0 && NormalizeDouble(value0-value1,ind_digits)<class="num">0) class="kw">return LINE_STATE_TURN_DOWN; class=class="str">"cmt">//--- Line downward direction(value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=class="num">0 && NormalizeDouble(value0-value1,ind_digits)<class="num">0) class="kw">return LINE_STATE_DOWN; class=class="str">"cmt">//--- Line downward stop(value2>=value1 && value0==value1)
相对指定水平的线路状态判定
在拿到指标缓冲区的两根相邻值(index 与 index+1)后,真正有用的不是数值本身,而是它们相对某个水平线的空间关系。下面这个函数把这种关系压缩成枚举状态,方便后续逻辑直接 switch 而不用反复写浮点比较。 调用时若只传 level0,则 level1 取 EMPTY_VALUE,函数内部会把它回退成 level0,也就是用同一条水平线做上下两侧判断;传了 level1 才变成双水平带区间判定。 比较全部走 NormalizeDouble(..., ind_digits),ind_digits 一般取自指标小数位。这样能避开黄金 0.001 与美日 0.001 不同精度下的相等误判——比如 value0-level0 严格等于 0 才返回 TOUCH 类状态,否则哪怕差 1e-9 也会落进 CROSS 或 UNDER/ABOVE。 注意原文里有一处明显笔误:从上方触碰水平线本应返回 LINE_STATE_TOUCH_ABOVE,但代码写成了 LINE_STATE_TOUCH_BELOW。开 MT5 跑这套时若发现触顶信号被归到触底桶里,优先查这一行而不是怀疑自己的调用参数。外汇与贵金属杠杆高,这类状态错分可能让策略在反转点反向开仓,实盘前务必在策略测试器用历史 tick 复验。
else if(NormalizeDouble(value2-value1,ind_digits)>=class="num">0 && NormalizeDouble(value0-value1,ind_digits)==class="num">0) class="kw">return LINE_STATE_STOP_DOWN; class=class="str">"cmt">//--- Undefined state class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the state of the line relative to the specified level | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(class="kw">const class="type">int ind_handle,class="kw">const class="type">int index,class="kw">const class="type">int buffer_num,class="kw">const class="type">class="kw">double level0,class="kw">const class="type">class="kw">double level1=EMPTY_VALUE) { class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1) relative to the passed index class="kw">const class="type">class="kw">double value0=IndicatorValue(ind_handle,index, buffer_num); class="kw">const class="type">class="kw">double value1=IndicatorValue(ind_handle,index+class="num">1,buffer_num); class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) class="kw">return LINE_STATE_NONE; class=class="str">"cmt">//--- Define the second level to compare class="type">class="kw">double level=(level1==EMPTY_VALUE ? level0 : level1); class=class="str">"cmt">//--- The line is below the level(value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<class="num">0 && NormalizeDouble(value0-level0,ind_digits)<class="num">0) class="kw">return LINE_STATE_UNDER; class=class="str">"cmt">//--- The line is above the level(value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>class="num">0 && NormalizeDouble(value0-level0,ind_digits)>class="num">0) class="kw">return LINE_STATE_ABOVE; class=class="str">"cmt">//--- The line crossed the level upwards(value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=class="num">0 && NormalizeDouble(value0-level0,ind_digits)>class="num">0) class="kw">return LINE_STATE_CROSS_UP; class=class="str">"cmt">//--- The line crossed the level downwards(value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=class="num">0 && NormalizeDouble(value0-level0,ind_digits)<class="num">0) class="kw">return LINE_STATE_CROSS_DOWN; class=class="str">"cmt">//--- The line touched the level from below(value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<class="num">0 && NormalizeDouble(value0-level0,ind_digits)==class="num">0) class="kw">return LINE_STATE_TOUCH_BELOW; class=class="str">"cmt">//--- The line touched the level from above(value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>class="num">0 && NormalizeDouble(value0-level0,ind_digits)==class="num">0) class="kw">return LINE_STATE_TOUCH_BELOW; class=class="str">"cmt">//--- Line is equal to the level value(value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==class="num">0 && NormalizeDouble(value0-level0,ind_digits)==class="num">0) class="kw">return LINE_STATE_EQUALS; class=class="str">"cmt">//--- Undefined state class="kw">return LINE_STATE_NONE; }