MQL5自动化交易策略(第十九部分):包络线趋势反弹剥头皮交易交易执行与风险管理(下篇)·进阶篇
(2/3)· 接上篇架构,本篇落地 MQL5 交易执行与动态仓位,让 EA 真正下单且控亏
接上篇,我们继续深挖包络线趋势反弹剥头皮系统——第十八部分只搭了信号骨架,这一篇要把它接上交易执行与风险管理的血肉。很多自建 EA 卡在「能看不能做」,根源就在订单触发和仓位控制没写透。下面直接进架构与实现。
「信号容器的内存与索引管理」
在 MT5 里做多信号策略组合,先得有一个能装信号的容器。下面这段类实现用动态数组 _tradeSignals 存指针,并用 _size 记录实际信号数、_pointer 做游标,初始构造时两者分别置 -1 和 0。
Add 方法每次把 _size 加 1,再用 ArrayResize(_tradeSignals, _size, 8) 扩容——末尾的 8 是预留内存增量,连续加信号时能少几次重分配。信号指针塞进 _tradeSignals[_size-1],即末位。
Remove 会先校验 index 在 [0, _size) 内,把待删指针暂存,随后把后方元素整体前移一位覆盖,再 ArrayResize 缩容并 _size 减 1;越界调用直接返回 NULL 不报错。析构里跑一遍 delete 释放每个信号对象,避免 EA 退出时漏内存。
外汇与贵金属杠杆高、滑点跳空频繁,这类容器在实盘重载或定时器里反复增删时,若忘了析构释放,跑几天可能把终端内存吃满。开 MT5 把这段粘进类里,故意 Add 100 个假信号再 Remove 中间 50 个,用 ArraySize 打印验证预留逻辑是否如预期。
class="type">int _size; class=class="str">"cmt">//--- Track number of signals class="kw">public: class=class="str">"cmt">//--- Initialize empty signal collection class="type">void TradeSignalCollection() { _pointer = -class="num">1; class=class="str">"cmt">//--- Set initial pointer to -class="num">1 _size = class="num">0; class=class="str">"cmt">//--- Set initial size to class="num">0 } class=class="str">"cmt">//--- Destructor to clean up signals class="type">void ~TradeSignalCollection() { for (class="type">int i = class="num">0; i < ArraySize(_tradeSignals); i++) { class=class="str">"cmt">//--- Iterate signals class="kw">delete(_tradeSignals[i]); class=class="str">"cmt">//--- Delete each signal } } class=class="str">"cmt">//--- Add signal to collection class="type">void Add(IAdvisorStrategyExpression* item) { _size = _size + class="num">1; class=class="str">"cmt">//--- Increment size ArrayResize(_tradeSignals, _size, class="num">8); class=class="str">"cmt">//--- Resize array with reserve _tradeSignals[(_size - class="num">1)] = item; class=class="str">"cmt">//--- Store signal at last index } class=class="str">"cmt">//--- Remove signal at index IAdvisorStrategyExpression* Remove(class="type">int index) { IAdvisorStrategyExpression* removed = NULL; class=class="str">"cmt">//--- Initialize removed signal as null if (index >= class="num">0 && index < _size) { class=class="str">"cmt">//--- Check valid index removed = _tradeSignals[index]; class=class="str">"cmt">//--- Store signal to remove for (class="type">int i = index; i < (_size - class="num">1); i++) { class=class="str">"cmt">//--- Shift signals left _tradeSignals[i] = _tradeSignals[i + class="num">1]; class=class="str">"cmt">//--- Move signal } ArrayResize(_tradeSignals, ArraySize(_tradeSignals) - class="num">1, class="num">8); class=class="str">"cmt">//--- Reduce array size _size = _size - class="num">1; class=class="str">"cmt">//--- Decrement size } class="kw">return removed; class=class="str">"cmt">//--- Return removed signal or null } class=class="str">"cmt">//--- Retrieve signal at index IAdvisorStrategyExpression* Get(class="type">int index) {
信号容器的越界与游标控制
把策略信号塞进一个定长数组后,最容易被忽视的是下标越界和游标状态。下面这段实现用 _size 约束访问边界,越界直接返回 NULL,而不是抛异常或读脏内存。
取单条信号时先判 index >= 0 && index < _size,成立才返回 _tradeSignals[index],否则返回 NULL。这套写法在 EA 回测里能避免历史信号数组长度变动导致的数组越界报错(MT5 默认数组越界会写日志并中断当前 tick)。
游标类方法才是实际遍历用的:Rewind() 把 _pointer 设为 -1;Next() 先自增再判断是否等于 _size,到尾了就重置并返回 NULL;Prev() 自减后若落到 -1 也返回 NULL。HasNext() 用 _pointer < (_size - 1) 判断,注意它不移动指针,只探路。
外汇与贵金属杠杆高、滑点跳空频繁,这类容器若在实盘重算信号时复用旧 _size,可能漏掉最新 K 线信号或重复触发,建议在 OnTick 里先 Count() 核对长度再遍历。
if (index >= class="num">0 && index < _size) { class=class="str">"cmt">//--- Check valid index class="kw">return _tradeSignals[index]; class=class="str">"cmt">//--- Return signal } class="kw">return NULL; class=class="str">"cmt">//--- Return null for invalid index } class=class="str">"cmt">//--- Retrieve number of signals class="type">int Count() { class="kw">return _size; class=class="str">"cmt">//--- Return current size } class=class="str">"cmt">//--- Reset iterator to start class="type">void Rewind() { _pointer = -class="num">1; class=class="str">"cmt">//--- Set pointer to -class="num">1 } class=class="str">"cmt">//--- Move to next signal IAdvisorStrategyExpression* Next() { _pointer++; class=class="str">"cmt">//--- Increment pointer if (_pointer == _size) { class=class="str">"cmt">//--- Check if at end Rewind(); class=class="str">"cmt">//--- Reset pointer class="kw">return NULL; class=class="str">"cmt">//--- Return null } class="kw">return Current(); class=class="str">"cmt">//--- Return current signal } class=class="str">"cmt">//--- Move to previous signal IAdvisorStrategyExpression* Prev() { _pointer--; class=class="str">"cmt">//--- Decrement pointer if (_pointer == -class="num">1) { class=class="str">"cmt">//--- Check if before start class="kw">return NULL; class=class="str">"cmt">//--- Return null } class="kw">return Current(); class=class="str">"cmt">//--- Return current signal } class=class="str">"cmt">//--- Check if more signals exist class="type">bool HasNext() { class="kw">return (_pointer < (_size - class="num">1)); class=class="str">"cmt">//--- Return true if pointer is before end }
◍ 信号集合与策略类的内存边界
把开仓和平仓信号按买卖方向拆成四个独立集合,是这套策略框架避免互相污染的关键。AdvisorStrategy 在构造时一次性 new 出 _openBuySignals、_openSellSignals、_closeBuySignals、_closeSellSignals,析构里逐个 delete,MT5 上跑长周期 EA 时不会漏掉释放。 EvaluateASLevel 用 level>0 && level<=signals.Count() 做越界保护,调用方传 1 基的层级,内部立刻减 1 转成 0 基索引再 Evaluate()。若层级非法直接返 false,这意味着你外面拼多层级信号时,错层不会崩只是静默失效。 _current 信号靠 TradeSignalCollection 里的 _pointer 游标定位,Current() 返回指针位置那一条,Key() 把游标下标暴露出来。实盘里想看「现在命中第几层」直接读 Key(),不用自己维护计数。 外汇与贵金属杠杆高,这类信号集合若在非主图指标里频繁 new/delete,要留意 MT5 对对象生命周期的线程限制,建议开策略测试器用 2023 全年 EURUSD M15 跑一遍确认无句柄泄漏。
class=class="str">"cmt">//--- Retrieve current signal IAdvisorStrategyExpression* Current() { class="kw">return _tradeSignals[_pointer]; class=class="str">"cmt">//--- Return signal at pointer } class=class="str">"cmt">//--- Retrieve current iterator index class="type">int Key() { class="kw">return _pointer; class=class="str">"cmt">//--- Return current pointer } }; class=class="str">"cmt">//--- Define class for managing trading signals class AdvisorStrategy { class="kw">private: TradeSignalCollection* _openBuySignals; class=class="str">"cmt">//--- Store open Buy signals TradeSignalCollection* _openSellSignals; class=class="str">"cmt">//--- Store open Sell signals TradeSignalCollection* _closeBuySignals; class=class="str">"cmt">//--- Store close Buy signals TradeSignalCollection* _closeSellSignals; class=class="str">"cmt">//--- Store close Sell signals class=class="str">"cmt">//--- Evaluate signal at specified level class="type">bool EvaluateASLevel(TradeSignalCollection* signals, class="type">int level) { if (level > class="num">0 && level <= signals.Count()) { class=class="str">"cmt">//--- Check valid level class="kw">return signals.Get(level - class="num">1).Evaluate(); class=class="str">"cmt">//--- Evaluate signal } class="kw">return false; class=class="str">"cmt">//--- Return false for invalid level } class="kw">public: class=class="str">"cmt">//--- Initialize strategy class="type">void AdvisorStrategy() { _openBuySignals = new TradeSignalCollection(); class=class="str">"cmt">//--- Create open Buy signals collection _openSellSignals = new TradeSignalCollection(); class=class="str">"cmt">//--- Create open Sell signals collection _closeBuySignals = new TradeSignalCollection(); class=class="str">"cmt">//--- Create close Buy signals collection _closeSellSignals = new TradeSignalCollection(); class=class="str">"cmt">//--- Create close Sell signals collection } class=class="str">"cmt">//--- Destructor to clean up signals class="type">void ~AdvisorStrategy() { class="kw">delete(_openBuySignals); class=class="str">"cmt">//--- Delete open Buy signals class="kw">delete(_openSellSignals); class=class="str">"cmt">//--- Delete open Sell signals class="kw">delete(_closeBuySignals); class=class="str">"cmt">//--- Delete close Buy signals class="kw">delete(_closeSellSignals); class=class="str">"cmt">//--- Delete close Sell signals } class=class="str">"cmt">//--- Retrieve trading advice class="type">bool GetAdvice(TradeAction tradeAction, class="type">int level) { if (tradeAction == OpenBuyAction) { class=class="str">"cmt">//--- Check open Buy action
「信号分级注册与动作派发的实现细节」
这段逻辑把开仓、平仓四类动作统一走 EvaluateASLevel 接口,靠 tradeAction 枚举分流。若传入的动作不在 OpenBuy/OpenSell/CloseBuy/CloseSell 之内,直接弹 Alert 并 return false,说明策略层没接住非法指令。 注册函数对 level 做了硬约束:以 RegisterOpenBuy 为例,当 level <= _openBuySignals.Count() 时就报“level already set”并退出,意味着层级必须严格递增、不能覆盖已有槽位。四个 Register 函数(开买、开卖、平买、平卖)结构完全对称,只是操作的信号数组不同。 在 MT5 里跑这套,你可以故意传一个重复 level 验证 Alert 是否触发;外汇与贵金属杠杆高,信号层级错乱可能让 EA 在极端波动中漏单或重复开仓,务必先在策略测试器用历史数据过一遍。
class="kw">return EvaluateASLevel(_openBuySignals, level); class=class="str">"cmt">//--- Evaluate Buy signal } else if (tradeAction == OpenSellAction) { class=class="str">"cmt">//--- Check open Sell action class="kw">return EvaluateASLevel(_openSellSignals, level); class=class="str">"cmt">//--- Evaluate Sell signal } else if (tradeAction == CloseBuyAction) { class=class="str">"cmt">//--- Check close Buy action class="kw">return EvaluateASLevel(_closeBuySignals, level); class=class="str">"cmt">//--- Evaluate close Buy signal } else if (tradeAction == CloseSellAction) { class=class="str">"cmt">//--- Check close Sell action class="kw">return EvaluateASLevel(_closeSellSignals, level); class=class="str">"cmt">//--- Evaluate close Sell signal } else { Alert("Unsupported TradeAction in Advisor Strategy. TradeAction: " + DoubleToStr(tradeAction)); class=class="str">"cmt">//--- Log unsupported action } class="kw">return false; class=class="str">"cmt">//--- Return false for invalid action } class=class="str">"cmt">//--- Register open Buy signal class="type">void RegisterOpenBuy(IAdvisorStrategyExpression* openBuySignal, class="type">int level) { if (level <= _openBuySignals.Count()) { class=class="str">"cmt">//--- Check if level already set Alert("Register Open Buy failed: level already set."); class=class="str">"cmt">//--- Log failure class="kw">return; class=class="str">"cmt">//--- Exit } _openBuySignals.Add(openBuySignal); class=class="str">"cmt">//--- Add signal } class=class="str">"cmt">//--- Register open Sell signal class="type">void RegisterOpenSell(IAdvisorStrategyExpression* openSellSignal, class="type">int level) { if (level <= _openSellSignals.Count()) { class=class="str">"cmt">//--- Check if level already set Alert("Register Open Sell failed: level already set."); class=class="str">"cmt">//--- Log failure class="kw">return; class=class="str">"cmt">//--- Exit } _openSellSignals.Add(openSellSignal); class=class="str">"cmt">//--- Add signal } class=class="str">"cmt">//--- Register close Buy signal class="type">void RegisterCloseBuy(IAdvisorStrategyExpression* closeBuySignal, class="type">int level) { if (level <= _closeBuySignals.Count()) { class=class="str">"cmt">//--- Check if level already set Alert("Register Close Buy failed: level already set."); class=class="str">"cmt">//--- Log failure class="kw">return; class=class="str">"cmt">//--- Exit }
信号容器的越级保护与批量复位
平仓信号的注册函数和平仓一样做了 level 防重保护:RegisterCloseSell 里若 level 小于等于已注册数量,直接 Alert 报错并 return,避免同一优先级被重复写入导致逻辑错乱。 GetNumberOfExpressions 按 TradeAction 枚举分流,分别返回 _openBuySignals、_openSellSignals、_closeBuySignals、_closeSellSignals 四个容器的 Count(),无效动作返回 0。这一步是策略引擎统计某类动作挂了几条表达式的依据。 SetFireOnlyWhenReset 用 Rewind+Next 遍历多空开仓信号容器,对每条表达式调 SetFireOnlyWhenReset(value)。实盘里若设为 true,信号在触发后必须等条件复位才能再触发,可压住外汇与贵金属_high 波动下的重复开仓噪音,但也可能漏掉连续趋势段,需按品种波动率手调。
_closeBuySignals.Add(closeBuySignal); class=class="str">"cmt">//--- Add signal } class=class="str">"cmt">//--- Register close Sell signal class="type">void RegisterCloseSell(IAdvisorStrategyExpression* closeSellSignal, class="type">int level) { if (level <= _closeSellSignals.Count()) { class=class="str">"cmt">//--- Check if level already set Alert("Register Close Sell failed: level already set."); class=class="str">"cmt">//--- Log failure class="kw">return; class=class="str">"cmt">//--- Exit } _closeSellSignals.Add(closeSellSignal); class=class="str">"cmt">//--- Add signal } class=class="str">"cmt">//--- Retrieve number of signals for action class="type">int GetNumberOfExpressions(TradeAction tradeAction) { if (tradeAction == OpenBuyAction) { class=class="str">"cmt">//--- Check open Buy action class="kw">return _openBuySignals.Count(); class=class="str">"cmt">//--- Return Buy signal count } else if (tradeAction == OpenSellAction) { class=class="str">"cmt">//--- Check open Sell action class="kw">return _openSellSignals.Count(); class=class="str">"cmt">//--- Return Sell signal count } else if (tradeAction == CloseBuyAction) { class=class="str">"cmt">//--- Check close Buy action class="kw">return _closeBuySignals.Count(); class=class="str">"cmt">//--- Return close Buy signal count } else if (tradeAction == CloseSellAction) { class=class="str">"cmt">//--- Check close Sell action class="kw">return _closeSellSignals.Count(); class=class="str">"cmt">//--- Return close Sell signal count } class="kw">return class="num">0; class=class="str">"cmt">//--- Return class="num">0 for invalid action } class=class="str">"cmt">//--- Set fire-only-when-reset for all signals class="type">void SetFireOnlyWhenReset(class="type">bool value) { _openBuySignals.Rewind(); class=class="str">"cmt">//--- Reset Buy signals iterator class="kw">while (_openBuySignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate Buy signals _openBuySignals.Current().SetFireOnlyWhenReset(value); class=class="str">"cmt">//--- Set flag } _openSellSignals.Rewind(); class=class="str">"cmt">//--- Reset Sell signals iterator class="kw">while (_openSellSignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate Sell signals _openSellSignals.Current().SetFireOnlyWhenReset(value); class=class="str">"cmt">//--- Set flag }
◍ 信号触发开关与逐笔重算的衔接
信号容器里挂了开多、开空、平多、平空四组迭代器,每组在设置阶段都能统一写入 FireOnlyWhenReset 标志。上面这段把 _closeBuySignals 和 _closeSellSignals 先 Rewind 归零游标,再 Next 遍历,对当前信号对象调 SetFireOnlyWhenReset(value) 批量打标,逻辑对称且无遗漏。 到了 HandleTick,四组信号每次都先 Rewind 再循环 ResetSignalValue 清空旧值;只有当 GetFireOnlyWhenReset 返回真时才进 Evaluate 重算。这意味着若某信号被标了“仅重置时触发”,它不会在每个 tick 盲目刷值,只在复位节点参与评估,能压住不必要的重绘与误触。 实盘接这套结构时,建议先在策略测试器里把 value 分别传 true / false 各跑一轮 EURUSD 的 M15 回测,观察信号线闪烁频率差异——外汇与贵金属杠杆高,信号抖动会直接放大滑点成本,参数取向需结合自己的点差环境定。
_closeBuySignals.Rewind(); class=class="str">"cmt">//--- Reset close Buy signals iterator class="kw">while (_closeBuySignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate close Buy signals _closeBuySignals.Current().SetFireOnlyWhenReset(value); class=class="str">"cmt">//--- Set flag } _closeSellSignals.Rewind(); class=class="str">"cmt">//--- Reset close Sell signals iterator class="kw">while (_closeSellSignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate close Sell signals _closeSellSignals.Current().SetFireOnlyWhenReset(value); class=class="str">"cmt">//--- Set flag } } class=class="str">"cmt">//--- Handle tick event for signals class="type">void HandleTick() { _openBuySignals.Rewind(); class=class="str">"cmt">//--- Reset Buy signals iterator class="kw">while (_openBuySignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate Buy signals _openBuySignals.Current().ResetSignalValue(); class=class="str">"cmt">//--- Reset signal value if (_openBuySignals.Current().GetFireOnlyWhenReset()) { class=class="str">"cmt">//--- Check fire-only-when-reset _openBuySignals.Current().Evaluate(); class=class="str">"cmt">//--- Evaluate signal } } _openSellSignals.Rewind(); class=class="str">"cmt">//--- Reset Sell signals iterator class="kw">while (_openSellSignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate Sell signals _openSellSignals.Current().ResetSignalValue(); class=class="str">"cmt">//--- Reset signal value if (_openSellSignals.Current().GetFireOnlyWhenReset()) { class=class="str">"cmt">//--- Check fire-only-when-reset _openSellSignals.Current().Evaluate(); class=class="str">"cmt">//--- Evaluate signal } } _closeBuySignals.Rewind(); class=class="str">"cmt">//--- Reset close Buy signals iterator class="kw">while (_closeBuySignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate close Buy signals _closeBuySignals.Current().ResetSignalValue(); class=class="str">"cmt">//--- Reset signal value if (_closeBuySignals.Current().GetFireOnlyWhenReset()) { class=class="str">"cmt">//--- Check fire-only-when-reset _closeBuySignals.Current().Evaluate(); class=class="str">"cmt">//--- Evaluate signal } } _closeSellSignals.Rewind(); class=class="str">"cmt">//--- Reset close Sell signals iterator class="kw">while (_closeSellSignals.Next() != NULL) { class=class="str">"cmt">//--- Iterate close Sell signals _closeSellSignals.Current().ResetSignalValue(); class=class="str">"cmt">//--- Reset signal value