构建K线图趋势约束模型(第九部分):多策略EA(第一部分)(基础篇)
「多策略EA的框架起点」
这一节是系列第九部分的开篇,定位在把七种已知策略塞进同一个EA骨架里跑,而不是新写一套信号逻辑。原文列了总览、魔法数字引入、趋势跟踪落地、回测与结论五个块,说明工程重点是‘多策略共存’而非‘单策略优化’。 魔法数字(magic number)在这里是关键隔离手段:不同子策略的下单用不同 magic 值标记,便于MT5后台按策略统计胜率与回撤,也避免平仓时误杀其他策略的持仓。对外汇与贵金属来说,多策略同账户运行会放大隔夜利息与滑点风险,实盘前必须用策略测试器分 magic 跑一遍。 作者给出的基础数据点:该文发布于2025年6月16日,原文交互量820次、评论0条,说明这套多策略框架当时还未经过社区充分质疑,直接照搬需自行验证。
日线定方向,小周期找切入点
做趋势约束型 EA,第一性原则是把仓位方向锁在 D1 日 K 线的主流情绪上。日线收阳(close > open)视为多头日,收阴(close < open)视为空头日,这是上层约束;真正下单信号则下沉到 M1 这类小周期,等 RSI 撞到极端值再触发,初版逻辑刻意不做复杂堆叠,方便手动跟代码。 回头看已发布的约束片段,判断函数非常直白:用 iOpen 和 iClose 抓当前日线的开收价,比大小即得 bool 标志。下面这段就是日线多空判定的最小可用核,建议直接开 MT5 新建脚本粘进去跑一遍验证。
class="type">void OnTick() { class=class="str">"cmt">// Determine current daily trend(bullish ) class="type">class="kw">double daily_open = iOpen(_Symbol, PERIOD_D1, class="num">0); class="type">class="kw">double daily_close = iClose(_Symbol, PERIOD_D1, class="num">0); class="type">bool is_bullish = daily_close > daily_open; } class="type">void OnTick() { class=class="str">"cmt">// Determine current daily trend(bearish) class="type">class="kw">double daily_open = iOpen(_Symbol, PERIOD_D1, class="num">0); class="type">class="kw">double daily_close = iClose(_Symbol, PERIOD_D1, class="num">0); class="type">bool is_bearish = daily_close < daily_open; } if (is_bullish) { class=class="str">"cmt">// Logic for bullish trend Print("The daily trend is bullish."); class=class="str">"cmt">// You can add your trading logic here, for example: class=class="str">"cmt">// if (OrderSelect(...)) { class=class="str">"cmt">/* Place buy order */ } } if (is_bearish) { class=class="str">"cmt">// Logic for bearish trend Print("The daily trend is bearish."); class=class="str">"cmt">// You can add your trading logic here, for example: class=class="str">"cmt">// if (OrderSelect(...)) { class=class="str">"cmt">/* Place sell order */ } } class="type">long position_type = PositionGetInteger(POSITION_TYPE);
class="type">void OnTick() { class=class="str">"cmt">// Determine current daily trend(bullish ) class="type">class="kw">double daily_open = iOpen(_Symbol, PERIOD_D1, class="num">0); class="type">class="kw">double daily_close = iClose(_Symbol, PERIOD_D1, class="num">0); class="type">bool is_bullish = daily_close > daily_open; } class="type">void OnTick() { class=class="str">"cmt">// Determine current daily trend(bearish) class="type">class="kw">double daily_open = iOpen(_Symbol, PERIOD_D1, class="num">0); class="type">class="kw">double daily_close = iClose(_Symbol, PERIOD_D1, class="num">0); class="type">bool is_bearish = daily_close < daily_open; } if (is_bullish) { class=class="str">"cmt">// Logic for bullish trend Print("The daily trend is bullish."); class=class="str">"cmt">// You can add your trading logic here, for example: class=class="str">"cmt">// if (OrderSelect(...)) { class=class="str">"cmt">/* Place buy order */ } } if (is_bearish) { class=class="str">"cmt">// Logic for bearish trend Print("The daily trend is bearish."); class=class="str">"cmt">// You can add your trading logic here, for example: class=class="str">"cmt">// if (OrderSelect(...)) { class=class="str">"cmt">/* Place sell order */ } } class="type">long position_type = PositionGetInteger(POSITION_TYPE);
◍ 把参数和句柄先钉死
EA 跑起来之前,先把外部可调参数摊开:超买 70、超卖 30 是 RSI 的经典边界,手数 0.1 属于轻仓试错,止损 100 点、止盈 200 点构成 1:2 的风险回报比,追踪止损 50 点用于在趋势延续时锁利。外汇与贵金属波动剧烈,这类固定点数止损在跳空时可能直接穿损,实盘前须在 MT5 策略测试器按品种点值重算。 初始化阶段只做一件事:用 iRSI 给当前品种、当前周期、收盘价算出的 RSI 建句柄。句柄若返回 INVALID_HANDLE,说明指标创建失败,OnInit 直接返回 INIT_FAILED 阻止 EA 挂载;正常则返回 INIT_SUCCEEDED。 退出时别漏了 IndicatorRelease 释放句柄,否则多次加载脚本会堆积内存占用。OnTick 里先抓日线开盘价与收盘价判断当日方向——收高于开视为偏多,收低于开视为偏空,再 CopyBuffer 取最新一根 RSI 值,失败就打印并 return,不让脏数据触发下单。
class="kw">input class="type">class="kw">double RSI_Overbought = class="num">70.0; class=class="str">"cmt">// RSI overbought level class="kw">input class="type">class="kw">double RSI_Oversold = class="num">30.0; class=class="str">"cmt">// RSI oversold level class="kw">input class="type">class="kw">double Lots = class="num">0.1; class=class="str">"cmt">// Lot size class="kw">input class="type">class="kw">double StopLoss = class="num">100; class=class="str">"cmt">// Stop Loss in points class="kw">input class="type">class="kw">double TakeProfit = class="num">200; class=class="str">"cmt">// Take Profit in points class="kw">input class="type">class="kw">double TrailingStop = class="num">50; class=class="str">"cmt">// Trailing Stop in points class=class="str">"cmt">// Global variables class="type">class="kw">double rsi_value; class="type">int rsi_handle; CTrade trade; class=class="str">"cmt">// Declare an instance of the CTrade class class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Create an RSI indicator handle rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE); if (rsi_handle == INVALID_HANDLE) { Print("Failed to create RSI indicator handle"); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED); } 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">// Release the RSI indicator handle IndicatorRelease(rsi_handle); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Determine current daily trend(bullish or bearish) class="type">class="kw">double daily_open = iOpen(_Symbol, PERIOD_D1, class="num">0); class="type">class="kw">double daily_close = iClose(_Symbol, PERIOD_D1, class="num">0); class="type">bool is_bullish = daily_close > daily_open; class="type">bool is_bearish = daily_close < daily_open; class=class="str">"cmt">// Get the RSI value for the current bar class="type">class="kw">double rsi_values[]; if (CopyBuffer(rsi_handle, class="num">0, class="num">0, class="num">1, rsi_values) <= class="num">0) { Print("Failed to get RSI value"); class="kw">return; } rsi_value = rsi_values[class="num">0]; class=class="str">"cmt">// Close open positions if the trend changes
「逆势持仓清场与信号开仓的循环逻辑」
这段逻辑解决一个实盘常见坑:当趋势判定翻转(如 bullish 转 bearish)而账户里还躺着反向单,必须先按 ticket 精准平仓,而不是靠 PositionClose(_Symbol) 偷懒——后者在同源多单时会漏单。 循环从 PositionsTotal()-1 递减到 0,用 PositionSelect(PositionGetSymbol(i)) 定位第 i 个持仓,再读 POSITION_TYPE 与 POSITION_TICKET。若持仓是多单且当前 is_bearish,或空单且 is_bullish,直接 trade.PositionClose(ticket) 平掉;外汇与贵金属杠杆高,逆势单过夜可能扩大浮亏,这种清理要快。 开仓侧只在 PositionsTotal()==0 时动手:bullish 且 rsi_value < RSI_Oversold 读 SYMBOL_ASK 开多,sl=price-StopLoss*_Point、tp=price+TakeProfit*_Point;bearish 且 rsi_value > RSI_Overbought 读 SYMBOL_BID 开空,方向相反。把 StopLoss / TakeProfit 调成 500 / 1000 这类点数,回测里能直观看到盈亏比变化。 尾部还有第二个同构循环做 trailing stop:重新遍历持仓,取 POSITION_PRICE_OPEN 与 POSITION_SL,按 POSITION_TYPE 分支处理。复制下面代码到 MT5 策略测试器,把 RSI 周期设 14、超买超卖 70/30,跑 EURUSD 小时图大概率能看到逆势单被扫掉后再开新单的节奏。
for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { if (PositionSelect(PositionGetSymbol(i))) class=class="str">"cmt">// Corrected usage { class="type">long position_type = PositionGetInteger(POSITION_TYPE); class="type">class="kw">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">// Get the position ticket if ((position_type == POSITION_TYPE_BUY && is_bearish) || (position_type == POSITION_TYPE_SELL && is_bullish)) { trade.PositionClose(ticket); class=class="str">"cmt">// Use the class="type">class="kw">ulong variable directly } } } class=class="str">"cmt">// Check for buy condition(bullish trend + RSI oversold) if (is_bullish && rsi_value < RSI_Oversold) { class=class="str">"cmt">// No open positions? Place a buy order if (PositionsTotal() == class="num">0) { class="type">class="kw">double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class="type">class="kw">double sl = price - StopLoss * _Point; class="type">class="kw">double tp = price + TakeProfit * _Point; class=class="str">"cmt">// Open a buy order trade.Buy(Lots, _Symbol, price, sl, tp, "TrendConstraintExpert Buy"); } } class=class="str">"cmt">// Check for sell condition(bearish trend + RSI overbought) if (is_bearish && rsi_value > RSI_Overbought) { class=class="str">"cmt">// No open positions? Place a sell order if (PositionsTotal() == class="num">0) { class="type">class="kw">double price = SymbolInfoDouble(_Symbol, SYMBOL_BID); class="type">class="kw">double sl = price + StopLoss * _Point; class="type">class="kw">double tp = price - TakeProfit * _Point; class=class="str">"cmt">// Open a sell order trade.Sell(Lots, _Symbol, price, sl, tp, "TrendConstraintExpert Sell"); } } class=class="str">"cmt">// Apply trailing stop for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { if (PositionSelect(PositionGetSymbol(i))) class=class="str">"cmt">// Corrected usage { class="type">class="kw">double price = PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">double stopLoss = PositionGetDouble(POSITION_SL); class="type">class="kw">double current_price; if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
多单与空单的跟进止损分支
上面这段逻辑把双向持仓的 trailing stop 拆成了两个独立判断。多头用 BID 价比对开仓价,空头用 ASK 价,方向不同取价方式不能反,否则回测里会出现止损永远不触发或者瞬间被扫的怪象。 以多头为例:当 current_price 与记录价 price 的差额超过 TrailingStop 个 _Point 时,才进入修改判断。这里 price 通常是持仓期间某次记录的最低/最高价,TrailingStop 是交易者自己设的步长,比如 30 点。 修改条件要求新止损位比原 stopLoss 更优(多头时原止损更小),才调用 trade.PositionModify 把止损挪到 current_price - TrailingStop*_Point,止盈保持不变。空头分支镜像处理,用 current_price + TrailingStop*_Point,且允许 stopLoss==0 时直接初始化跟进。 外汇与贵金属杠杆高,这类自动改止损在滑点行情中可能成交在预期外价位,建议先在 MT5 策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍,观察 TrailingStop 从 15 调到 50 点时的回撤变化。
current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID); if (current_price - price > TrailingStop * _Point) { if (stopLoss < current_price - TrailingStop * _Point) { trade.PositionModify(PositionGetInteger(POSITION_TICKET), current_price - TrailingStop * _Point, PositionGetDouble(POSITION_TP)); } } } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { current_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if (price - current_price > TrailingStop * _Point) { if (stopLoss > current_price + TrailingStop * _Point || stopLoss == class="num">0) { trade.PositionModify(PositionGetInteger(POSITION_TICKET), current_price + TrailingStop * _Point, PositionGetDouble(POSITION_TP)); } } } }
◍ 把数学原理落成可跑的算法
外汇与贵金属交易天然带高杠杆与双向波动风险,任何策略都只是提高概率,而非消除亏损可能。 文中列出的七类策略并非凭空而来,它们各自锚定了某位经济学家或实战派交易者提出的数学框架——比如均值回归对应正态分布假设,突破类则依赖波动率聚类现象。先把原理吃透,后续写代码才不会只抄个形态外壳。 真正分水岭在第二步:当你能口述清楚某策略的进出场条件,再用 MQL5 把它写成 EA,验证成本就只剩电费。很多人口中的「策略懂了」其实停在直觉层,一碰到滑点和点差重写就露怯。 所以这一节的价值不在罗列名字,而是提醒你——原理记熟后,编码实现才是把主观经验变成可回测对象的唯一路径,打开 MT5 新建一个空 EA 试着落一条规则,比反复读图文有用。