创建动态多货币对EA(第二部分):投资组合多元化与优化(基础篇)
📘

创建动态多货币对EA(第二部分):投资组合多元化与优化(基础篇)

第 1/3 篇

「把多品种塞进一个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 跑通这套时间窗与振荡器阈值。

MQL5 / C++
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 句柄。

MQL5 / C++
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);

常见问题

需要。多品种EA在OnInit里要用SymbolArray逐个调用iBars或CopyRates拉取各自数据,不能只认当前图表品种。
单一策略在突变市容易集体触发同向止损。组合多品种是为了分散相关性,但参数需按各自波动重优化,不能直接套用。
小布可自动扫描你账户下的品种波动特征并生成初始化骨架提示,你直接抄进EA就能少写几十行重复代码。
通常是价格突破N根K线高低点区间,且当前ATR或成交量高于阈值,两者同时成立才发信号,避免毛刺假突破。
不是。高相关品种堆再多也难分散风险,建议选低相关且波动结构不同的3到5个,并用优化器测过组合回撤再上实盘。