利用解析入场点为指标的技术创建新的交易策略·进阶篇
(2/3)· 100% 交易者都想稳定盈利但仅 5% 做到,把入场信号翻成指标来反向验证或许是破局切口
◍ 多周期指标的参数入口怎么摆
把鳄鱼、CCI、Chaikin、Force 这几类指标直接做成 input 参数块,开 MT5 挂 EA 时就能在属性框里实时改周期,不用回编译器重编。 CCI 这里给了三档周期:14、28、56,价格源用 PRICE_TYPICAL(典型价)。Chaikin 则是快/慢线各三套:3/14、6/28、12/56,均线方法锁 MODE_EMA,成交量取 VOLUME_TICK。 下面这段就是参数声明原文,逐行看:input ENUM_APPLIED_PRICE 定义鳄鱼用的价格(中位数价);CCI_Period1~3 是三周期;CCI_Price 指定典型价。Chaikin 的 Ch_Fast/Slow_Period 对应三组合;Ch_Method 与 Ch_Volume 固定算法与量源。Force 只露了 Period1=14、Period2=28 两档,后续小节会接上取值逻辑。 外汇与贵金属杠杆高、滑点跳空频繁,参数只是过滤器,信号失效时止损要先于调参。
input ENUM_APPLIED_PRICE Alligator_Price = PRICE_MEDIAN ; input class="type">class="kw">string s5 = "CCI" ; class=class="str">"cmt">//--- input class="type">uint CCI_Period1 = class="num">14 ; input class="type">uint CCI_Period2 = class="num">28 ; input class="type">uint CCI_Period3 = class="num">56 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input class="type">class="kw">string s6 = "Chaikin" ; class=class="str">"cmt">//--- input class="type">uint Ch_Fast_Period1 = class="num">3 ; input class="type">uint Ch_Slow_Period1 = class="num">14 ; input class="type">uint Ch_Fast_Period2 = class="num">6 ; input class="type">uint Ch_Slow_Period2 = class="num">28 ; input class="type">uint Ch_Fast_Period3 = class="num">12 ; input class="type">uint Ch_Slow_Period3 = class="num">56 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input class="type">class="kw">string s7 = "Force Index" ; class=class="str">"cmt">//--- input class="type">uint Force_Period1 = class="num">14 ; input class="type">uint Force_Period2 = class="num">28 ;
「双指标参数与初始化骨架」
这段声明把力道指数(Force Index)和三档 MACD 的输入参数直接摊开。Force_Period3 取 56,均线算法用 SMA,成交量源锁定 VOLUME_TICK;MACD 则铺了三组快慢线——最慢一档慢线周期到 104、信号线 36,明显是想在日线级别以外叠一层更钝的动量滤波。 OnInit 里先做两个状态归零:last_bar 和 last_closed_deal 都置 0,避免挂上 EA 时沿用上一次运行的残留计数。随后 new 出 CArrayObj 与 CDealsToIndicators 两个对象,并用 CheckPointer 判空,任一指针无效就回 INIT_FAILED,这是 MT5 里防止后续裸指针调用的标准写法。 atr 句柄用 iATR(_Symbol, TimeFrame1, ATR_Period) 取,若返回 INVALID_HANDLE 同样走失败分支。开 MT5 把这段贴进 EA 模板,改 TimeFrame1 和 ATR_Period 就能验证不同周期下句柄获取是否稳定,外汇与贵金属品种波动大,参数误配可能让 ATR 滤波失效,实盘前务必在策略测试器跑一轮。
input class="type">uint Force_Period3 = class="num">56 ; input ENUM_MA_METHOD Force_Method = MODE_SMA ; input ENUM_APPLIED_VOLUME Force_Volume = VOLUME_TICK ; input class="type">class="kw">string s8 = "MACD" ; class=class="str">"cmt">//--- input class="type">uint MACD_Fast1 = class="num">12 ; input class="type">uint MACD_Slow1 = class="num">26 ; input class="type">uint MACD_Signal1 = class="num">9 ; input class="type">uint MACD_Fast2 = class="num">24 ; input class="type">uint MACD_Slow2 = class="num">52 ; input class="type">uint MACD_Signal2 = class="num">18 ; input class="type">uint MACD_Fast3 = class="num">48 ; input class="type">uint MACD_Slow3 = class="num">104 ; input class="type">uint MACD_Signal3 = class="num">36 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ; class="type">int OnInit() { class=class="str">"cmt">//--- last_bar=class="num">0; last_closed_deal=class="num">0; class=class="str">"cmt">//--- Deals = new CArrayObj(); if(CheckPointer(Deals)==POINTER_INVALID) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- IndicatorsStatic = new CDealsToIndicators(); if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- atr=iATR(_Symbol,TimeFrame1,ATR_Period); if(atr==INVALID_HANDLE)
多周期指标批量挂载的实现细节
这段逻辑出现在 EA 初始化尾部,先尝试返回失败状态,随后对三个时间周期分别调用 AddIndicators,最后返回 INIT_SUCCEEDED。也就是说,指标容器若在任一周期挂载出错,初始化就会中断,MT5 终端会在日志抛出对应 handle 错误。 AddIndicators 函数内部先用 CheckPointer 判断 IndicatorsStatic 是否为空指针,无效时才 new 一个 CDealsToIndicators 实例;若二次分配仍无效直接 return false。接着用 StringSubstr(EnumToString(timeframe),7) 截取周期名(如 M15 取自 PERIOD_M15 的第 7 字符起),拼到每个指标名后面做可视化区分。 每个周期里实际挂了 3 组 ADX(周期参数 ADX_Period1/2/3)、3 组 Alligator(颌/齿/唇三组不同周期与位移)、2 组 MACD(快慢与信号线参数各两套)。任何一组 AddXXX 返回 false,函数即退出,EA 不会带残缺指标运行。 开 MT5 把这段塞进 OnInit 之后的私有方法,调 ADX_Period1 等外部输入,就能在 EURUSD 的 M5/M15/H1 上验证多周期指标是否齐刷刷出现在子窗口。外汇与贵金属杠杆高,指标齐了也不代表信号可靠,仍需结合价格行为判断。
class="kw">return INIT_FAILED; class=class="str">"cmt">//--- AddIndicators(TimeFrame1); AddIndicators(TimeFrame2); AddIndicators(TimeFrame3); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">bool AddIndicators(ENUM_TIMEFRAMES timeframe) { if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) { IndicatorsStatic = new CDealsToIndicators(); if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) class="kw">return false; } class="type">class="kw">string tf_name=StringSubstr(EnumToString(timeframe),class="num">7); class="type">class="kw">string name="ADX("+IntegerToString(ADX_Period1)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period1, name)) class="kw">return false; name="ADX("+IntegerToString(ADX_Period2)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period2, name)) class="kw">return false; name="ADX("+IntegerToString(ADX_Period3)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period3, name)) class="kw">return false; name="Alligator("+IntegerToString(JAW_Period1)+","+IntegerToString(TEETH_Period1)+","+IntegerToString(LIPS_Period1)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period1, JAW_Shift1, TEETH_Period1, TEETH_Shift1, LIPS_Period1, LIPS_Shift1, Alligator_Method, Alligator_Price, name)) class="kw">return false; name="Alligator("+IntegerToString(JAW_Period2)+","+IntegerToString(TEETH_Period2)+","+IntegerToString(LIPS_Period2)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period2, JAW_Shift2, TEETH_Period2, TEETH_Shift2, LIPS_Period2, LIPS_Shift2, Alligator_Method, Alligator_Price, name)) class="kw">return false; name="Alligator("+IntegerToString(JAW_Period3)+","+IntegerToString(TEETH_Period3)+","+IntegerToString(LIPS_Period3)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period3, JAW_Shift3, TEETH_Period3, TEETH_Shift3, LIPS_Period3, LIPS_Shift3, Alligator_Method, Alligator_Price, name)) class="kw">return false; name="MACD("+IntegerToString(MACD_Fast1)+","+IntegerToString(MACD_Slow1)+","+IntegerToString(MACD_Signal1)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast1, MACD_Slow1, MACD_Signal1, MACD_Price, name)) class="kw">return false; name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name)) class="kw">return false;
◍ 多周期指标句柄的批量注册写法
这段逻辑在一个时间框架下把 MACD、CCI、Force、Chaikin 四个系列的指标句柄统一挂进静态指标容器,任一创建失败就直接 return false,保证后续引用不会拿到野指针。 MACD 走的是封装好的 AddMACD,名字拼成 "MACD(f,s,sig) TF";CCI 用 iCCI 拿 handle 后交给 AddOneBuffer,周期参数 CCI_Period1/2/3 各开一条缓冲,默认价格类型由 CCI_Price 决定。 Force 指标连续建了三个周期(Force_Period1~3),方法 Force_Method 与成交量类型 Force_Volume 复用同一组入参;Chaikin 则把快/慢周期反过来拼进名字 "CHO(slow,fast) TF",同样三档周期各注册一次。 外汇与贵金属杠杆高,这类多指标堆叠在秒级周期上可能拖慢 MT5 报价响应,实盘前建议在策略测试器里用 2023 年 XAUUSD 的 M15 跑一遍句柄数量压力检查。
name="MACD("+IntegerToString(MACD_Fast3)+","+IntegerToString(MACD_Slow3)+","+IntegerToString(MACD_Signal3)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast3, MACD_Slow3, MACD_Signal3, MACD_Price, name)) class="kw">return false; name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name; class="type">int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price); if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; name="CCI("+IntegerToString(CCI_Period2)+") "+tf_name; handle = iCCI(_Symbol, timeframe, CCI_Period2, CCI_Price); if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; handle = iCCI(_Symbol, timeframe, CCI_Period3, CCI_Price); name="CCI("+IntegerToString(CCI_Period3)+") "+tf_name; if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; handle = iForce(_Symbol, timeframe, Force_Period1, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period1)+") "+tf_name; if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; handle = iForce(_Symbol, timeframe, Force_Period2, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period2)+") "+tf_name; if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period3)+") "+tf_name; if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; name="CHO("+IntegerToString(Ch_Slow_Period1)+","+IntegerToString(Ch_Fast_Period1)+") "+tf_name; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period1, Ch_Slow_Period1, Ch_Method, Ch_Volume); if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name; if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period3, Ch_Slow_Period3, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period3)+","+IntegerToString(Ch_Fast_Period3)+") "+tf_name; if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; class="kw">return true; }
「用成交记录驱动双向挂单的 OnTick 逻辑」
这段 OnTick 的核心不是每次报价都下单,而是先扫一遍 Deals 容器里的新增成交,把已平仓的索引跳过,只对未平仓的 deal 调用 Tick() 更新状态。变量 last_closed_deal 记录了上次已处理到的平仓位置,循环从它往后走,遇到 IsClosed() 就前移索引并 continue,遇到未平仓才把 found 置真并继续刷新。 扫完成交后,代码取当前图表最后一根 K 线的开盘时间 cur_bar,以及服务器时间 cur_time。若 cur_bar 等于上一根记录的 last_bar,或者距新 K 线已过去超过 10 秒((cur_time-cur_bar)>10),直接 return,避免在同一根 bar 内重复发单。这个 10 秒阈值是个实打实的可调参数,你可以按品种波动节奏改小或改大。 接着用 CopyBuffer 从 atr 指标句柄取 1 根历史 ATR 值(偏移 1、取 1 个)。若返回值 <=0 就退出。止损距离 stops 取 2 倍 ATR 与经纪商 SYMBOL_TRADE_STOPS_LEVEL 点数限制中的较大者,再按 _Digits 规范化成 sl。止盈 tp 则按 Reward_Risk 倍数乘(stops+点差)得出,外汇与贵金属杠杆高、滑点跳空频繁,实际成交距离可能偏离计算值,需先在策略测试器跑一遍。 最后分别 new 一个 BUY 和一个 SELL 的 CDeal 对象,买单价位为 ask、SL=bid-sl、TP=ask+tp;卖单对称。指针有效就加入 Deals 并调用 IndicatorsStatic.SaveNewValues 按 Total()-1 存指标快照。把这段代码贴进你的 EA 的 OnTick,改一下 Reward_Risk 和 ATR 周期,就能在 MT5 里观察双向预设单如何随每根新 K 线重生。
class="type">void OnTick() { class=class="str">"cmt">//--- class="type">int total=Deals.Total(); CDeal *deal; class="type">bool found=false; for(class="type">int i=last_closed_deal;i<total;i++) { deal = Deals.At(i); if(CheckPointer(deal)==POINTER_INVALID) class="kw">continue; if(!found) { if(deal.IsClosed()) { last_closed_deal=i; class="kw">continue; } else found=true; } deal.Tick(); } class=class="str">"cmt">//--- class="type">class="kw">datetime cur_bar=(class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE); class="type">class="kw">datetime cur_time=TimeCurrent(); if(cur_bar==last_bar || (cur_time-cur_bar)>class="num">10) class="kw">return; class="type">class="kw">double atrs[]; if(CopyBuffer(atr,class="num">0,class="num">1,class="num">1,atrs)<=class="num">0) class="kw">return; last_bar=cur_bar; class="type">class="kw">double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double stops=MathMax(class="num">2*atrs[class="num">0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point); class="type">class="kw">double sl=NormalizeDouble(stops,_Digits); class="type">class="kw">double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-class="num">1); deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-class="num">1); class="kw">return; }
四轮滤波把EURUSD区间榨出盈利区
回测区间选得够长才有客观性。我拿 EURUSD 从 2016-01-01 到 2017-01-10 这段跑, 里面既有单向趋势也有双向摆动和横盘, 这样模板在任意走势里都可能出利润。每阶段都用利润/风险 1:1 和 15:1 各跑一次, 前者看方向概率, 后者看运动力度, 参数存成 set 文件反复加载。 第一阶段 M5 的力度指数在 -0.01 到 0.01 之间时, 买卖交易利润都贴着零轴下滑, 所有参数组合都复现了这个现象, 大概率是因为这区段波动性缺失。EA 里直接加全局句柄并在 OnTick 舍入后拦截: 力度的归一值绝对值 ≤0.01 就 return 不开单。 第二阶段 MACD 冒出盈利区, 15:1 图更明显, 说明那一段信号有潜力; 第三阶段 D1 的 Chaikin 振荡器显示数值降多头利润增、值增空头利润增; 第四阶段 D1 的 CCI 周期 14 时多空利润随指标同向增长最猛, 三个子区间都见到。这四层过滤依次挂进全局变量、AddIndicators 和 OnTick。 外汇和贵金属杠杆高, 上述依赖关系只是样本内统计倾向, 实盘前请在 MT5 用同区间重跑验证。下面这段是阶段四落地的核心片段, 注意力度过滤的舍入和 MACD 区间判断写法。
class="type">int force; handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume); if(timeframe==TimeFrame1) force=handle; class="type">class="kw">double atrs[]; class="type">class="kw">double force_data[]; if(CopyBuffer(atr,class="num">0,class="num">1,class="num">1,atrs)<=class="num">0 || CopyBuffer(force,class="num">0,class="num">1,class="num">1,force_data)<=class="num">0) class="kw">return; class=class="str">"cmt">// 加载指标数据时的一些错误 last_bar=cur_bar; class="type">class="kw">double d_Step=_Point*class="num">1000; if(MathAbs(NormalizeDouble(force_data[class="num">0]/d_Step,class="num">0)*d_Step)<=class="num">0.01) class="kw">return; class=class="str">"cmt">// 力度指标过滤 class="type">int macd; name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name; if(timeframe==TimeFrame1) { if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name, macd)) class="kw">return false; } else { if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name)) class="kw">return false; } class="type">class="kw">double macd_data[]; if(CopyBuffer(atr,class="num">0,class="num">1,class="num">1,atrs)<=class="num">0 || CopyBuffer(force,class="num">0,class="num">1,class="num">1,force_data)<=class="num">0 || CopyBuffer(macd,class="num">0,class="num">1,class="num">1,macd_data)<=class="num">0) class="kw">return; class="type">class="kw">double macd_Step=_Point*class="num">50; macd_data[class="num">0]=NormalizeDouble(macd_data[class="num">0]/macd_Step,class="num">0)*macd_Step; if(macd_data[class="num">0]>=class="num">0.0015 && macd_data[class="num">0]<=class="num">0.0035) {