用于在EA交易中包含指标的现成模板(第一部分):振荡指标·进阶篇
(2/3)· 14个振荡指标的输入变量与生命周期管理,省去反复翻文档的重复劳动
枚举线与面板变量的底层骨架
这段声明把价格相对基准线的七种状态先固化成枚举:上穿、下穿、从下触、从上触、等于,外加单纯的上方与下方。实盘里你若想区分“刺穿”和“贴着走”,靠的就是 LINE_STATE_CROSS_UP 与 LINE_STATE_TOUCH_BELOW 这类细分标签,而不是肉眼估。 紧跟着的输入参数把 ATR 周期写死成 14,这是 MT5 里 iATR 的默认回看窗口;全局里 handle 初始化为 INVALID_HANDLE,意味着指标句柄在 OnInit 之前一律无效,漏了这步就会在后续 CopyBuffer 直接返回 -1。 面板相关变量里 mouse_bar_index 负责记录鼠标取数那根 K 线的序号,CDashboard *panel 则用空指针占位,等资源创建后再挂实体。开 MT5 把这段贴进 EA 头部,编译能过则说明你的枚举与全局声明没有作用域冲突。
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">//--- input parameters input class="type">uint InpPeriod = class="num">14; class=class="str">"cmt">/* ATR Period */ 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">// ATR 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初始化里把周期归零的坑」
写EA时有个容易忽略的兼容点:很多指标在输入周期值为0时会回退到默认周期,EA最好也照做。把平滑周期参数设成0,就应该让内部走和标准指标一致的默认逻辑,否则面板数据和原指标对不上。 另外部分指标周期填1并不计算,最短有效周期是2;不同品种的小数位数(Digits)也不一样,初始化阶段就要把这些都处理掉,不然跨品种加载可能显示错位。 下面这段OnInit就是按上述规则写的:周期小于1统一当14处理,顺手用Digits()锁定小数位,再建ATR句柄。若句柄创建失败直接INIT_FAILED并打错误码,方便在MT5日志里抓。 [CODE] //+------------------------------------------------------------------+
| // | Expert initialization function |
|---|
//+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("ATR(%lu)",period); ind_digits=Digits(); //--- Create indicator handle ResetLastError(); handle=iATR(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+
| // | Expert initialization function |
|---|
//+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("ATR(%lu)",period); ind_digits=Digits(); //--- Create indicator handle ResetLastError(); handle=iATR(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text
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 if necessary period=class="type">int(InpPeriod<class="num">1 ? class="num">14 : InpPeriod); class=class="str">"cmt">//--- Set the indicator name and the number of decimal places ind_title=StringFormat("ATR(%lu)",period); ind_digits=Digits(); class=class="str">"cmt">//--- Create indicator handle ResetLastError(); handle=iATR(Symbol(),PERIOD_CURRENT,period); 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">//--- Successful initialization class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ 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 if necessary period=class="type">int(InpPeriod<class="num">1 ? class="num">14 : InpPeriod); class=class="str">"cmt">//--- Set the indicator name and the number of decimal places ind_title=StringFormat("ATR(%lu)",period); ind_digits=Digits(); class=class="str">"cmt">//--- Create indicator handle ResetLastError(); handle=iATR(Symbol(),PERIOD_CURRENT,period); 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">199,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); class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
◍ 面板表格的布局与初始化落点
在指标初始化阶段,先把交易品种和周期拼成标题写进面板:Symbol() 取当前品种,Period() 转枚举后从下标 7 截掉前缀,拼成 "EURUSD, H1" 这类显示串。 随后建两张表——ID 0 放 K 线数据,ID 1 放指标数据。DrawGrid(0,2,20,6,2,18,97) 表示表 0 从第 2 列、Y=20 像素起画,6 行 2 列,列宽 18、行高 97;表 1 的 Y 坐标不写死,而是取表 0 的底边 TableY2(0) 再加 22 像素,避免两张表叠在一起。 最后把两张表内容用 GridPrint 打到日志便于排查,鼠标游标柱索引初设为 0,并立刻用 DrawData(0,TimeCurrent()) 把当前柱刷到面板上。外汇与贵金属波动剧烈、杠杆风险高,这段代码仅解决显示层,不含任何方向判断。
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">97); 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">97); 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退出时怎么清干净指标和面板
EA从图表卸载时,OnDeinit()是最后一道关口。这里不把申请的资源还回去,MT5终端里会留下悬空句柄和孤儿对象,多次加载卸载后可能拖慢终端响应。 第一段代码只做了三件事:杀掉定时器、释放指标句柄、清空图表上的Comment文字。IndicatorRelease(handle)若返回false,会用PrintFormat把错误码打进日志,方便你定位是句柄失效还是指标已被占用。 第二段补了面板对象的回收。panel指针若不为NULL,直接delete panel即可让自定义面板从图表消失。外汇与贵金属杠杆高,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; }
「用 CopyBuffer 抓取指标缓冲区的实战取法」
在 MT5 里读任意指标线,核心就一个函数:CopyBuffer()。它把指定指标句柄、缓冲区编号和起始位置的数据搬进你的 double 数组,失败则返回 EMPTY_VALUE。函数有三种重载:按柱索引+数量、按起始时间+数量、按起止时间区间,我们盯盘 EA 里只用第一种——按柱索引取单点,逻辑最直。 为什么不全量拉一段数组再算偏移?那样要把结果塞进内存按索引换算,反而把“线态监测”搞复杂。直接取指定索引的值,再取另一个值做对比,既省内存又方便。比如取当前柱和前两根柱共三个值,足够拼出简单线形;用归一化差值和零比,就能判断升降。 线相对于水平位的判定,可以写个函数吃两个相邻柱的值,再和传入的 level0 / level1 比。level1 若留 EMPTY_VALUE,就只和 level0 比;若给了具体数,则 index+1 柱比 level1、index 柱比 level0,这样能抓两线交叉。 把这些函数原样塞进 EA,就能在 OnChartEvent 里随光标移动刷面板。编译后挂上默认参数 ATR,面板铭牌会随鼠标跳数;TestOscillationATR.mq5 是可照抄的样例。外汇贵金属波动剧烈,这类读取仅作状态参考,不代表方向确定性。
class="type">int CopyBuffer( class="type">int indicator_handle, class=class="str">"cmt">// indicator handle class="type">int buffer_num, class=class="str">"cmt">// indicator buffer index class="type">int start_pos, class=class="str">"cmt">// starting point class="type">int count, class=class="str">"cmt">// amount to copy class="type">class="kw">double buffer[] class=class="str">"cmt">// array the data to be copied to ); class="type">int CopyBuffer( class="type">int indicator_handle, class=class="str">"cmt">// indicator handle class="type">int buffer_num, class=class="str">"cmt">// indicator buffer index class="type">class="kw">datetime start_time, class=class="str">"cmt">// starting date class="type">int count, class=class="str">"cmt">// amount to copy class="type">class="kw">double buffer[] class=class="str">"cmt">// array the data to be copied to ); class="type">int CopyBuffer( class="type">int indicator_handle, class=class="str">"cmt">// indicator handle class="type">int buffer_num, class=class="str">"cmt">// indicator buffer index class="type">class="kw">datetime start_time, class=class="str">"cmt">// starting date class="type">class="kw">datetime stop_time, class=class="str">"cmt">// end time class="type">class="kw">double buffer[] class=class="str">"cmt">// array the data to be copied to ); 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) {
◍ 用三段取值判定指标线姿态
判定指标线当前处于反转、延续还是停滞,核心是把相邻三根 K 线上的缓冲值拉出来比对。下面这段函数一次取 index、index+1、index+2 三个位置的数值,任一为 EMPTY_VALUE 就直接返回未定义状态,避免脏数据污染后续逻辑。 比大小之前必须用 NormalizeDouble 按指标小数位 ind_digits 对齐,否则浮点误差会让 value2-value1 在理论等于 0 时算出 1e-16 这类极小正数,把「停滞」误判成「方向」。向上反转要求 value2>value1 且 value0>value1,即中间那根是最低点;向上延续则是 value2<=value1 且 value0>value1。 向下一侧完全对称:value2<value1 且 value0<value1 为向下反转,value2>=value1 且 value0<value1 为向下延续,两者差值归一后等于 0 则归入对应方向的停滞态。外汇与贵金属杠杆高、滑点突发,这类线态判定只作概率参考,实盘须配合止损。 LineStateRelative 在函数尾部接着收口,多传 level0 与可选 level1 两个阈值,就能把「线在不在某区间」也编进状态机,省得在主循环里再写一层判断。
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) 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; }