创建动态多货币对EA(第二部分):投资组合多元化与优化(基础篇)
「把多品种塞进一个EA的底层逻辑」
在 MT5 里做动态多货币对 EA,核心不是把十几个图表挂在一起,而是用一个 Expert Advisor 实例通过 Symbol() 遍历与 iSelect 机制去轮询账户内可交易品种。这样回测时只需开一个品种上下文,就能在 OnTick 里切到目标品种取价,避免多图表资源抢占。 原文给出的基准测试数据:单实例轮询 8 个主流外汇对(EURUSD、GBPUSD、USDJPY、AUDUSD、USDCAD、NZDUSD、USDCHF、XAUUSD)在 2024.01–2024.12 的 M1 数据上跑一遍组合逻辑,Agent 报告耗时约 865 秒(约 14 分 25 秒),说明品种数量线性增加时 CPU 占用可控,但 XAUUSD 因点值浮动大需单独处理保证金系数。 外汇与贵金属自带高杠杆与跳空风险,多品种同开不等于风险分散,组合内相关性(如 EURUSD 与 GBPUSD 常同向)可能让回撤集中爆发,实盘前务必用 MT5 的「净值曲线 + 品种贡献」报告核对。
◍ 为什么单一策略扛不住突变市
专业交易里最磨人的不是找信号,而是让组合在行情拐弯时不崩。很多人把仓位压在单一货币对或一条策略上,一旦相关性突然抬升、波动放大,回撤就会超出预期。叠加杠杆滥用,几笔同源亏损能直接打穿风险预算。 缺乏严格分散和优化的账户,净值曲线往往呈现一段盈利接一段大回吐,交易者随后容易用情绪单去填坑,进一步放大波动。要长期活下来,得有一套能在非相关资产间系统性分配风险调整后收益的框架。 把组合优化、跨资产分散和振荡器确认突破捆在一起,是应对上述问题的量化思路。在多个货币对上跑突破,既吃高概率动量,又靠不相关市场摊薄风险;振荡器过滤掉一部分假突破,无效交易次数下降,组合对波动的韧性也更强。
区间突破 EA 的双条件触发与结构实现
这套 EA 的核心逻辑只在每天 UTC+2 的 10:00–12:00 框出一个价格区间,记录最高价与最低价。12:00 之后任意报价,若卖价刺破区间高点且随机振荡器 ≤20,才发买入;若买价跌破区间低点且振荡器 ≥80,才发卖出。用振荡器卡超卖 / 超买,是为了过滤掉延伸过度的假突破,给反向动量留空间。 输入参数里,In_Lot 固定 0.01 手,TakeProfit 500 点、StopLoss 1000 点,TrailingStop 70 点动态追踪;KPeriod=20、upper_level=80。RangeStart=600 分钟(10:00)、RangeDuration=120 分钟、RangeClose=1200 分钟(12:00),到点停止算区间只监测突破。 Ranger 结构体把每个品种的 start_time、end_time、close_time、high、low(初始化 999999)、b_entry、b_high_breakout、b_low_breakout 打包。rangeArray 存多个品种实例,OnInit 里按逗号拆 Symbols、建 iStochastic 句柄,句柄失败直接 INIT_FAILED 退出。 OnTick 先把报价塞进 prevTick,活跃时段内用最高 ask 抬 high、最低 bid 压 low,并置 b_entry。收盘后满足三条件之一就调 CalculateRange 重算。checkBreak 只在时间过 end_time 且 b_entry 激活时评估:高位突破要 ask>high 且振荡器<20,低位突破要 bid<low 且振荡器>80,过 CLots 手数校验再下单。 CHLots 按经纪商最小 / 最大 / 步长修手数,MathFloor(lots/step)*step 对齐步长,超最大返回 false 拦单。Trailer 按价格位移和区间百分比重算止损锁利。外汇与贵金属杠杆高,实盘前务必在 MT5 策略测试器用 XAUUSD 跑通这套时间窗与振荡器阈值。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Dyna MP.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
「多品种区间突破的初始化骨架」
这段初始化逻辑把 XAUUSD、GBPUSD、USDCAD、USDJPY 四个品种塞进同一个 EA 里跑,用逗号分割输入字符串后,靠 StringSplit 得到 symb_List 数组,Num_symbs 直接等于数组大小,实测四个品种时返回 4。 随后对 rangeArray、prevTick、currTick、handles 按品种数动态扩容,每个品种独立持有一份 RANGER 结构和 tick 快照,互不串数据。 RANGER 结构体里 low 默认赋 999999、high 默认 0,相当于先把区间上下沿放到极端值,等后续 K 线回看时再被真实高低点覆盖;b_entry、b_high_breakout、b_low_breakout 三个布尔位初始全 false,意味着开盘前任何突破信号都处于未触发状态。 外汇与贵金属杠杆高、滑点跳空频繁,这种多品种同跑的 EA 在实盘前务必在 MT5 策略测试器里用逐笔 tick 回测一遍,重点看 handles 是否正确拿到每个品种的 Stochastic 句柄。
class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Trade/Trade.mqh> CTrade trade; enum Signal_Breakout{ Normal_Signal, Reversed_Signal, }; input group "--------------General Inputs--------------" input class="type">class="kw">string Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY"; input Signal_Breakout BreakOutMode = Reversed_Signal; input class="type">class="kw">double In_Lot = class="num">0.01; input class="type">int TakeProfit = class="num">500; class="type">class="kw">double StopLoss = class="num">500; input class="type">bool TrailYourStop = false; input class="type">int TrailingStop = class="num">50; class=class="str">"cmt">// Stochastic input class="type">int KPeriod = class="num">21; input class="type">int upprer_level = class="num">80; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global vars | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int handles[]; class="type">class="kw">double bufferM[]; class="type">int RangeStart = class="num">600; class="type">int RangeDuration = class="num">120; class="type">int RangeClose = class="num">1200; class="type">int Num_symbs = class="num">0; class="type">class="kw">string symb_List[]; class="type">class="kw">string Formatted_Symbs[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Ranger Global Vars | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct RANGER{ class="type">class="kw">datetime start_time; class="type">class="kw">datetime end_time; class="type">class="kw">datetime close_time; class="type">class="kw">double high; class="type">class="kw">double low; class="type">bool b_entry; class="type">bool b_high_breakout; class="type">bool b_low_breakout; RANGER() : start_time(class="num">0), end_time(class="num">0), close_time(class="num">0), high(class="num">0), low(class="num">999999), b_entry(false), b_high_breakout(false), b_low_breakout(false) {}; }; RANGER rangeArray[]; class="type">MqlTick prevTick[], currTick[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(){ class="type">class="kw">string separator = ","; class="type">class="kw">ushort usprtr; usprtr = StringGetCharacter(separator, class="num">0); StringSplit(Symbols, usprtr, symb_List); Num_symbs = ArraySize(symb_List); ArrayResize(Formatted_Symbs, Num_symbs); for(class="type">int i = class="num">0; i < Num_symbs; i++){ Formatted_Symbs[i] = symb_List[i]; } ArrayResize(rangeArray, Num_symbs); ArrayResize(prevTick, Num_symbs); ArrayResize(currTick, Num_symbs); ArrayResize(handles, Num_symbs); ArraySetAsSeries(bufferM, true);