开发多币种 EA 交易(第 8 部分):负载测试和处理新柱·综合运用
(3/3)·从8到512个策略实例的压测曲线,看清内存与单次通过时间的真实拐点
◍ 用新柱事件卡住无效分时
把策略里的关键动作包进 IsNewBar() 判断,就能让它们只在对应品种和周期出现新柱时触发。比如 H1 新柱到了才跑必要逻辑,D1 新柱到了才重算一次 ATR——省掉每笔报价都重算均幅的浪费。 在 EA 的 OnTick 里也可以直接拦截:给 CVirtualAdvisor 加一个 m_useOnlyNewBar 属性,默认 false。置 true 后,只要没有任何跟踪品种/周期出新柱,Tick() 提前 return,策略实例根本拿不到分时。 策略类侧因此能瘦身:构造函数里用 m_symbol 和 PERIOD_M1 调一次 IsNewBar() 做订阅,Tick() 内不必再手写分钟柱检查。下面这段代码把「非 M1 新柱就退出」写死在策略 Tick 开头,外汇与贵金属杠杆品种波动剧烈、滑点风险高,只在新柱运算可明显降低无效下单概率。 改完记得存到 SimpleVolumesStrategy.mqh,MT5 里重新编译 EA 即可验证行为差异。
class=class="str">"cmt">// If a new bar arrived on H1 for the current strategy symbol, then if(IsNewBar(m_symbol, PERIOD_H1)) { class=class="str">"cmt">// perform the necessary actions ... } class=class="str">"cmt">// If a new bar arrived on D1 for the current strategy symbol, then if(IsNewBar(m_symbol, PERIOD_H1)) { CalcATR(); class=class="str">"cmt">// call our ATR calculation function } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "Tick" event handler function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSimpleVolumesStrategy::Tick() class="kw">override { class=class="str">"cmt">// If there is no new bar on M1, if(!IsNewBar(m_symbol, PERIOD_M1)) class="kw">return; class=class="str">"cmt">// If their number is less than allowed if(m_ordersTotal < m_maxCountOfOrders) { class=class="str">"cmt">// Get an open signal class="type">int signal = SignalForOpen(); if(signal == class="num">1 class=class="str">"cmt">/* || m_ordersTotal < class="num">1 */) { class=class="str">"cmt">// If there is a buy signal, then OpenBuyOrder(); class=class="str">"cmt">// open the BUY_STOP order } else if(signal == -class="num">1) { class=class="str">"cmt">// If there is a sell signal, then OpenSellOrder(); class=class="str">"cmt">// open the SELL_STOP order } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| OnTick event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CVirtualAdvisor::Tick(class="type">void) { class=class="str">"cmt">// Define a new bar for all required symbols and timeframes class="type">bool isNewBar = UpdateNewBar(); class=class="str">"cmt">// If there is no new bar anywhere, and we only work on new bars, then exit if(!isNewBar && m_useOnlyNewBar) { class="kw">return; } class=class="str">"cmt">// Receiver handles class="kw">virtual positions m_receiver.Tick(); class=class="str">"cmt">// Start handling in strategies CAdvisor::Tick(); class=class="str">"cmt">// Adjusting market volumes m_receiver.Correct(); class=class="str">"cmt">// Save status Save(); class=class="str">"cmt">// Render the interface m_interface.Redraw(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class of the EA handling class="kw">virtual positions(orders) | class=class="str">"cmt">//+------------------------------------------------------------------+ class CVirtualAdvisor : class="kw">public CAdvisor { class="kw">protected: ... class="type">bool m_useOnlyNewBar; class=class="str">"cmt">// Handle only new bar ticks class="kw">public: CVirtualAdvisor(class="type">class="kw">ulong p_magic = class="num">1, class="type">class="kw">string p_name = "", class="type">bool p_useOnlyNewBar = class="kw">false); class=class="str">"cmt">// Constructor ... };
虚拟顾问与量能策略的初始化咬合
把策略挂上虚拟交易框架,第一步是构造 CVirtualAdvisor 对象。它用 magic 号绑定静态接收器与静态接口,默认 p_useOnlyNewBar 为 false 意味着每一 tick 都可能触发检查,而不是只在新 K 线出现后才动作;回测环境下文件名会自动追加 .test 后缀,实盘则是普通 csv,方便你 afterwards 区分数据源。 CSimpleVolumesStrategy 的初始化列表里干了三件实打实的事:用 iVolumes 抓 tick 成交量、把 m_volumes 数组尺寸锁成 m_signalPeriod 并设为时间序列寻址、调用 IsNewBar(m_symbol, PERIOD_M1) 注册一分钟新柱事件。这三步少一个,后续 SignalForOpen 拿到的量能窗口就会错位。
| Tick() 里的开仓闸门很直白:m_ordersTotal 小于 m_maxCountOfOrders 才放信号,signal==1 走 OpenBuyOrder,signal==-1 走卖侧逻辑。被注释掉的 ` | m_ordersTotal < 1` 说明作者曾考虑过无持仓强制首单,现在默认关掉,你若想测“首单必发”可把注释揭开看成交分布变化。外汇与贵金属杠杆高,这类虚拟回测结论只代表历史样本概率,实盘可能显著偏离。 |
|---|
CVirtualAdvisor::CVirtualAdvisor(class="type">class="kw">ulong p_magic = class="num">1, class="type">class="kw">string p_name = "", class="type">bool p_useOnlyNewBar = class="kw">false) : m_receiver(CVirtualReceiver::Instance(p_magic)), m_interface(CVirtualInterface::Instance(p_magic)), m_lastSaveTime(class="num">0), m_useOnlyNewBar(p_useOnlyNewBar) { m_name = StringFormat("%s-%d%s.csv", (p_name != "" ? p_name : "Expert"), p_magic, (MQLInfoInteger(MQL_TESTER) ? ".test" : "") ); }; CSimpleVolumesStrategy::CSimpleVolumesStrategy( ...) : ... { CVirtualReceiver::Get(GetPointer(this), m_orders, m_maxCountOfOrders); m_iVolumesHandle = iVolumes(m_symbol, m_timeframe, VOLUME_TICK); ArrayResize(m_volumes, m_signalPeriod); ArraySetAsSeries(m_volumes, true); IsNewBar(m_symbol, PERIOD_M1); } class="type">void CSimpleVolumesStrategy::Tick() class="kw">override { if(m_ordersTotal < m_maxCountOfOrders) { class="type">int signal = SignalForOpen(); if(signal == class="num">1 class=class="str">"cmt">/* || m_ordersTotal < class="num">1 */) { OpenBuyOrder(); } else if(signal == -class="num">1) {
「挂单函数里的卖单触发细节」
上面这段 MQL5 片段只做了一件事:在条件满足时调用 OpenSellOrder() 并附上注释 // open the SELL_STOP order,说明此处意图是挂一个 SELL_STOP 卖单。 外层还有未展示的 if 判断与大括号嵌套,意味着该挂单动作被包在某种信号过滤逻辑里,不会每根 K 线都发单。 在 MT5 里验证时,重点看 OpenSellOrder() 内部是否真的用了 ORDER_TYPE_SELL_STOP,以及挂单价是否低于当前 Bid,否则注释与实单类型不符。 外汇与贵金属杠杆高,SELL_STOP 触发后若遇跳空可能滑点放大,下单前应在策略测试器用真实点差回测确认。
OpenSellOrder(); class=class="str">"cmt">// open the SELL_STOP order } } }
◍ 只跑新柱开盘的报价反而更赚
给 BenchmarkInstancesExpert.mq5 加了个输入参数 useOnlyNewBars_,用来控制 EA 是否处理那些不在新柱形开始时出现的分时报价。初始化时把这个值直接丢进 EA 的构造函数,逻辑上等于告诉虚拟持仓管理器:要么每个分时都干活,要么只在柱头动一次。 用 256 个策略实例在小周期上跑「基于真实分时的每个分时报价」模式做了对比。useOnlyNewBars_ = false 时,EA 每个分时都跑,利润 296 美元,耗时 04:15;改成 true、只认新柱开盘那一个分时后,利润 434 美元,00:25 就跑完。计算开销压到约十分之一,利润还略高一点。 别把这次结果当通解。换一套策略,跳过分时能不能保利润完全未知,每种逻辑都得单独拉回测看。外汇和贵金属杠杆高,这类加速手段只解决算力浪费,不替你扛回撤风险。 代码里参数默认就设成 true,想复现第一种慢模式得手动改回 false 再编译。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Inputs | class=class="str">"cmt">//+------------------------------------------------------------------+ ... input group "::: Other parameters" sinput class="type">class="kw">ulong magic_ = class="num">27183; class=class="str">"cmt">// - Magic input class="type">bool useOnlyNewBars_ = true; class=class="str">"cmt">// - Work only at bar opening ... 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 EA handling class="kw">virtual positions expert = new CVirtualAdvisor(magic_, "SimpleVolumes_BenchmarkInstances", useOnlyNewBars_); ... }
把实例初始化拉回正轨
这轮压测把 EA 在大量策略实例并发下的表现摸清楚了:多品种、多周期、多逻辑可以塞进同一个 EA 跑,类库顺手补了新柱事件跟踪。该功能在当前 Volume 策略里用不上,但留着能接别的策略;更实际的是,把 EA 计算锁在柱开盘瞬间,能压低开销,也让不同分时模拟模式的回测更接近。 附带五个文件可直接下:BenchmarkInstancesExpert.mq5(13.23 KB)、NewBarEvent.mqh(11.52 KB)、SimpleVolumesStrategy.mqh(33.62 KB)、VirtualAdvisor.mqh(14.64 KB)、VirtualOrder.mqh(39.52 KB)。外汇与贵金属杠杆高、滑点跳空频繁,并发 EA 回测漂亮不等于实盘稳,上 MT5 前先单品种裸跑一遍。 项目又滑出了原定路线,不过偏离本身也在给终局铺路。接下来该用字符串常量重新初始化策略实例,并搭一套优化结果存储,把自动测试的主线接回来。