利用解析入场点为指标的技术创建新的交易策略·综合运用
◍ 用 MACD 阈值卡住多空触发点
这段逻辑把 MACD 柱值夹在 ±0.0015 到 ±0.0035 的窄区间里做信号过滤,区间外的动量被认为太弱或太极端,不进场。 多头分支在 macd_data[0] 落在 [0.0015, 0.0035] 时,以 ask 开买、bid-sl 挂止损、ask+tp 挂止盈;空头分支对称处理,macd_data[0] 在 [-0.0035, -0.0015] 时以 bid 开卖。 每次 new CDeal 后都用 CheckPointer 确认对象有效,再 Deals.Add 入列,并调 IndicatorsStatic.SaveNewValues(Deals.Total()-1) 把当前指标快照存进对应成交槽。 后面接了 Chaikin 振荡器(iChaikin)做二次确认:买需 cho_data[1]-cho_data[0] < 0(动能拐头向下但仍处正 MACD 区),卖需差值为正。外汇与贵金属杠杆高,这种窄阈值策略在震荡市可能频繁假触发,建议先上 MT5 用策略测试器跑 EURUSD 的 M15 验证命中率。
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); } if(macd_data[class="num">0]<=(-class="num">0.0015) && macd_data[class="num">0]>=(-class="num">0.0035)) { 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="type">int cho; 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; if(timeframe==TimeFrame3) cho=handle; class="type">class="kw">double cho_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 || CopyBuffer(cho,class="num">0,class="num">1,class="num">2,cho_data)<class="num">2) class="kw">return; if(macd_data[class="num">0]>=class="num">0.0015 && macd_data[class="num">0]<=class="num">0.0035 && (cho_data[class="num">1]-cho_data[class="num">0])<class="num">0) { 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); } if(macd_data[class="num">0]<=(-class="num">0.0015) && macd_data[class="num">0]>=(-class="num">0.0035) && (cho_data[class="num">1]-cho_data[class="num">0])>class="num">0) { 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="type">int cci; name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name;
多指标共振的开仓判定逻辑
下面这段 MT5 EA 片段展示了如何用 CCI 配合 MACD、CHO、ATR、Force 做多指标共振过滤。核心思路是:MACD 柱值被限制在一个窄区间(0.0015~0.0035 或 -0.0035~-0.0015),同时 CHO 与 CCI 出现反向斜率,才触发挂单。 先看指标句柄获取与缓冲拷贝。iCCI 创建 CCI 句柄后,若失败或无法加入缓冲就直接返回 false;当周期等于 TimeFrame3 时把句柄存到 cci 变量。随后用 CopyBuffer 一次性拉取 atr、force、macd、cho、cci 各 1~2 根历史值,任一拷贝长度不达标就退出,避免脏数据进场。 开多条件写得很死:macd_data[0] 必须在 0.0015 到 0.0035 之间,cho_data[1]-cho_data[0] 小于 0(CHO 向下),cci_data[1]-cci_data[0] 大于 0(CCI 向上)。满足后 new 一个 CDeal 买价单,sl/tp 用 ask±变量算好,指针有效就加入 Deals 并保存指标值。 开空完全对称:macd_data[0] 在 -0.0015 到 -0.0035,CHO 向上、CCI 向下,生成卖价单。外汇与贵金属波动剧烈,这种窄区间共振在震荡市可能频繁信号、趋势市可能踏空,上 MT5 用策略测试器跑 EURUSD 15M 看命中率再决定是否放宽 MACD 区间。
class="type">int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price); if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) class="kw">return false; if(timeframe==TimeFrame3) cci=handle; class="type">class="kw">double cci_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 || CopyBuffer(cho,class="num">0,class="num">1,class="num">2,cho_data)<class="num">2 || CopyBuffer(cci,class="num">0,class="num">1,class="num">2,cci_data)<class="num">2) class="kw">return; if(macd_data[class="num">0]>=class="num">0.0015 && macd_data[class="num">0]<=class="num">0.0035 && (cho_data[class="num">1]-cho_data[class="num">0])<class="num">0 && (cci_data[class="num">1]-cci_data[class="num">0])>class="num">0) { 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); } if(macd_data[class="num">0]<=(-class="num">0.0015) && macd_data[class="num">0]>=(-class="num">0.0035) && (cho_data[class="num">1]-cho_data[class="num">0])>class="num">0 && (cci_data[class="num">1]-cci_data[class="num">0])<class="num">0) { 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); }
「把四指标策略塞进EA跑回测」
策略定了四个指标做决策,再用 ATR 管止损止盈,下一步就是写成 EA 丢进测试器看真实表现。这一阶段不碰资金管理,所有订单固定手数,输入参数里先把指标周期、手数、报酬风险比都暴露出来方便调。 EA 骨架很直接:全局里放交易类实例、指标句柄、记录已处理柱线和上次交易日的辅助变量;OnInit 初始化指标并赋初值,OnDeinit 释放句柄。OnTick 开头先判新柱,只在开盘 10 秒内且未超最大时延才允许开仓,等于一根 K 线只吃一个信号。 指标取数若报错直接退出;力度指数不满足滤波就等下一根。多头信号来了先查持仓——空单先平,多单浮亏则忽略。参数算完发单,空头对称处理。完整代码随文附上,复制就能编。 为避开曲线拟合,测试拉长到 2015.01.01–2017.01.12,初始本金 10000 美元、1 手交易。结果净值获利 74.8%,最大余额回撤 12.4%、净值回撤 23.8%,共 44 笔(多空各 22),但盈利单占比仅 18.2%——根子在那 15:1 的报酬风险比定太高,留了明显优化口子。外汇与贵金属杠杆品种回撤波动剧烈,这类回测仅作逻辑验证,实盘仍属高风险。
class=class="str">"cmt">//--- 输入参数 input class="type">class="kw">double Lot = class="num">0.1 ; input class="type">class="kw">double Reward_Risk = class="num">15.0 ; input ENUM_TIMEFRAMES ATR_TimeFrame = PERIOD_M5 ; input class="type">int ATR_Period = class="num">288 ; input class="type">class="kw">string s1 = "CCI" ; class=class="str">"cmt">//--- input ENUM_TIMEFRAMES CCI_TimeFrame = PERIOD_D1 ; input class="type">uint CCI_Period = class="num">14 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input class="type">class="kw">string s2 = "Chaikin" ; class=class="str">"cmt">//--- input ENUM_TIMEFRAMES Ch_TimeFrame = PERIOD_D1 ; input class="type">uint Ch_Fast_Period = class="num">6 ; input class="type">uint Ch_Slow_Period = class="num">28 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input class="type">class="kw">string s3 = "Force Index" ; class=class="str">"cmt">//---
◍ 指标句柄的初始化与周期边界
把 ATR、Force、MACD、Chaikin、CCI 五个指标一次性挂到 EA 上,核心在 OnInit 里用 iATR / iForce / iMACD / iChaikin / iCCI 拿句柄,任一句柄返回 INVALID_HANDLE 就直接 INIT_FAILED,MT5 会拒绝加载。 输入参数里 Force 默认 PERIOD_M5、周期 56、SMA、VOLUME_TICK;MACD 默认快 12 / 慢 26 / 信号 9、收盘价。这些数字直接决定后续信号计算的采样粒度,调参时先动 Force_Period 比动 MACD 更能改变趋势敏感度。 最后一段用 fmax / fmin 在四个时间帧里挑出 MaxPeriod 与 MinPeriod,这是给后面控制 K 线同步用的——若你切到 M15 图表但指标最小帧是 M5,同步逻辑就得靠这两个值卡边界。外汇与贵金属杠杆高,句柄初始化失败往往意味着品种不可交易或时段无数据,上真实账户前先在策略测试器跑一遍。
input ENUM_TIMEFRAMES Force_TimeFrame = PERIOD_M5 ; input class="type">uint Force_Period = 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 s4 = "MACD" ; class=class="str">"cmt">//--- input ENUM_TIMEFRAMES MACD_TimeFrame = PERIOD_M5 ; input class="type">uint MACD_Fast = class="num">12 ; input class="type">uint MACD_Slow = class="num">26 ; input class="type">uint MACD_Signal = class="num">9 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ; class="type">int OnInit() { class=class="str">"cmt">//--- last_bar=class="num">0; last_deal=class="num">0; class=class="str">"cmt">//--- atr=iATR(_Symbol,ATR_TimeFrame,ATR_Period); if(atr==INVALID_HANDLE) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- force=iForce(_Symbol,Force_TimeFrame,Force_Period,Force_Method,Force_Volume); if(force==INVALID_HANDLE) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- macd=iMACD(_Symbol,MACD_TimeFrame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price); if(macd==INVALID_HANDLE) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- cho=iChaikin(_Symbol,Ch_TimeFrame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume); if(cho==INVALID_HANDLE) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- cci=iCCI(_Symbol,CCI_TimeFrame,CCI_Period,CCI_Price); if(cci==INVALID_HANDLE) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- MaxPeriod=fmax(Force_TimeFrame,MACD_TimeFrame); MaxPeriod=fmax(MaxPeriod,Ch_TimeFrame); MaxPeriod=fmax(MaxPeriod,CCI_TimeFrame); MinPeriod=fmin(Force_TimeFrame,MACD_TimeFrame); MinPeriod=fmin(MinPeriod,Ch_TimeFrame); MinPeriod=fmin(MinPeriod,CCI_TimeFrame); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); }
指标句柄释放与多周期触发门槛
EA 卸载时若不清掉自定义指标句柄,MT5 会在策略测试器里留下僵尸资源,反复加载回测时内存占用可能线性爬升。OnDeinit 里对 atr、force、macd、cho、cci 五个句柄逐个判 INVALID_HANDLE 再 IndicatorRelease,是低成本防漏的标准动作。 OnTick 的入口过滤决定了实盘触发频率。代码用 cur_bar<=last_bar 封掉同根 K 线重复计算,并用 (cur_time-cur_bar)>10 把「距当前小周期 bar 开盘超 10 秒」的延迟 tick 拒之门外;cur_max<=last_deal 则卡住大周期未换柱时的无效扫描。这三道门槛能把无谓的 CopyBuffer 调用砍掉大半。 数据拷贝环节有个隐藏坑:cho 和 cci 取的是 2 根柱(索引 1 和 0),其余取 1 根。若 CopyBuffer 返回值小于 2 直接 return,说明大周期数据没齐,此时硬算价差 (cho_data[1]-cho_data[0]) 会越界或失真。 force 的归一化用 _Point*1000 当步长,MathAbs 后小于 0.01 就退出——相当于力指标绝对值不足 1 个点(标准点对大多数品种已极小)时不参与信号。macd 则先除 _Point*50 取整再乘回,把 0.0015~0.0035 区间当作「弱多头动量带」,叠上 cho 向下、cci 向上的背离形态才放行。外汇与贵金属杠杆高,这类多指标共振仅提高概率,不代表方向必现。
class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- if(atr!=INVALID_HANDLE) IndicatorRelease(atr); class=class="str">"cmt">//--- if(force==INVALID_HANDLE) IndicatorRelease(force); class=class="str">"cmt">//--- if(macd==INVALID_HANDLE) IndicatorRelease(macd); class=class="str">"cmt">//--- if(cho==INVALID_HANDLE) IndicatorRelease(cho); class=class="str">"cmt">//--- if(cci==INVALID_HANDLE) IndicatorRelease(cci); } class="type">void OnTick() { class=class="str">"cmt">//--- class="type">class="kw">datetime cur_bar=(class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,MinPeriod,SERIES_LASTBAR_DATE); class="type">class="kw">datetime cur_max=(class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,MaxPeriod,SERIES_LASTBAR_DATE); class="type">class="kw">datetime cur_time=TimeCurrent(); if(cur_bar<=last_bar || (cur_time-cur_bar)>class="num">10 || cur_max<=last_deal) class="kw">return; last_bar=cur_bar; class="type">class="kw">double atrs[]; class="type">class="kw">double force_data[]; class="type">class="kw">double macd_data[]; class="type">class="kw">double cho_data[]; class="type">class="kw">double cci_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 || CopyBuffer(cho,class="num">0,class="num">1,class="num">2,cho_data)<class="num">2 || CopyBuffer(cci,class="num">0,class="num">1,class="num">2,cci_data)<class="num">2) { class="kw">return; } class="type">class="kw">double force_Step=_Point*class="num">1000; if(MathAbs(NormalizeDouble(force_data[class="num">0]/force_Step,class="num">0)*force_Step)<=class="num">0.01) 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 && (cho_data[class="num">1]-cho_data[class="num">0])<class="num">0 && (cci_data[class="num">1]-cci_data[class="num">0])>class="num">0) { if(PositionSelect(_Symbol)) { class="kw">switch((class="type">int)PositionGetInteger(POSITION_TYPE)) { case POSITION_TYPE_BUY: if(PositionGetDouble(POSITION_PROFIT)<=class="num">0) class="kw">return; } } } }
「双向触发下的平仓与挂单逻辑」
这段逻辑把 MACD 区间与 CHO、CCI 的斜率组合当作双向触发器。当 MACD 落在 -0.0015 到 -0.0035 之间、CHO 差值由正转负、CCI 差值反向时,系统判定为偏空信号;对应地,前文 MACD 在 +0.0015~+0.0035 且 CHO 上行、CCI 下行时走偏多分支。 持仓处理用 switch 按 POSITION_TYPE 分流:若已持有同向单且浮动利润 ≤0 则直接 return 不动作;若持反向单则 Trade.PositionClose(_Symbol) 先平掉。止损距离取 2 倍 ATR 与经纪商 SYMBOL_TRADE_STOPS_LEVEL 点值的较大者,再 NormalizeDouble 到品种精度。 止盈按 Reward_Risk*(stops+ask-bid) 推算,买单价差补偿用 ask-bid。外汇与贵金属杠杆高,这类自动平仓重开逻辑在滑点扩大时可能频繁触发,实盘前务必在 MT5 策略测试器用历史数据跑一遍验证触发频率。
break; case POSITION_TYPE_SELL: Trade.PositionClose(_Symbol); break; } } last_deal=cur_max; 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 ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double sl=NormalizeDouble(stops,_Digits); class="type">class="kw">double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); class="type">class="kw">double SL=NormalizeDouble(bid-sl,_Digits); class="type">class="kw">double TP=NormalizeDouble(ask+tp,_Digits); if(!Trade.Buy(Lot,_Symbol,ask,SL,TP,"New Strategy")) Print("Error of open BUY ORDER "+Trade.ResultComment()); } if(macd_data[class="num">0]<=( -class="num">0.0015) && macd_data[class="num">0]>=( -class="num">0.0035) && (cho_data[class="num">1]-cho_data[class="num">0])>class="num">0 && (cci_data[class="num">1]-cci_data[class="num">0])<class="num">0) { if(PositionSelect(_Symbol)) { class="kw">switch((class="type">int)PositionGetInteger(POSITION_TYPE)) { case POSITION_TYPE_SELL: if(PositionGetDouble(POSITION_PROFIT)<=class="num">0) class="kw">return; break; case POSITION_TYPE_BUY: Trade.PositionClose(_Symbol); break; } } last_deal=cur_max; 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 ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double sl=NormalizeDouble(stops,_Digits); class="type">class="kw">double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
◍ 卖单止损止盈的挂单计算
在 MT5 的 EA 逻辑里,卖单的止损和止盈不能直接用裸价格,必须经过点位偏移再按品种精度规整。下面这段片段把 ask 加上 sl 点数、bid 减去 tp 点数,再用 NormalizeDouble 对齐 _Digits,避免报价小数位不匹配导致的下单失败。 double SL=NormalizeDouble(ask+sl,_Digits); double TP=NormalizeDouble(bid-tp,_Digits); if(!Trade.Sell(Lot,_Symbol,bid,SL,TP,"New Strategy")) Print("Error of open SELL ORDER "+Trade.ResultComment()); 这里 SL 放在 ask 上方、TP 放在 bid 下方,符合卖单盈利空间向下的结构。若 Trade.Sell 返回 false,直接打印 ResultComment 能拿到经纪商返回的具体拒单原因,比盲猜高效。 实际验证时把 sl、tp 设成 300、600 这类常见点数,在 XAUUSD 的 5 位平台上跑回测,能看到 NormalizeDouble 后价格尾数恒为经纪商 _Digits 位,外汇与贵金属杠杆高,点位计算错一位就可能触发异常平仓,建议先开策略测试器单步跟一遍。
class="type">class="kw">double SL=NormalizeDouble(ask+sl,_Digits); class="type">class="kw">double TP=NormalizeDouble(bid-tp,_Digits); if(!Trade.Sell(Lot,_Symbol,bid,SL,TP,"New Strategy")) Print("Error of open SELL ORDER "+Trade.ResultComment());
把入场逻辑拆成指标再用起来
把入场条件反向解析成独立指标,是这套思路真正能落地的关键。原文用三年回测说明,按此法搭建的策略有长期盈利的可能,但标准 MetaTrader 自带指标生成的信号,和文献里写的经典形态常常差得很远,不能拿书本结论直接套。 技术价值在于不绑定某个指标:任意用户指标或其变体都能接进来,用同一套解析框架评估信号质量。外汇与贵金属波动受杠杆和消息驱动,高风险,回测结论只代表历史样本,实盘前务必在 MT5 用自己品种重跑。 想验证就开 MT5 建个自定义指标,把现有入场规则逐条输出成缓冲数组,再比对着图表看信号偏移在哪。
「随包附带的源码与回测素材」
这套四阶段策略不是只给思路,作者把可跑的 MT5 工程也一并打了包。New_Strategy_Gizlyk.zip 里是 4 个分阶段 EA(NewStrategy1~4.mq5)加一个统测用的 NewStrategy_Final.mq5,另有 16 个 .mqh 类库,分别管 ADX、Alligator、MACD 三大指标的状态、历史与静态统计。 Common.zip 则塞了 9 份 html 测试报告,覆盖 2016–2017 年、盈利/风险比分别取 1/1 与 15/1 的各阶段结果,以及最终统测页。两个压缩包体量分别为 1374.2 KB 与 1455.62 KB,直接丢进 MT5 的 MQL5 目录就能编译。 回测报告里 1/1 与 15/1 两种盈亏比对照,能直观看出冒同样风险时激进入场和收紧离场的权益曲线差异。外汇与贵金属杠杆高、滑点跳空频繁,报告结论只代表历史样本,实盘复制前务必用自己的品种重跑一遍。