掌握市场动态:创建有关支撑与阻力位策略的EA·进阶篇
(2/3)·从策略逻辑到EA落地的关键一跃,多数手动交易者卡在代码实现这一步
不少交易者能凭眼力在图表上圈出支撑阻力,却始终没把这套判断交给EA跑历史与实时验证。手动盯盘容易漏掉刺穿瞬间的信号,而策略若只停在脑内,便无从谈风险管理与概率优势。
「在MT5里搭支撑阻力EA的骨架」
把支撑阻力策略自动化,第一步是在 MT5 里建 EA 工程。按 F4 或点工具栏 IDE 图标进 MetaEditor,Ctrl+N 新建文件,向导里选 Expert Advisor (template),名称处可加反斜杠指定子目录(如 Experts\MySR),连点下一步到完成,空模板就位。 代码开头先 #include <Trade/Trade.mqh>,预处理器会把它替换成标准目录 MQL5\Include 下的 Trade.mqh 内容,从而拿到 CTrade 类。全局域里声明四个数组:两个 double 动态数组存最高/最低价(空大小,随意扩),两个静态双精度数组存识别出的支撑/阻力坐标(固定第二维为 2)。再用 #define 给阻力线起名 RESISTANCE LEVEL、染红、加前缀 R,支撑位同理,避免多 EA 同账户时线名打架。 OnInit 里两件实事:ArraySetAsSeries 把价格数组转成时间序列(索引 0 是最新 bar,早年数据甩到高索引),举例 2020–2024 的年数据会被翻成 2024 在 0、2020 在 4;ArrayResize 给四个数组各开 50 个元素——我们后面只留前 10 个极值,多开没意义。 OnDeinit 做收尾:动态数组用 ArrayFree 释放内存,静态的支撑阻力数组不能用 ArrayFree,得调 ArrayRemove(数组,0,总元素数) 清空。 核心在 OnTick——它每个报价跳一下就触发,但找支撑阻力只需每根 bar 算一次。用 iBars(品种,周期) 取 currBars,静态 prevBars 存上根 bar 数;若 currBars==prevBars 直接 return,不等则更新 prevBars 继续。可见 bar 才参与:ChartGetInteger 拿可见 K 线数转 int 赋给 visible_bars,外层 for 从 i=1(成型中当前 bar 不算)跑到 visible_bars-1,内层 j 从 i+10 扫到倒数第二根,专门隔开 10 根以上才比同价,防连续 K 线凑数。 比价时 high_diff = NormalizeDouble(MathAbs(iHigh外-iHigh内)/_Point,0),≤10 点判为阻力,同理判支撑。先 ArrayCopy+ArraySort 升序排高/低价,ArrayRemove 截前 10 高、后 10 低当极端点,未处理的最低价截取稍绕一点。外汇贵金属波动剧烈,这种历史极值区被触碰后反转只是概率倾向,实盘前务必开 MT5 用可见 bar 跑一遍印日志核对。
把阻力支撑画上图并管住重复信号
识别完最高价 K 线后,下一步是把价位落到图表上。两个自定义函数分工:draw_S_R_Level 只画水平线,draw_S_R_Level_Point 在线上补箭头和价格标签。水平线用 OBJ_HLINE,靠 ObjectFind 返回负整数判断对象不存在再去创建,已存在就只改价格重绘。 draw_S_R_Level_Point 收 7 个参数(名称、价格、时间、箭头码、方向、颜色、角度),用 OBJ_ARROW 建点,再把名称拼上时间价格做成悬停提示,另建 OBJ_TEXT 写价格当注释。箭头码直接吃 Wingdings 预定义字符,文中例子用 218 画出了阻力点但无描述,拼文本后才显示价格。 水平失效靠可见 K 线范围判断。复制 visibleHighs 数组,遍历比对阻力线价格,无匹配就视为超出邻近区,调 deleteLevel 用 ObjectDelete 删对象。支撑同理反向。 突破信号要防重复触发。声明静态 double 存信号价,阻力存在时先比对「信号价 != 阻力价」才继续;用前一根 K 线加最新报价判突破,满足则打印卖出信号、obj_Trade.Sell 开仓,再把信号价更新成该阻力位,同价位再破也不会二次报单。支撑反向处理。 外汇与贵金属杠杆高,阻力支撑被破只是概率事件,实盘前请在 MT5 策略测试器用历史数据跑一遍上述逻辑验证信号频率。
class="macro">#include <Trade/Trade.mqh> CTrade obj_Trade; class="type">class="kw">double pricesHighest[], pricesLowest[]; class="type">class="kw">double resistanceLevels[class="num">2], supportLevels[class="num">2]; class="macro">#define resLine "RESISTANCE LEVEL" class="macro">#define colorRes clrRed class="macro">#define resline_prefix "R" class="macro">#define supLine "SUPPORT LEVEL" class="macro">#define colorSup clrBlue class="macro">#define supline_prefix "S" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(){ class=class="str">"cmt">//--- ArraySetAsSeries(pricesHighest,true); ArraySetAsSeries(pricesLowest,true); class=class="str">"cmt">// define the size of the arrays ArrayResize(pricesHighest,class="num">50); ArrayResize(pricesLowest,class="num">50); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason){ class=class="str">"cmt">//--- ArrayFree(pricesHighest);
◍ 动态数组与静态数组的清理分水岭
在支撑阻力扫描的收尾阶段,动态数组和静态数组不能用同一套释放逻辑。pricesLowest 是动态申请的,直接 ArrayFree 清空内存即可;而 resistanceLevels 与 supportLevels 若以静态方式分配,调用 ArrayFree 会在编译或运行期报错,原文注释里就明确标了“cannot be used for static allocated array”。 替代做法是 ArrayRemove(resistanceLevels,0,WHOLE_ARRAY) 与 ArrayRemove(supportLevels,0,WHOLE_ARRAY),从索引 0 开始删到 WHOLE_ARRAY,等于把静态数组逻辑长度归零但保留缓冲区。开 MT5 把这两行换着注释掉编译一次,能直观看到静态数组走 ArrayFree 的报错信息。 OnTick 里的节流也值得抄:用 static int prevBars 缓存上一次 iBars(_Symbol,_Period) 返回值,若等于当前 currBars 直接 return,避免每跳都重算。visible_bars 取自 ChartGetInteger(0,CHART_VISIBLE_BARS),外循环 i 从 1 跑到 visible_bars-1,内循环 j 从 i+10 起步,diff_i_j 硬编码为 10 根.bar 的错位窗口。 阻力判定看 high_diff:用 NormalizeDouble(MathAbs(high-high_j)/_Point,0) 把价差折成整数点,<=10 点就判 is_resistance;支撑同理看 low_diff<=10。外汇与贵金属波动大、点值敏感,10 点阈值在黄金 M1 上可能过于密集,在欧美 H4 上又可能漏掉,实盘前务必按品种重调。
ArrayFree(pricesLowest); class=class="str">"cmt">//ArrayFree(resistanceLevels); // cannot be used for class="kw">static allocated array class=class="str">"cmt">//ArrayFree(supportLevels); // cannot be used for class="kw">static allocated array ArrayRemove(resistanceLevels,class="num">0,WHOLE_ARRAY); ArrayRemove(supportLevels,class="num">0,WHOLE_ARRAY); } class="type">void OnTick(){ class=class="str">"cmt">//--- } class="type">int currBars = iBars(_Symbol,_Period); class="kw">static class="type">int prevBars = currBars; if (prevBars == currBars) class="kw">return; prevBars = currBars; class="type">int visible_bars = (class="type">int)ChartGetInteger(class="num">0,CHART_VISIBLE_BARS); for (class="type">int i=class="num">1; i<=visible_bars-class="num">1; i++){ ... } class="type">class="kw">double open = iOpen(_Symbol,_Period,i); class="type">class="kw">double high = iHigh(_Symbol,_Period,i); class="type">class="kw">double low = iLow(_Symbol,_Period,i); class="type">class="kw">double close = iClose(_Symbol,_Period,i); class="type">class="kw">datetime time = iTime(_Symbol,_Period,i); class="type">int diff_i_j = class="num">10; for (class="type">int j=i+diff_i_j; j<=visible_bars-class="num">1; j++){ ... } class=class="str">"cmt">//Print in the outer loop Print(":: BAR NO: ",i); class=class="str">"cmt">//Print in the inner loop Print("BAR CHECK NO: ",j); class="type">class="kw">double open_j = iOpen(_Symbol,_Period,j); class="type">class="kw">double high_j = iHigh(_Symbol,_Period,j); class="type">class="kw">double low_j = iLow(_Symbol,_Period,j); class="type">class="kw">double close_j = iClose(_Symbol,_Period,j); class="type">class="kw">datetime time_j = iTime(_Symbol,_Period,j); class=class="str">"cmt">// CHECK FOR RESISTANCE class="type">class="kw">double high_diff = NormalizeDouble((MathAbs(high-high_j)/_Point),class="num">0); class="type">bool is_resistance = high_diff <= class="num">10; class=class="str">"cmt">// CHECK FOR SUPPORT class="type">class="kw">double low_diff = NormalizeDouble((MathAbs(low-low_j)/_Point),class="num">0); class="type">bool is_support = low_diff <= class="num">10; if (is_resistance){ Print("RESISTANCE AT BAR ",i," (",high,") & ",j," (",high_j,"), Pts = ",high_diff); ...
「剔除重复阻力位的提前跳出」
在抓取可视K线的高低价数组后,代码先释放旧数组再重新拷贝:从当前图表第1根(跳过最新一根)起取 visible_bars 根 High/Low 填入 pricesHighest / pricesLowest。 随后对两个数组做升序排序并打印,再用 ArrayRemove 截尾——pricesHighest 删掉索引10之后的全部元素,pricesLowest 删掉前 visible_bars-10 个,只保留各10个极值用于匹配。 内层循环逐个比对 pricesHighest[k] 是否等于候选阻力 high 与 high_j,命中则置 matchFound_high1 / matchFound_high2 并打印日志。若两者同时命中,再判断 resistanceLevels[0]==high 或 resistanceLevels[1]==high_j:若是同一阻力位,打印跳过提示并将 stop_processing 置 true 后 break,避免重复更新。 外层以 for(i=1; i<=visible_bars-1 && !stop_processing; i++) 驱动,stop_processing 为真时立即终止整轮扫描。实盘跑这段时,把 visible_bars 调到 200 以上,更容易观察到极值裁剪后匹配效率的变化;外汇与贵金属波动剧烈,这种层级判定仅作概率参考,杠杆品种风险高。
} ArrayFree(pricesHighest); ArrayFree(pricesLowest); class="type">int copiedBarsHighs = CopyHigh(_Symbol,_Period,class="num">1,visible_bars,pricesHighest); class="type">int copiedBarsLows = CopyLow(_Symbol,_Period,class="num">1,visible_bars,pricesLowest); ArrayPrint(pricesHighest); ArrayPrint(pricesLowest); class=class="str">"cmt">// sort the array in ascending order ArraySort(pricesHighest); ArraySort(pricesLowest); ArrayPrint(pricesHighest); ArrayPrint(pricesLowest); ArrayRemove(pricesHighest,class="num">10,WHOLE_ARRAY); ArrayRemove(pricesLowest,class="num">0,visible_bars-class="num">10); for (class="type">int k=class="num">0; k<ArraySize(pricesHighest); k++){ ... } class="type">bool matchFound_high1 = false, matchFound_low1 = false; class="type">bool matchFound_high2 = false, matchFound_low2 = false; if (pricesHighest[k]==high){ matchFound_high1 = true; Print("> RES H1(",high,") FOUND @ ",k," (",pricesHighest[k],")"); } if (pricesHighest[k]==high_j){ matchFound_high2 = true; Print("> RES H2(",high_j,") FOUND @ ",k," (",pricesHighest[k],")"); } if (matchFound_high1 && matchFound_high2){ if (resistanceLevels[class="num">0]==high || resistanceLevels[class="num">1]==high_j){ Print("CONFIRMED BUT This is the same resistance level, skip updating!"); stop_processing = true; class=class="str">"cmt">// Set the flag to stop processing break; class=class="str">"cmt">// stop the inner loop prematurely } ... } class="type">bool stop_processing = false; class=class="str">"cmt">// Flag to control outer loop class=class="str">"cmt">//... for (class="type">int i=class="num">1; i<=visible_bars-class="num">1 && !stop_processing; i++){ ... } else { Print(" ++++++++++ RESISTANCE LEVELS CONFIRMED @ BARS ",i,
阻力位落地与图形标注的两条路径
阻力识别段会把当前高点 high 与次高点 high_j 塞进 resistanceLevels 数组的前两个槽位,再调 ArrayPrint 把数值打到日志,便于在 MT5 Experts 标签里核对。随后连续调用 draw_S_R_Level 与 draw_S_R_Level_Point,把水平线和带箭头的标注画到图表上;其中箭头对象用了 218 这个 arrowcode,direction 传 -1 表示锚定在价格下方,angle 参数 90 控制文本角度。 draw_S_R_Level 先 ObjectFind 查重,没有就 ObjectCreate 一条 OBJ_HLINE,设置颜色和线宽(示例里 width=5);已存在则只改 OBJPROP_PRICE,避免重复建对象造成闪烁。末尾 ChartRedraw(0) 强制重绘当前图表。 draw_S_R_Level_Point 用 StringConcatenate 把时间与价格拼进对象名,创建 OBJ_ARROW 后按 direction 决定 ANCHOR_TOP 或 ANCHOR_BOTTOM,字号固定 10。外汇与贵金属波动剧烈,这类自动画线仅作结构参考,阻力被突破的概率随会话波动率变化,实盘须自行验证。
"(",high,") & ",j,"(",high_j,")");
resistanceLevels[class="num">0] = high;
resistanceLevels[class="num">1] = high_j;
ArrayPrint(resistanceLevels);
...
}
draw_S_R_Level(resLine,high,colorRes,class="num">5);
draw_S_R_Level_Point(resline_prefix,high,time,class="num">218,-class="num">1,colorRes,class="num">90);
draw_S_R_Level_Point(resline_prefix,high,time_j,class="num">218,-class="num">1,colorRes,class="num">90);
stop_processing = true; class=class="str">"cmt">// Set the flag to stop processing
break;
class="type">void draw_S_R_Level(class="type">class="kw">string levelName,class="type">class="kw">double price,class="type">color clr,class="type">int width){
if (ObjectFind(class="num">0,levelName) < class="num">0){
ObjectCreate(class="num">0,levelName,OBJ_HLINE,class="num">0,TimeCurrent(),price);
ObjectSetInteger(class="num">0,levelName,OBJPROP_COLOR,clr);
ObjectSetInteger(class="num">0,levelName,OBJPROP_WIDTH,width);
}
else {
ObjectSetDouble(class="num">0,levelName,OBJPROP_PRICE,price);
}
ChartRedraw(class="num">0);
}
class="type">void draw_S_R_Level_Point(class="type">class="kw">string objName,class="type">class="kw">double price,class="type">class="kw">datetime time,
class="type">int arrowcode,class="type">int direction,class="type">color clr,class="type">class="kw">double angle){
class=class="str">"cmt">//objName = " ";
StringConcatenate(objName,objName," @ \nTime: ",time,"\nPrice: ",DoubleToString(price,_Digits));
if (ObjectCreate(class="num">0,objName,OBJ_ARROW,class="num">0,time,price)) {
ObjectSetInteger(class="num">0,objName,OBJPROP_ARROWCODE,arrowcode);
ObjectSetInteger(class="num">0,objName,OBJPROP_COLOR,clr);
ObjectSetInteger(class="num">0,objName,OBJPROP_FONTSIZE,class="num">10);
if (direction > class="num">0) ObjectSetInteger(class="num">0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
if (direction < class="num">0) ObjectSetInteger(class="num">0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
}
class="type">class="kw">string prefix = resline_prefix;
class="type">class="kw">string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";◍ 给支撑阻力线挂上带价标签
在 MT5 里给水平线加文字注释,关键不是画线本身,而是把价格和方向锚定清楚。下面这段逻辑先拼出对象名,再用 ObjectCreate 以 OBJ_TEXT 类型在指定 time/price 处落一个文本对象,失败就跳过。 文本属性里 FONTSIZE 写死 10,ANGLE 由外部 angle 参数控制,COLOR 用传入的 clr。direction>0 时锚点设 ANCHOR_LEFT,direction<0 时设 ANCHOR_BOTTOM,两种都往文本前塞四个空格做缩进,避免压到线身。 标签内容用 resline_prefix 拼换行和 DoubleToString(price,_Digits) 出来的报价,例如 EURUSD 上可能显示「R1(1.08452)」。最后 ChartRedraw(0) 强制重绘,否则子窗口里文字可能滞后一两帧才显示。 支撑识别那段用 pricesLowest 数组比对 low,命中就置 matchFound_low1=true,这是后续决定是否标注「SUPPORT」分支的开关。外汇和贵金属波动大,这类自动标注只作辅助,信号失效概率不低,实盘前先在策略测试器跑一遍看重绘开销。
class="type">class="kw">string objNameDescription = objName + txt; if (ObjectCreate(class="num">0,objNameDescription,OBJ_TEXT,class="num">0,time,price)) { class=class="str">"cmt">// ObjectSetString(class="num">0,objNameDescription,OBJPROP_TEXT, "" + txt); ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_COLOR,clr); ObjectSetDouble(class="num">0,objNameDescription,OBJPROP_ANGLE, angle); ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_FONTSIZE,class="num">10); if (direction > class="num">0) { ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT); ObjectSetString(class="num">0,objNameDescription,OBJPROP_TEXT, " " + txt); } if (direction < class="num">0) { ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM); ObjectSetString(class="num">0,objNameDescription,OBJPROP_TEXT, " " + txt); } } ChartRedraw(class="num">0); } class="type">class="kw">string prefix = resline_prefix; class="type">class="kw">string txt = "\n"+prefix+ "(" + DoubleToString(price,_Digits) + ")"; class="type">class="kw">string objNameDescription = objName + txt; if (ObjectCreate(class="num">0,objNameDescription,OBJ_TEXT,class="num">0,time,price)) { class=class="str">"cmt">// ObjectSetString(class="num">0,objNameDescription,OBJPROP_TEXT, "" + txt); ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_COLOR,clr); ObjectSetDouble(class="num">0,objNameDescription,OBJPROP_ANGLE, angle); ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_FONTSIZE,class="num">10); if (direction > class="num">0) { ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT); ObjectSetString(class="num">0,objNameDescription,OBJPROP_TEXT, " " + txt); } if (direction < class="num">0) { ObjectSetInteger(class="num">0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM); ObjectSetString(class="num">0,objNameDescription,OBJPROP_TEXT, " " + txt); } } else if (is_support){ class=class="str">"cmt">//Print("SUPPORT AT BAR ",i," (",low,") & ",j," (",low_j,"), Pts = ",low_diff); for (class="type">int k=class="num">0; k<ArraySize(pricesLowest); k++){ if (pricesLowest[k]==low){ matchFound_low1 = true;