MQL5 Cookbook: 多币种EA交易 - 简洁而快速的途径·综合运用
(3/3)·从指标代理到参数数组,一套代码框架覆盖多品种回测与实盘的差异处理
◍ 同向加仓与反转开仓的执行细节
当信号方向与现有仓位相反且允许反转时,代码先把交易量拼成原仓体积加新增计算量,再调用 OpenPosition 翻转持仓。这里 lot=pos.volume+CalculateLot(...) 意味着反转不是平掉重开,而是在原仓基础上叠加手数,实际风险敞口会短时翻倍,外汇与贵金属的高杠杆下可能触发追加保证金。 若信号与仓位同向且 VolumeIncrease[symbol_number]>0,则只取回当前仓的 SL、TP,用 CalculateLot 算出加仓量后同价加仓。注意加仓沿用 pos.sl 与 pos.tp,不会为新增部分单独设止损,回撤被放大时整体浮亏可能加速。 OpenPosition 本身按执行模式分流:INSTANT 与 MARKET 模式下直接 PositionOpen,滑点由 CorrectValueBySymbolDigits(Deviation) 按品种精度校正。从 build 803 起 MARKET 模式也可带止损获利,老版本回测若忽略这点会误判成交逻辑。
class=class="str">"cmt">//--- 调整交易量 lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]); class=class="str">"cmt">//--- 反转仓位 OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment); class="kw">return; } class=class="str">"cmt">//--- 如果信号与仓位方向相同并且启用了增加交易量,则增加仓位交易量 if(!(pos.type==opposite_position_type) && VolumeIncrease[symbol_number]>class="num">0) { class=class="str">"cmt">//--- 取得当前仓位的止损 GetPositionProperties(symbol_number,P_SL); class=class="str">"cmt">//--- 取得当前仓位的获利 GetPositionProperties(symbol_number,P_TP); class=class="str">"cmt">//--- 调整交易量 lot=CalculateLot(symbol_number,VolumeIncrease[symbol_number]); class=class="str">"cmt">//--- 增加仓位交易量 OpenPosition(symbol_number,lot,order_type,position_open_price,pos.sl,pos.tp,comment); class="kw">return; } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 开启一个仓位 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OpenPosition(class="type">int symbol_number, class="type">class="kw">double lot, ENUM_ORDER_TYPE order_type, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp, class="type">class="kw">string comment) { class=class="str">"cmt">//--- 设置交易结构的幻数 trade.SetExpertMagicNumber(MagicNumber); class=class="str">"cmt">//--- 设置滑点点数 trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); class=class="str">"cmt">//--- 即时执行和市场执行模式 class=class="str">"cmt">// *** 从 build class="num">803 开始, 止损和获利 *** class=class="str">"cmt">// *** 可以在以 SYMBOL_TRADE_EXECUTION_MARKET 模式下设置 *** if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT || symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET) { class=class="str">"cmt">//--- 如果仓位开始失败,打印相关信息 if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment)) Print("开始仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError())); } }
分品种优化再合并回测的实操路径
做多品种 EA 时,别一次性把所有品种丢进优化器。先单独跑 EURUSD:在策略测试器里把 EA 的「交易品种名称」参数只填 EURUSD,其余品种栏位留空,这样优化进程只算这一条,双核机器上完整优化大概耗 1 小时。附件里的 .set 文件直接调出来用,能省掉手填参数的麻烦。 NZDUSD 同理,但切换时不用重开工程——把第一段参数块的交易品种名清空,或者干脆在名称末尾加一条横线(例如 NZDUSD-)。EA 按这个名字去查市场里根本不存在该品种,对应数组索引自动置空,等于静默跳过了前一品种。两轮各自出最大采收率结果后,再把两个品种名都填回设置。 合并测试阶段有个细节:策略测试器里 EA 挂在哪个图表品种启动都行,哪怕选一个根本不参与交易的品种也没影响,最终净值曲线是俩品种叠加的结果。外汇与贵金属品种波动大、滑点跳空频繁,这种分开优化再合并的做法只是降低单轮算力占用,不表示实盘风险可控,请务必在 MT5 用真实点差重跑一遍验证。
「多品种EA的部署与反转开仓细节」
把 MultiSymbolExpert 文件夹丢进 MetaTrader 5\MQL5\Experts,EventsSpy.mq5 放到 MetaTrader 5\MQL5\Indicators,EA 才能正常加载多品种监听。附件里给了 EURUSD 与 NZDUSD 两组 .set 文件,体积都只有 1.47 KB,可直接拖进策略测试器验证参数敏感度的差异。 评论区有人提到在策略测试器里撞上 4753 报错「未找到头寸」,这通常发生在 trade.PositionOpen 调用时账户类型或执行模式不匹配;净头寸账户与对冲账户在反向开仓时的行为不一样,调试前先确认账户是 NETTING 还是 HEDGING。 代码里的反转逻辑并非平掉原仓再开新仓,而是检测出现相反信号且 Reverse 开关打开时,用原仓位量加上新计算手数直接调用 OpenPosition。下方片段里 lot=pos.volume+CalculateLot(...) 这一行就是加仓式反转的关键,对冲账户上会表现为同品种双向持仓,而不是净额翻转。 有读者指出原文用的 TimeframeToString 在现行 MQL5 里不存在,应换成 EnumToString,编译老代码时这类 API 变动最容易被忽略,建议开 MT5 直接改完跑一次编译。外汇与贵金属品种波动剧烈,多品种同时持仓会放大保证金占用,实盘前务必用历史数据回测确认风险敞口。
class=class="str">"cmt">//--- 如果有一个位置 else { class=class="str">"cmt">//--- 获取位置类型 GetPositionProperties(symbol_number,P_TYPE); class=class="str">"cmt">//--- 如果位置与信号相反且位置反转已启用 if(pos.type==opposite_position_type && Reverse[symbol_number]) { class=class="str">"cmt">//--- 获取位置音量 GetPositionProperties(symbol_number,P_VOLUME); class=class="str">"cmt">//--- 调整音量 lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]); class=class="str">"cmt">//--- 让我们颠倒一下位置 OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment); class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|| 打开一个位置| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OpenPosition(class="type">int symbol_number, class="type">class="kw">double lot, ENUM_ORDER_TYPE order_type, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp, class="type">class="kw">string comment) { class=class="str">"cmt">//--- 设置贸易结构中的主编号 trade.SetExpertMagicNumber(MagicNumber); class=class="str">"cmt">//--- 以点为单位设置滑移大小 trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); class=class="str">"cmt">//--- 即时执行和市场执行模式 class=class="str">"cmt">// *** 从第 class="num">803 版开始,止损和获利水平 *** class=class="str">"cmt">// *** 可在 SYMBOL_TRADE_EXECUTION_MARKET 模式下开仓时设置 *** if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT || symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET) { class=class="str">"cmt">//--- 如果位置未打开,则打印相关信息
◍ 一点提醒
上面那段 PositionOpen 失败分支里,把 GetLastError() 连续调用了两次:第一次拼字符串、第二次喂给 ErrorDescription。在 MT5 实际运行中,两次调用之间若有其他底层动作触发,第二次拿到的错误码可能已不是开仓当时的那个,描述也就对不上。
更稳的做法是先存本地变量:int err=GetLastError(); Print("开仓时出错:",err," - ",ErrorDescription(err));。你在 MT5 策略测试器里跑多品种遍历时,这类错码错位会让日志难以排查,外汇与贵金属杠杆高、滑点跳空频繁,错误定位准一点就少踩坑。
把这段改完丢进 EA 的报错回调里,下次批量下单失败就能直接看到真实原因,不用再猜。
if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment)) Print("开仓时出错:",GetLastError()," - ",ErrorDescription(GetLastError())); } }