并行粒子群优化·综合运用
(3/3)· 遗传算法之外,用粒子群在本地代理并行搜参,收尾篇给出完整集成路径
用 Virtual 库把 MQL5 EA 跑成虚拟回测
想在 MetaTrader 5 里用粒子群(PSO)优化 EA,第一步是让 EA 脱离实时账户、在本地历史数据上模拟成交。自己写报价与账户状态的 API 中间层太重,不如直接用 fxsaber 的 Virtual 库——它纯 MQL 实现,吃标准的 tick 与 bar 结构,在线自优化和测试器里都能跑。 接库只需包含 Virtual.mqh,并在 EA 里用一行静态调用把 tick 数组、OnTick 指针、初始存款喂进去。所有动作由 VIRTUAL::Tester 执行;测试器里调用 TesterStatistics 会被库“重叠”代理到虚拟内核,不过并非全部 STAT_* 指标都有实现,写统计逻辑时要实测哪些为空。 注意该库底层是 MetaTrader 4 交易 API,只认老式下单函数。即便 EA 用 MQL5 写,也得靠 MT4Orders 这类兼容层才能在 MT5 环境编译运行。本文改自 ExprBot.mq5 的 ExprBotPSO.mq5 就是这么干的:双均线交叉策略,信号由表达式解析引擎算,解析器已升到 v1.1(旧版不可用)。 EA 关键开关里,VirtualTester=false 是实盘常态;InternalOptimization=true 才在虚拟模式里拉起 PSO。PSO_SwarmSize=0 表示按参数数量自动定粒子数,PSO_Cycles 越大搜得越细但单趟无日志时间更长。总测试点近似 PSO_GroupCount * PSO_Cycles * PSO_SwarmSize,因 swarm 用二叉树存点,实际点数会小于该乘积——重复坐标不重算。 一个坑:虚拟优化要把可优化参数写进“EA_name.mq5.csv”辅助文件发给代理。第一次跑前务必手动建空文件,否则代理初始化报 INIT_PARAMETERS_INCORRECT;测试器缓存资源列表,不重选 EA 就不会补发新文件。用 ResetOptimizableParam 清掉优化旗标,再借 Expert 库记下参数名,代码就能通用到任意 EA。 tick 收集别直接调 CopyTicksRange:它只在每报价模式好使,且 tick 生成模式数组上限 131072(真实 tick 无此限)。我们在 OnTick 里先攒 _ticks[],等 OnTester 把 OnTesterCalled 置 true 后再交给 VIRTUAL::Tester 循环回放,同一 OnTick 据此切到交易逻辑而非采集逻辑。
class="type">MqlTick _ticks[]; class=class="str">"cmt">// global array ... class=class="str">"cmt">// copy/collect ticks[] VIRTUAL::Tester(_ticks, OnTick class=class="str">"cmt">/*, TesterStatistics(STAT_INITIAL_DEPOSIT)*/ );
◍ 把虚拟成交与粒子群塞进测试器
想在 MT5 策略测试器里跑「不真正下单」的虚拟成交,同时用粒子群(PSO)搜参数,核心就几行:先 Print(VIRTUAL::ToString(INT_MAX)) 把虚拟单打进日志,再用 TesterStatistics(STAT_PROFIT) 取净值,跑完用 VIRTUAL::ResetTickets() 和 VIRTUAL::Delete() 清场。这样回测不会污染真实账户,外汇与贵金属品种的高波动风险也只在模拟层面暴露。 信号描述直接写成字符串就能交给框架解析:SignalBuy 用 EMA_OPEN_{Fast}(0)/EMA_OPEN_{Slow}(0) > 1 + Threshold,SignalSell 反过来;Variables 里 Threshold=0.001,Fast=10、Slow=21 是默认输入。改这两个数,欧澳美 15M 上的信号频率会明显变化,可自行开 MT5 验证。 WorkerFunctor 里的 calculate() 是真正干活的地方:VIRTUAL::Tester(_ticks, OnTick, TesterStatistics(STAT_INITIAL_DEPOSIT)) 用初始入金跑虚拟 tick,算完取 Estimator 指标、删虚拟单返回。OnTesterInit() 会写一份 __FILE__.csv 共享参数表,并依据 TERMINAL_CPU_CORES 把 PSO_GroupCount 默认设为物理核数——8 核机器就并行 8 组,搜参速度可能提升数倍。
Print(VIRTUAL::ToString(INT_MAX)); class=class="str">"cmt">// output class="kw">virtual trades in the log class="kw">const class="type">class="kw">double result = TesterStatistics(STAT_PROFIT); class=class="str">"cmt">// get required performance meter VIRTUAL::ResetTickets(); class=class="str">"cmt">// optional VIRTUAL::Delete(); class=class="str">"cmt">// optional class="kw">input class="type">class="kw">string SignalBuy = "EMA_OPEN_{Fast}(class="num">0)/EMA_OPEN_{Slow}(class="num">0) > class="num">1 + Threshold"; class="kw">input class="type">class="kw">string SignalSell = "EMA_OPEN_{Fast}(class="num">0)/EMA_OPEN_{Slow}(class="num">0) < class="num">1 - Threshold"; class="kw">input class="type">class="kw">string Variables = "Threshold=class="num">0.001"; class="kw">input class="type">int Fast = class="num">10; class="kw">input class="type">int Slow = class="num">21; class BaseFunctor: class="kw">public Functor { ... class="kw">public: class="kw">virtual class="type">bool test(class="type">void) { Swarm swarm(PSO_SwarmSize, PSO_GroupCount, params, max, min, steps); if(MQLInfoInteger(MQL_OPTIMIZATION)) { if(!swarm.restoreIndex()) class="kw">return class="kw">false; } optimum = swarm.optimize(this, PSO_Cycles); swarm.getSolution(result); if(MQLInfoInteger(MQL_OPTIMIZATION)) { swarm.exportIndex(PSO_GroupCount); } class="kw">return true; } }; class WorkerFunctor: class="kw">public BaseFunctor { class="type">class="kw">string names[]; class="kw">public: WorkerFunctor(class="kw">const Settings &s): BaseFunctor(s.size()) { s.getNames(names); for(class="type">int i = class="num">0; i < params; i++) { max[i] = s.get<class="type">class="kw">double>(i, SET_COLUMN_STOP); min[i] = s.get<class="type">class="kw">double>(i, SET_COLUMN_START); steps[i] = s.get<class="type">class="kw">double>(i, SET_COLUMN_STEP); } } class="kw">virtual class="type">class="kw">double calculate(class="kw">const class="type">class="kw">double &vec[]) { VIRTUAL::Tester(_ticks, OnTick, TesterStatistics(STAT_INITIAL_DEPOSIT)); VIRTUAL::ResetTickets(); class="kw">const class="type">class="kw">double r = TesterStatistics(Estimator); VIRTUAL::Delete(); class="kw">return r; } }; class="macro">#define PPSO_SHARED_SETTINGS __FILE__ + ".csv" class="macro">#class="kw">property tester_file PPSO_SHARED_SETTINGS class="macro">#define MAX_COLUMN class="num">4 class="macro">#define SET_COLUMN_NAME class="num">0 class="macro">#define SET_COLUMN_START class="num">1 class="macro">#define SET_COLUMN_STEP class="num">2 class="macro">#define SET_COLUMN_STOP class="num">3 class="type">class="kw">string header[]; class="type">void OnTesterInit() { class="type">int h = FileOpen(PPSO_SHARED_SETTINGS, FILE_ANSI|FILE_WRITE|FILE_CSV, &class="macro">#x27;,&class="macro">#x27;); if(h == INVALID_HANDLE) { Print("FileSave error: ", GetLastError()); } class="type">MqlParam parameters[]; class="type">class="kw">string names[]; EXPERT::Parameters(class="num">0, parameters, names); for(class="type">int i = class="num">0; i < ArraySize(names); i++) { if(ResetOptimizableParam<class="type">class="kw">double>(names[i], h)) { class="kw">const class="type">int n = ArraySize(header); ArrayResize(header, n + class="num">1); header[n] = names[i]; } } FileClose(h); class=class="str">"cmt">// class="num">5008 class="type">bool enabled; class="type">long value, start, step, stop; if(ParameterGetRange("PSO_GroupCount", enabled, value, start, step, stop)) { if(!enabled) { class="kw">const class="type">int cores = TerminalInfoInteger(TERMINAL_CPU_CORES); Print("PSO_GroupCount is set to class="kw">default (number of cores): ", cores); ParameterSetRange("PSO_GroupCount", true, class="num">0, class="num">1, class="num">1, cores); } } class=class="str">"cmt">// remove CRC indices from previous optimization runs Swarm::removeIndex(); } class="kw">template<class="kw">typename T>
「把原生优化让位给粒子群」
在 MT5 策略测试器里跑 PSO(粒子群)前,得先把普通参数优化通道掐掉,否则框架会重复劳动。下面这个函数只保留名称带 PSO_ 前缀的参数可优化,其余一律 ParameterSetRange 关掉,并把原始起止步长写进文件句柄 h 留底。 [CODE] bool ResetOptimizableParam(const string name, const int h) { bool enabled; T value, start, step, stop; if(ParameterGetRange(name, enabled, value, start, step, stop)) { // 除 PSO_ 相关参数外,禁用所有原生优化 // 把原始设置存到文件 h if((StringFind(name, "PSO_") != 0) && enabled) { ParameterSetRange(name, false, value, start, step, stop); FileWrite(h, name, start, step, stop); // 5007 return true; } } return false; } [/CODE] OnInit 里若读不到共享设置文件且正处在 MQL_OPTIMIZATION 模式,直接返回 INIT_PARAMETERS_INCORRECT 终止,避免空跑;非优化模式下才会打印“Virtual optimization inside single pass”警告,那是单机虚拟回测最慢的路径,仅调试用。 虚拟 tick 收集走 OnTick 的旁路:当 VirtualTester 为真且 OnTester 尚未触发,就把每笔 MqlTick 塞进 _ticks 数组并直接 return,不进真实交易逻辑。OnTester 里若 settings 为空或关了内部优化,就退化为单次虚拟测试,调用 VIRTUAL::Tester 并输出 VirtualOrdersHistoryTotal 单数供核对。 信号表达式用占位符拼:SignalBuy 写成 EMA_OPEN_{Fast}(Bar)/EMA_OPEN_{Slow}(Bar) > 1 + Threshold,Fast/Slow/Threshold 由 PSO 在每组粒子里替换求值;iBarShift 取当前 K 线偏移量 Bar,使信号对准指定棒。外汇与贵金属品种用这套做虚拟优化时杠杆与滑点敏感,回测结果仅代表历史概率,实盘偏差可能偏大。
class="type">bool ResetOptimizableParam(class="kw">const class="type">class="kw">string name, class="kw">const class="type">int h) { class="type">bool enabled; T value, start, step, stop; if(ParameterGetRange(name, enabled, value, start, step, stop)) { class=class="str">"cmt">// disable all native optimization except for PSO-related params class=class="str">"cmt">// preserve original settings in the file h if((StringFind(name, "PSO_") != class="num">0) && enabled) { ParameterSetRange(name, class="kw">false, value, start, step, stop); FileWrite(h, name, start, step, stop); class=class="str">"cmt">// class="num">5007 class="kw">return true; } } class="kw">return class="kw">false; } Settings settings; class="type">int OnInit() { ... FileReader f(PPSO_SHARED_SETTINGS); if(f.isReady() && f.read(settings)) { class="kw">const class="type">int n = settings.size(); Print("Got settings: ", n); } else { if(MQLInfoInteger(MQL_OPTIMIZATION)) { Print("FileLoad error: ", GetLastError()); class="kw">return INIT_PARAMETERS_INCORRECT; } else { Print("WARNING!Virtual optimization inside single pass - slowest mode, debugging only"); } } ... } class="type">bool OnTesterCalled = class="kw">false; class="type">void OnTick() { if(VirtualTester && !OnTesterCalled) { class="type">MqlTick _tick; SymbolInfoTick(_Symbol, _tick); class="kw">const class="type">int n = ArraySize(_ticks); ArrayResize(_ticks, n + class="num">1, n / class="num">2); _ticks[n] = _tick; class="kw">return; class=class="str">"cmt">// skip all time scope and collect ticks } ... class=class="str">"cmt">// trading goes on here } class="type">class="kw">double OnTester() { if(VirtualTester) { OnTesterCalled = true; class=class="str">"cmt">// MQL API implies some limitations for CopyTicksRange function, so ticks are collected in OnTick class="kw">const class="type">int size = ArraySize(_ticks); PrintFormat("Ticks size=%d error=%d", size, GetLastError()); if(size <= class="num">0) class="kw">return class="num">0; if(settings.isVoid() || !InternalOptimization) class=class="str">"cmt">// fallback to a single class="kw">virtual test without PSO { VIRTUAL::Tester(_ticks, OnTick, TesterStatistics(STAT_INITIAL_DEPOSIT)); Print(VIRTUAL::ToString(INT_MAX)); Print("Trades: ", VIRTUAL::VirtualOrdersHistoryTotal()); class="kw">return TesterStatistics(Estimator); } settings.print(); class="kw">const class="type">int n = settings.size(); if(PSO_Enable) { MathSrand(PSO_GroupCount + PSO_RandomSeed); class=class="str">"cmt">// reproducable randomization WorkerFunctor worker(settings); Swarm::Stats stats; if(worker.test(&stats)) { class="type">class="kw">double output[]; class="type">class="kw">double result = worker.getSolution(output); if(MQLInfoInteger(MQL_OPTIMIZATION)) { FrameAdd(StringFormat("PSO%d/%d", stats.done, stats.planned), PSO_GroupCount, result, output); } ArrayResize(output, n + class="num">1); output[n] = result; ArrayPrint(output); class="kw">return result; } } ... class="kw">return class="num">0; } class="kw">return TesterStatistics(Estimator); } class="kw">input class="type">class="kw">string SignalBuy = "EMA_OPEN_{Fast}(class="num">0)/EMA_OPEN_{Slow}(class="num">0) > class="num">1 + Threshold"; class="kw">input class="type">class="kw">string SignalSell = "EMA_OPEN_{Fast}(class="num">0)/EMA_OPEN_{Slow}(class="num">0) < class="num">1 - Threshold"; class="kw">const class="type">int bar = iBarShift(_Symbol, PERIOD_CURRENT, TimeCurrent()); class="kw">input class="type">class="kw">string SignalBuy = "EMA_OPEN_{Fast}(Bar)/EMA_OPEN_{Slow}(Bar) > class="num">1 + Threshold"; class="kw">input class="type">class="kw">string SignalSell = "EMA_OPEN_{Fast}(Bar)/EMA_OPEN_{Slow}(Bar) < class="num">1 - Threshold";
把当前K线塞进条件解析
上一节里信号表达式直接 resolve,实际回测会发现它默认吃的是指标缓冲的当前位,未必对齐你正在看的这根 bar。MQL5 里用 iBarShift 把 TimeCurrent 映射到当前图表周期的 bar 下标,再透传进 with("Bar", bar),条件才会锁定在「这一根」上计算。 下面三行就是改造核心:先取 bar 序号,再分别把 buy / sell 两套 pattern 绑定该 bar 后解析。注释里 WAS 开头的是旧写法,去掉 Bar 参数时,在切换周期或挂单场景下可能误判上一根闭合值。 信号字符串本身没动,依旧是快线慢线开盘价之比偏离 1 加减阈值 T。但要注意,外汇与贵金属杠杆高、滑点大,这种比值信号只在波动率平稳段有效,极端消息行情下假突破概率显著抬升。 开 MT5 把这段替进你原有 EA 的 OnTick 前段,用「iBarShift(_Symbol, PERIOD_CURRENT, TimeCurrent())」打日志看 bar 值是否随新蜡烛递增,就能验证绑定是否生效。
class="kw">const class="type">int bar = iBarShift(_Symbol, PERIOD_CURRENT, TimeCurrent()); class=class="str">"cmt">// NEW class="type">bool buy = p1.with("Bar", bar).resolve(); class=class="str">"cmt">// WAS: class="type">bool buy = p1.resolve(); class="type">bool sell = p2.with("Bar", bar).resolve(); class=class="str">"cmt">// WAS: class="type">bool sell = p2.resolve(); class="kw">input class="type">class="kw">string SignalBuy = "M_EMA_OPEN_9_6_27(Fast,Bar)/M_EMA_OPEN_9_6_27(Slow,Bar) > class="num">1 + T"; class="kw">input class="type">class="kw">string SignalSell = "M_EMA_OPEN_9_6_27(Fast,Bar)/M_EMA_OPEN_9_6_27(Slow,Bar) < class="num">1 - T";
◍ 慢速全迭代压测下的时间账本
在 MT5 测试器里跑这套 EA,必须选慢速优化——也就是所有参数完整迭代,不能开遗传算法。原因很直接:遗传没法保证每组参数都被算到,它会自己往「好看结果」偏移,而组数(PSO_GroupCount)只是 PSO 数据结构的随机化器,和盈利没有依赖关系;再加上群体数量通常太小,遗传方法根本不够用。 先按常规方式优化,关掉虚拟库(ExprBotPSO-standard-optimization.set)。Fast 和 Slow 取 9~45、步长 6,T 取 0~0.01、步长 0.0025,EURUSD H1,从 2020 年起始用真实报价点。日志显示两个代理跑了将近 21 分钟,总 pass 数 245,平均单 pass 约 10.2 秒。 换虚拟交易加 PSO(ExprBotPSO-virtual-pso-optimization.set),PSO_GroupCount 从 0 迭代到 3 确定组数为 4。同样真实报价模拟,测试器日志里每个「过程」变成一个虚拟优化组,总 pass 降到 4,但单 pass 拉长到平均 1 分 56 秒,总时间只用了 4 分钟——比标准优化砍掉八成以上。 真实与虚拟结果不会完全对齐,哪怕是无指标策略。测试器特性导致:市价附近的限价单触发路径可能不同;保证金不计算或算不准;佣金因 MQL API 限制不会自动算;净额结算下订单与交易账户处理有差异;且仅支持当前品种。这些在逐点回测里都会带来偏移。 调试时可用 ExprBotPSO-virtual-internal-optimization-single-pass.set 在单核正常测试跑虚拟优化,记得关掉测试器优化开关。小空间下 19 个循环就全覆盖了,但真实问题若有百万级组合,就得权衡 PSO_Cycles、PSO_SwarmSize 和 PSO_GroupCount。单次 PSO 测试内部要跑最多 PSO_Cycles * PSO_SwarmSize 个虚拟测试,进度条比常规慢是正常的。 很多交易者连跑几十次遗传优化碰运气,相当于 PSO 里把次数摊进 PSO_GroupCount。遗传跑满一万次,在 PSO 中可拆成例如 100 周期 × 100 群体。PSO_Cycles 类比世代,PSO_SwarmSize 就是群体规模。外汇与贵金属杠杆高,回测省下的时间不代表实盘胜率,参数仍需谨慎验证。
Experts optimization frame expert ExprBotPSO(EURUSD,H1) processing started Tester Experts\ExprBotPSO.ex5 on EURUSD,H1 from class="num">2020.01.class="num">01 class="num">00:class="num">00 to class="num">2020.08.class="num">01 class="num">00:class="num">00 Tester complete optimization started ... Core class="num">2 connected Core class="num">1 connected Core class="num">2 authorized(agent build class="num">2572) Core class="num">1 authorized(agent build class="num">2572) ... Tester optimization finished, total passes class="num">245 Statistics optimization done in class="num">20 minutes class="num">55 seconds Statistics shortest pass class="num">0:class="num">00:class="num">05.691, longest pass class="num">0:class="num">00:class="num">23.114, average pass class="num">0:class="num">00:class="num">10.206 Tester class="kw">input parameter &class="macro">#x27;Fast&class="macro">#x27; set to: enable=class="kw">false, value=class="num">9, start=class="num">9, step=class="num">6, stop=class="num">45 Tester class="kw">input parameter &class="macro">#x27;Slow&class="macro">#x27; set to: enable=class="kw">false, value=class="num">21, start=class="num">9, step=class="num">6, stop=class="num">45 Tester class="kw">input parameter &class="macro">#x27;T&class="macro">#x27; set to: enable=class="kw">false, value=class="num">0, start=class="num">0, step=class="num">0.0025, stop=class="num">0.01 Experts optimization frame expert ExprBotPSO(EURUSD,H1) processing started Tester Experts\ExprBotPSO.ex5 on EURUSD,H1 from class="num">2020.01.class="num">01 class="num">00:class="num">00 to class="num">2020.08.class="num">01 class="num">00:class="num">00 Tester complete optimization started ... Core class="num">1 connected Core class="num">2 connected Core class="num">2 authorized(agent build class="num">2572) Core class="num">1 authorized(agent build class="num">2572) ... Tester optimization finished, total passes class="num">4 Statistics optimization done in class="num">4 minutes class="num">00 seconds Statistics shortest pass class="num">0:class="num">01:class="num">27.723, longest pass class="num">0:class="num">02:class="num">24.841, average pass class="num">0:class="num">01:class="num">56.597 Statistics class="num">4 frames(class="num">784 bytes total, class="num">196 bytes per frame) received class="num">22:class="num">22:class="num">52.261 ExprBotPSO(EURUSD,H1) class="num">2 tmp-files deleted class="num">22:class="num">25:class="num">07.981 ExprBotPSO(EURUSD,H1) class="num">0 PSO75/class="num">1500 class="num">0 class="num">1974.400000000025 class="num">22:class="num">25:class="num">23.348 ExprBotPSO(EURUSD,H1) class="num">2 PSO84/class="num">1500 class="num">2 class="num">402.6000000000062 class="num">22:class="num">26:class="num">51.165 ExprBotPSO(EURUSD,H1) class="num">3 PSO70/class="num">1500 class="num">3 class="num">455.000000000003 class="num">22:class="num">26:class="num">52.451 ExprBotPSO(EURUSD,H1) class="num">1 PSO79/class="num">1500 class="num">1 class="num">458.3000000000047 class="num">22:class="num">26:class="num">52.466 ExprBotPSO(EURUSD,H1) Solution: class="num">1974.400000000025 class="num">22:class="num">26:class="num">52.466 ExprBotPSO(EURUSD,H1) class="num">39.00000 class="num">15.00000 class="num">0.00500 Ticks size=class="num">15060113 error=class="num">0 [,class="num">0] [,class="num">1] [,class="num">2] [,class="num">3] [class="num">0,] "Fast" "class="num">9" "class="num">6" "class="num">45" [class="num">1,] "Slow" "class="num">9" "class="num">6" "class="num">45" [class="num">2,] "T" "class="num">0" "class="num">0.0025" "class="num">0.01" PSO[class="num">3] created: class="num">15/class="num">3 PSO Processing... Fast:class="num">9.0, Slow:class="num">33.0, T:class="num">0.0025, class="num">1.31285 Fast:class="num">21.0, Slow:class="num">21.0, T:class="num">0.0025, -class="num">1.0 Fast:class="num">15.0, Slow:class="num">33.0, T:class="num">0.0075, -class="num">1.0 Fast:class="num">27.0, Slow:class="num">39.0, T:class="num">0.0025, class="num">0.07673 Fast:class="num">9.0, Slow:class="num">9.0, T:class="num">0.005, -class="num">1.0 Fast:class="num">33.0, Slow:class="num">21.0, T:class="num">0.01, -class="num">1.0 Fast:class="num">39.0, Slow:class="num">45.0, T:class="num">0.0025, -class="num">1.0 Fast:class="num">15.0, Slow:class="num">15.0, T:class="num">0.0025, -class="num">1.0 Fast:class="num">33.0, Slow:class="num">21.0, T:class="num">0.0, class="num">0.32895 Fast:class="num">33.0, Slow:class="num">39.0, T:class="num">0.0075, -class="num">1.0 Fast:class="num">33.0, Slow:class="num">15.0, T:class="num">0.005, class="num">384.5 Fast:class="num">15.0, Slow:class="num">27.0, T:class="num">0.0, class="num">2.44486 Fast:class="num">39.0, Slow:class="num">27.0, T:class="num">0.0025, class="num">11.41199 Fast:class="num">9.0, Slow:class="num">15.0, T:class="num">0.0, class="num">1.08838 Fast:class="num">33.0, Slow:class="num">27.0, T:class="num">0.0075, -class="num">1.0 Cycle class="num">0 done, skipped class="num">0 of class="num">15 / class="num">384.5000000000009 ... Fast:class="num">45.0, Slow:class="num">9.0, T:class="num">0.0025, class="num">0.86209 Fast:class="num">21.0, Slow:class="num">15.0, T:class="num">0.005, -class="num">1.0 Cycle class="num">15 done, skipped class="num">13 of class="num">15 / class="num">402.6000000000062 Fast:class="num">21.0, Slow:class="num">15.0, T:class="num">0.0025, class="num">101.4 Cycle class="num">16 done, skipped class="num">14 of class="num">15 / class="num">402.6000000000062 Fast:class="num">27.0, Slow:class="num">15.0, T:class="num">0.0025, class="num">8.18754 Fast:class="num">39.0, Slow:class="num">15.0, T:class="num">0.005, class="num">1974.40002 Cycle class="num">17 done, skipped class="num">13 of class="num">15 / class="num">1974.400000000025 Fast:class="num">45.0, Slow:class="num">9.0, T:class="num">0.005, class="num">1.00344 Cycle class="num">18 done, skipped class="num">14 of class="num">15 / class="num">1974.400000000025 Cycle class="num">19 done, skipped class="num">15 of class="num">15 / class="num">1974.400000000025 PSO Finished class="num">89 of class="num">1500 planned calculations: true class="num">39.00000 class="num">15.00000 class="num">0.00500 class="num">1974.40000 final balance class="num">10000.00 USD OnTester result class="num">1974.400000000025
「用中间层把 MQL5 调用桥接到 MQL4」
想在 MT5 里跑基于「新」MQL5 API 写的 EA,却复用原先 MQL4 的 Virtual 交易逻辑,可以加一层实验性的 MT5Bridge.mqh。它把 MQL5 API 调用重定向到 MQL4 API,依赖 Virtual 库和/或 MT4Orders 才能工作。 具体做法:在代码最开头、其他 #include 之前先引入 Virtual 和 MT5Bridge,之后所有 MQL5 API 函数会通过重定义的 bridge 函数转到 Virtual 的 MQL4 API。这样就能在 MT5 测试器中虚拟化地回测和优化 EA,比如把前面的 ExprBotPSO 那套粒子群优化直接跑起来。 不过指标信号去适配可变参数,是这套流程里最耗资源和时间的部分,优化前先估好算力。MT5Bridge.mqh 目前是概念验证状态,作者没做广泛测试,外汇和贵金属杠杆品种波动剧烈、风险高,用它跑实盘前务必在策略测试器里自行排错。 下面这段就是桥接层的典型引入顺序,注意 Virtual 和 MT5Bridge 必须排在最前。
class="macro">#include <fxsaber/Virtual/Virtual.mqh> class="macro">#include <MT5Bridge.mqh> class="macro">#include <Expert\Expert.mqh> class="macro">#include <Expert\Signal\SignalMA.mqh> class="macro">#include <Expert\Trailing\TrailingParabolicSAR.mqh> class="macro">#include <Expert\Money\MoneySizeOptimized.mqh> ...
画得少,看得清
把粒子群优化(PSO)搬进 MQL5 之后,最大好处不是算得快,而是把惯性、自增强、组增强这三个系数直接摊在你面前:默认值分别是 0.8、0.4、0.4,想换策略就动手调,不用被内置遗传优化的黑箱绑死。代价也很直白——可调空间一大,针对某个具体 EA 的调参反而更费眼。 附带的那份 PSO 库(压缩包约 105 KB)有两种跑法:数学计算模式自己喂虚拟报价和交易逻辑,或者接第三方现成模拟类在真实柱形上跑。外汇和贵金属市场高杠杆、滑点凶,回测顺不代表实盘能复制,拿去跑之前先在小资金账户验证。 真要落地,就开 MT5 把库挂上,先只动 inertia 从 0.8 降到 0.6 看收敛轨迹变化——少改几个数,反而更容易看明白算法在干什么。