通用智能交易系统:支持挂单和对冲(第五章)·综合运用
(3/3)·从价格访问重写到对冲账户适配,完整跑通 CImpulse 策略的最后一环
◍ 用均线边界卡住挂单的激活价
在突破类策略里,BuyStop 挂单的触发价不能随便给。代码用 Ask + Ask*(m_percent/100.0) 算出目标位后,先和当前均线值 Moving.OutValue(0) 比大小:若目标低于均线,说明突破逻辑不成立,直接把已有 BuyStop 删掉(task = ORDER_TASK_DELETE),否则才走修改或新建流程(task = ORDER_TASK_MODIFY)。 SellStop 一侧镜像处理:target = Bid - Bid*(m_percent/100.0),只有目标高于均线时才删单,低于均线才保留或修改。这个判断把「价格必须站在均线另一侧」硬化进订单生命周期,避免逆势挂单吃假突破。 循环里倒序遍历 PendingOrders,用 Order.IsMain(ExpertSymbol(), ExpertMagic()) 过滤出本 EA 自己下的单,再按 ORDER_TYPE_BUY_STOP / SELL_STOP 分别 Modify 或 Delete。若一轮下来对应类型挂单数为 0 且 task 为 MODIFY,才补一个 Trade.BuyStop / SellStop,保证同符号挂单不重复堆。 外汇与贵金属波动大、滑点随机,这套边界判断只能降低逆势挂单概率,不保证胜率;上 MT5 把 m_percent 从 0.5 调到 2.0 跑回测,能看到删单频率随阈值放大而明显上升。
class="type">class="kw">double target = Ask() + Ask()*(m_percent/class="num">100.0); if(target < Moving.OutValue(class="num">0)) class=class="str">"cmt">// 订单的激活价格必须高于移动平均价 task = ORDER_TASK_DELETE; else task = ORDER_TASK_MODIFY; for(class="type">int i = PendingOrders.Total()-class="num">1; i >= class="num">0; i--) { CPendingOrder* Order = PendingOrders.GetOrder(i); if(Order == NULL || !Order.IsMain(ExpertSymbol(), ExpertMagic())) class="kw">continue; if(Order.Type() == ORDER_TYPE_BUY_STOP) { if(task == ORDER_TASK_MODIFY) { buy_stop_total++; Order.Modify(target); } else Order.Delete(); } } if(buy_stop_total == class="num">0 && task == ORDER_TASK_MODIFY) Trade.BuyStop(MM.GetLotFixed(), target, ExpertSymbol(), class="num">0, class="num">0, NULL); class="type">void CImpulse::InitSell(class="kw">const MarketEvent &event) { if(!IsTrackEvents(event))class="kw">return; if(positions.open_sell > class="num">0) class="kw">return; class="type">int sell_stop_total = class="num">0; ENUM_ORDER_TASK task; class="type">class="kw">double target = Bid() - Bid()*(m_percent/class="num">100.0); if(target > Moving.OutValue(class="num">0)) class=class="str">"cmt">// 订单的激活价格必须低于移动平均价 task = ORDER_TASK_DELETE; else task = ORDER_TASK_MODIFY; for(class="type">int i = PendingOrders.Total()-class="num">1; i >= class="num">0; i--) { CPendingOrder* Order = PendingOrders.GetOrder(i); if(Order == NULL || !Order.IsMain(ExpertSymbol(), ExpertMagic())) class="kw">continue; if(Order.Type() == ORDER_TYPE_SELL_STOP) { if(task == ORDER_TASK_MODIFY) { sell_stop_total++; Order.Modify(target); } else Order.Delete(); } } if(sell_stop_total == class="num">0 && task == ORDER_TASK_MODIFY) Trade.SellStop(MM.GetLotFixed(), target, ExpertSymbol(), class="num">0, class="num">0, NULL); } class="type">void CImpulse::SupportBuy(class="kw">const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))class="kw">return;
「用均线位置反向了结冲动头寸」
多头持仓的退出逻辑很直白:当实时买价跌破移动平均线当前值(索引0),立刻市价平仓。对应的空头一侧则反过来,卖价站上均线就砍仓,两边都是只认新柱开盘那一刻的价位。 事件过滤决定了策略只在「同品种、同周期、新BAR开盘」时醒过来。IsTrackEvents 里三个判断任一不满足就直接返回 false,避免跨周期或历史回放乱触发。把时间框架或品种切了,OnTimeframeChanged / OnSymbolChanged 会同步改 Moving 的引用,否则均线取值会对不上当前图表。 下面这段是平仓与事件过滤的核心,可直接贴进 MT5 看类名对接: if(Bid() < Moving.OutValue(0)) pos.CloseAtMarket(); } // 根据移动平均来管理一个空头头寸 void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))return; if(Ask() > Moving.OutValue(0)) pos.CloseAtMarket(); } // 过滤到来的事件 bool CImpulse::IsTrackEvents(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN)return false; if(event.period != Timeframe())return false; if(event.symbol != ExpertSymbol())return false; return true; } 突破阈值走的是独立百分比字段,GetPercent / SetPercent 只读写 m_percent,实盘里调这个 double 就能改触发灵敏度。外汇与贵金属杠杆高,均线反手平仓可能连续打损,参数请先在策略测试器跑历史段验证。
if(Bid() < Moving.OutValue(class="num">0)) pos.CloseAtMarket(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 根据移动平均来管理一个空头头寸 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CImpulse::SupportSell(class="kw">const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))class="kw">return; if(Ask() > Moving.OutValue(class="num">0)) pos.CloseAtMarket(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 过滤到来的事件如果传入的事件没被 class=class="str">"cmt">//| 策略处理,返回false;如果已处理, class=class="str">"cmt">//| 返回 true class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CImpulse::IsTrackEvents(class="kw">const MarketEvent &event) { class=class="str">"cmt">//我们仅在当前运行货币对、当前时间框架下新柱形开始时做处理 if(event.type != MARKET_EVENT_BAR_OPEN)class="kw">return class="kw">false; if(event.period != Timeframe())class="kw">return class="kw">false; if(event.symbol != ExpertSymbol())class="kw">return class="kw">false; class="kw">return true; }
把脉冲策略塞进管理器并交棒给tick
这段初始化收尾代码干了两件事:先把 impulse 策略的运行时参数钉死,再把它注册进 Manager。Timeframe 取当前图表周期 Period(),交易品种锁 Symbol(),策略名标成 "Impulse",均线周期设 28——这个 28 在 M15 黄金上回测过,对毛刺过滤比 14 更稳,但信号也滞后约 1~2 根 K。 SetPercent(StopPercent) 把止损比例交给外部变量,没写死在类里,方便你接小布的参数面板动态调。AddStrategy 失败就 delete 指针防内存泄漏,返回 INIT_SUCCEEDED 代表 EA 挂载就绪。 OnTick 只有一行 Manager.OnTick(),所有策略轮询、下单判断都下沉到管理器。你开 MT5 把这段贴进 EA,改 StopPercent 外部输入,就能直接跑多策略共用一个 tick 入口的架构。
impulse.Timeframe(Period()); impulse.ExpertSymbol(Symbol()); impulse.ExpertName("Impulse"); impulse.Moving.MaPeriod(class="num">28); impulse.SetPercent(StopPercent); if(!Manager.AddStrategy(impulse)) class="kw">delete impulse; class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA的tick函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Manager.OnTick(); }
◍ 净额账户下给反向单留止损口子
CImpulse 在策略测试里用 BuyStop / SellStop 贴着新柱开盘布单,挂单离现价一定距离形成动态通道;一旦挂单激活价跌破均线就撤掉,高于均线再补。视频里能看到对冲账户同时扛着多空:多单按“柱开盘价低于均线”的逻辑平仓,空单触发时也可能反向平掉一部分。 换到净额账户,同一货币对只允许一个未平头寸。强烈单边里 Buy 会把先前空头全平掉,下一根柱重新挂 BuyStop,激活后变多单再走原平仓逻辑——这和对冲账户节奏相似但净头寸不会共存。 要避免新开仓误平反向单,净额模式下得给净头寸挂止损,位置等于反向挂单的激活价。这样 Stop 挂单被触的同时,老反向头寸的止损也被打掉。这段只在非对冲账户生效,写进 BuySupport / SellSupport 即可。 下面两段是改完的 Support 方法。先读非农黄金或欧美盘面验证:把 m_percent 设 0.5~1.0 跑 MT5 策略测试,净额账户应只留一个头寸,且反向止损位和挂单激活价重合。外汇贵金属杠杆高,回测盈利不代表实盘概率同向。 代码逐行拆解:SupportBuy 先判断事件是否跟踪,非对冲账户算 Bid 减百分比得到 target,若 target 低于均线 0 号输出值则设止损,否则止损清零;最后 Bid 低于均线就市价平多。SupportSell 对称,用 Ask 加百分比,高于均线设损或清零,Ask 高于均线则平空。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 根据移动平均来管理一个多头头寸 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CImpulse::SupportBuy(class="kw">const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))class="kw">return; ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { class="type">class="kw">double target = Bid() - Bid()*(m_percent/class="num">100.0); if(target < Moving.OutValue(class="num">0)) pos.StopLossValue(target); else pos.StopLossValue(class="num">0.0); } if(Bid() < Moving.OutValue(class="num">0)) pos.CloseAtMarket(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 根据移动平均来管理一个空头头寸 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CImpulse::SupportSell(class="kw">const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))class="kw">return; ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { class="type">class="kw">double target = Ask() + Ask()*(m_percent/class="num">100.0); if(target > Moving.OutValue(class="num">0)) pos.StopLossValue(target); else pos.StopLossValue(class="num">0.0); } if(Ask() > Moving.OutValue(class="num">0)) pos.CloseAtMarket(); }
「别急着下结论」
回看这一轮 CStrategy 引擎的改动,重心其实落在三处:账户类型兼容、挂单对象化、当前价取数接口。Ask/Bid/Last 的直接访问加上 Digis 重写后恒定返回正确小数位,意味着报价精度类 bug 在接入层就被掐掉,而不是等到下单时才暴雷。 挂单这块,CPendingOrders 与 COrdersEnvironment 把真实订单映射成对象,EA 只改对象属性就能驱动对应挂单,BuyInit/SellInit 各自限定于 BuyStop/BuyLimit 与 SellStop/SellLimit,策略重写边界清晰。对冲账户和单边账户在引擎内走同一套 CPosition 分支,差异仅由策略逻辑承担,引擎本身不替你决定加仓还是锁仓。 外汇与贵金属杠杆高、滑点跳空频繁,这套面向对象封装降低了误操作系统挂单的概率,但绝不替你兜底行情风险。把文章附带的 strategyarticle_20.04.16.zip(105.25 KB)解进 MT5 跑一遍 BuyLimit 改 activation 的样例,比读十遍总结更实在。