跨平台智能交易系统: 停止位·进阶篇
(2/3)·当订单管理器被停止位逻辑撑爆,CStop 如何把复杂度拆出去
「挂单对象的止损止盈接口长这样」
在 MT5 的自定义订单管理类里,挂单(pending order)相关的属性大多用一对重载方法暴露:带参的是 setter,无参 const 的是 getter。比如 StopLoss(const double) 写止损价位,StopLoss(void) const 读回来,TakeProfit 同理。 OCO(const bool) 和 OCO(void) const 控制是否启用一键取消组(One-Cancels-Other),Pending(void) const 直接返回当前是否挂单状态。StopLossVisible / TakeProfitVisible 管画线显隐,StopLossStyle / TakeProfitStyle 接 ENUM_LINE_STYLE 改虚线实线。 SymbolName(void) 返回品种名,Volume(double) 设手数、Volume(void) const 读手数,Virtual(void) const 判断是不是虚拟单。下面这段是原始声明,直接贴进头文件就能用。 别把 getter 的 const 漏掉 漏掉 const 的话,把这个对象传进只读函数时会编译报错;MT5 编译器对 const 正确性卡得较死,改完接口记得两个重载都对齐。
class="type">void Name(class="kw">const class="type">class="kw">string); class="type">class="kw">string Name(class="type">void) class="kw">const; class="type">void OCO(class="kw">const class="type">bool oco); class="type">bool OCO(class="type">void) class="kw">const; class="type">bool Pending(class="type">void) class="kw">const; class="type">void StopLoss(class="kw">const class="type">class="kw">double); class="type">class="kw">double StopLoss(class="type">void) class="kw">const; class="type">void StopLossColor(class="kw">const class="type">class="kw">color); class="type">bool StopLossCustom(class="type">void); class="type">void StopLossName(class="kw">const class="type">class="kw">string); class="type">class="kw">string StopLossName(class="type">void) class="kw">const; class="type">void StopLossVisible(class="kw">const class="type">bool); class="type">bool StopLossVisible(class="type">void) class="kw">const; class="type">void StopLossStyle(class="kw">const ENUM_LINE_STYLE); class="type">void StopType(class="kw">const ENUM_STOP_TYPE); ENUM_STOP_TYPE StopType(class="type">void) class="kw">const; class="type">class="kw">string SymbolName(class="type">void); class="type">void TakeProfit(class="kw">const class="type">class="kw">double); class="type">class="kw">double TakeProfit(class="type">void) class="kw">const; class="type">void TakeProfitColor(class="kw">const class="type">class="kw">color); class="type">bool TakeProfitCustom(class="type">void); class="type">void TakeProfitName(class="kw">const class="type">class="kw">string); class="type">class="kw">string TakeProfitName(class="type">void) class="kw">const; class="type">void TakeProfitStyle(class="kw">const ENUM_LINE_STYLE); class="type">void TakeProfitVisible(class="kw">const class="type">bool); class="type">bool TakeProfitVisible(class="type">void) class="kw">const; class="type">bool Virtual(class="type">void) class="kw">const; class="type">void Volume(class="type">class="kw">double); class="type">class="kw">double Volume(class="type">void) class="kw">const;
◍ 订单停止逻辑的类接口拆解
在 MT5 的 EA 架构里,停止单(止损、止盈、挂单)的管理往往被抽象成一个独立类,通过一组虚函数暴露给派生层去实现。下面这段声明直接给出了该类对停止订单的「检查—删除—改单—创建—计价—尾随」全链路接口。 void VolumeType(const ENUM_VOLUME_TYPE); ENUM_VOLUME_TYPE VolumeType(void) const; //--- 停止订单检查 virtual bool CheckStopLoss(COrder*,COrderStop*); virtual bool CheckTakeProfit(COrder*,COrderStop*); virtual bool CheckStopOrder(ENUM_STOP_MODE,COrder*,COrderStop*)=0; virtual bool DeleteStopOrder(const ulong)=0; virtual bool DeleteMarketStop(const ulong)=0; virtual bool OrderModify(const ulong,const double); //--- 停止订单对象创建 virtual CStopLine *CreateEntryObject(const long,const string,const int,const double); virtual CStopLine *CreateStopLossObject(const long,const string,const int,const double); virtual CStopLine *CreateTakeProfitObject(const long,const string,const int,const double); //--- 停止订单价格计算 virtual bool Refresh(const string); virtual double StopLossCalculate(const string,const ENUM_ORDER_TYPE,const double); virtual double StopLossCustom(const string,const ENUM_ORDER_TYPE,const double); virtual double StopLossPrice(COrder*,COrderStop*); virtual double StopLossTicks(const ENUM_ORDER_TYPE,const double); virtual double TakeProfitCalculate(const string,const ENUM_ORDER_TYPE,const double); virtual double TakeProfitCustom(const string,const ENUM_ORDER_TYPE,const double); virtual double TakeProfitPrice(COrder*,COrderStop*); virtual double TakeProfitTicks(const ENUM_ORDER_TYPE,const double); //--- 尾随 virtual bool Add(CTrails*); virtual double CheckTrailing(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET); 逐行看:前两句是成交量类型的 setter/getter;CheckStopLoss / CheckTakeProfit 负责校验挂好的止损止盈是否合规,而 CheckStopOrder 是纯虚函数(=0),意味着具体触发逻辑必须由子类写死。DeleteStopOrder 和 DeleteMarketStop 也是纯虚,分别针对挂单和市价单上的停止位做移除。 CreateEntryObject 等三个工厂方法,入参都是 (long 魔术码, string 品种, int 周期, double 价格),返回 CStopLine*,说明停止线对象是在运行时按品种动态生成的。价格计算组里 StopLossCalculate / StopLossCustom 区分了「按算法算」和「按自定义算」,StopLossTicks 则直接吃 ENUM_ORDER_TYPE + 点数换算成绝对价——这类接口在回测 EURUSD 上,若 tick 尺寸取 0.00001,30 点止损会返回 0.00030 的偏移量。 尾随部分只有 Add(CTrails*) 和 CheckTrailing(...) 两个虚函数,后者入参带 ENUM_TRAIL_TARGET 枚举,表明尾随目标(止损线 or 止盈线)是在调用时指定的。外汇与贵金属杠杆高,这类自动停止管理若参数误设,可能瞬间放大回撤,上 MT5 跑之前务必用策略测试器单品种验证。
class="type">void VolumeType(class="kw">const ENUM_VOLUME_TYPE); ENUM_VOLUME_TYPE VolumeType(class="type">void) class="kw">const; class=class="str">"cmt">//--- 停止订单检查 class="kw">virtual class="type">bool CheckStopLoss(COrder*,COrderStop*); class="kw">virtual class="type">bool CheckTakeProfit(COrder*,COrderStop*); class="kw">virtual class="type">bool CheckStopOrder(ENUM_STOP_MODE,COrder*,COrderStop*)=class="num">0; class="kw">virtual class="type">bool DeleteStopOrder(class="kw">const class="type">class="kw">ulong)=class="num">0; class="kw">virtual class="type">bool DeleteMarketStop(class="kw">const class="type">class="kw">ulong)=class="num">0; class="kw">virtual class="type">bool OrderModify(class="kw">const class="type">class="kw">ulong,class="kw">const class="type">class="kw">double); class=class="str">"cmt">//--- 停止订单对象创建 class="kw">virtual CStopLine *CreateEntryObject(class="kw">const class="type">long,class="kw">const class="type">class="kw">string,class="kw">const class="type">int,class="kw">const class="type">class="kw">double); class="kw">virtual CStopLine *CreateStopLossObject(class="kw">const class="type">long,class="kw">const class="type">class="kw">string,class="kw">const class="type">int,class="kw">const class="type">class="kw">double); class="kw">virtual CStopLine *CreateTakeProfitObject(class="kw">const class="type">long,class="kw">const class="type">class="kw">string,class="kw">const class="type">int,class="kw">const class="type">class="kw">double); class=class="str">"cmt">//--- 停止订单价格计算 class="kw">virtual class="type">bool Refresh(class="kw">const class="type">class="kw">string); class="kw">virtual class="type">class="kw">double StopLossCalculate(class="kw">const class="type">class="kw">string,class="kw">const ENUM_ORDER_TYPE,class="kw">const class="type">class="kw">double); class="kw">virtual class="type">class="kw">double StopLossCustom(class="kw">const class="type">class="kw">string,class="kw">const ENUM_ORDER_TYPE,class="kw">const class="type">class="kw">double); class="kw">virtual class="type">class="kw">double StopLossPrice(COrder*,COrderStop*); class="kw">virtual class="type">class="kw">double StopLossTicks(class="kw">const ENUM_ORDER_TYPE,class="kw">const class="type">class="kw">double); class="kw">virtual class="type">class="kw">double TakeProfitCalculate(class="kw">const class="type">class="kw">string,class="kw">const ENUM_ORDER_TYPE,class="kw">const class="type">class="kw">double); class="kw">virtual class="type">class="kw">double TakeProfitCustom(class="kw">const class="type">class="kw">string,class="kw">const ENUM_ORDER_TYPE,class="kw">const class="type">class="kw">double); class="kw">virtual class="type">class="kw">double TakeProfitPrice(COrder*,COrderStop*); class="kw">virtual class="type">class="kw">double TakeProfitTicks(class="kw">const ENUM_ORDER_TYPE,class="kw">const class="type">class="kw">double); class=class="str">"cmt">//--- 尾随 class="kw">virtual class="type">bool Add(CTrails*); class="kw">virtual class="type">class="kw">double CheckTrailing(class="kw">const class="type">class="kw">string,class="kw">const ENUM_ORDER_TYPE,class="kw">const class="type">class="kw">double,class="kw">const class="type">class="kw">double,class="kw">const ENUM_TRAIL_TARGET);
止损止盈的基类算法怎么落锤
在 MT5 的 EA 架构里,把止损止盈的计算抽进 CStopBase 这类基类,能让多品种、多订单类型的逻辑只写一遍。下面这段实现用 m_stoploss 和 m_takeprofit 存的是「点数」,真正挂单价格靠乘 symbol.Point() 换算,避免手动算错最小变动价。 StopLossCalculate 先调 Refresh(symbol) 拉最新行情,失败直接返回 0,相当于这次计算作废。买类订单(市价买、买停、买限)用 price 减去止损点数乘 Point,卖类则加回去——方向反着来,这是裸价格行为里最容易被忽略的符号坑。 TakeProfitCalculate 结构对称:买类加点数、卖类减点数。两个函数对 ORDER_TYPE_BUY_LIMIT 这类挂单同样生效,说明基类不区分市价与挂单触发态,只在入场价层面做静态测算。 虚函数表里 CreateObject、LotSizeCalculate、CloseStop 等留了 virtual 接口,其中 CloseStop 被标成 =0 纯虚,派生类必须自己写离场规则。外汇和贵金属杠杆高,这类自动测算若 Point 乘数设错,可能让止损偏出几十点,实盘前务必在策略测试器用 EURUSD 的 0.00001 点差验证一遍。
class="kw">virtual CStopLine *CreateObject(class="kw">const class="type">long,class="kw">const class="type">class="kw">string,class="kw">const class="type">int,class="kw">const class="type">class="kw">double); class="kw">virtual class="type">class="kw">double LotSizeCalculate(COrder*,COrderStop*); class="kw">virtual class="type">bool GetClosePrice(class="kw">const class="type">class="kw">string,class="kw">const ENUM_ORDER_TYPE,class="type">class="kw">double&); class="kw">virtual class="type">bool CloseStop(COrder*,COrderStop*,class="kw">const class="type">class="kw">double)=class="num">0; class="kw">virtual class="type">void Deinit(class="type">void); class="kw">virtual class="type">void DeinitSymbol(class="type">void); class="kw">virtual class="type">void DeinitTrade(class="type">void); class="kw">virtual class="type">void DeinitTrails(class="type">void); }; class="type">class="kw">double CStopBase::StopLossCalculate(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_ORDER_TYPE type,class="kw">const class="type">class="kw">double price) { if(!Refresh(symbol)) class="kw">return class="num">0; if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_LIMIT) class="kw">return price-m_stoploss*m_symbol.Point(); else if(type==ORDER_TYPE_SELL || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_LIMIT) class="kw">return price+m_stoploss*m_symbol.Point(); class="kw">return class="num">0; } class="type">class="kw">double CStopBase::TakeProfitCalculate(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_ORDER_TYPE type,class="kw">const class="type">class="kw">double price) { if(!Refresh(symbol)) class="kw">return class="num">0; if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_LIMIT) class="kw">return price+m_takeprofit*m_symbol.Point(); else if(type==ORDER_TYPE_SELL || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_LIMIT) class="kw">return price-m_takeprofit*m_symbol.Point(); class="kw">return class="num">0; }
「止损容器类的基类骨架」
在订单管理器里挂止损逻辑时,CStops 本身只是个壳,真正承担容器职责的是它的基类 CStopsBase。它从 CArrayObj 派生,意味着底层直接复用 MQL5 的对象数组能力来存一堆 CStop 实例,不用自己写链表。 基类里有两个受保护成员很关键:m_active 控制该组止损是否参与实时判定,m_event_man 指向事件聚合器,便于把止损触发事件广播给盯盘面板。m_container 则反向持有上层容器对象,形成双向引用。 看下面的类声明,Type() 直接返回 CLASS_TYPE_STOPS,这是运行时区分止损容器与其它管理类对象的硬标识。Init() 要求传入品种管理器和账户信息指针,说明止损计算会依赖实时点差与保证金数据,外汇和贵金属品种因杠杆与波动差异,初始化后务必Validate()自检一遍。 Main() 返回主止损对象指针,通常对应最先触发的那一层;CreateElement() 按索引动态建元素,方便策略在加载时批量生成多个止损档位。
class CStopsBase : class="kw">public CArrayObj { class="kw">protected: class="type">bool m_active; CEventAggregator *m_event_man; CObject *m_container; class="kw">public: CStopsBase(class="type">void); ~CStopsBase(class="type">void); class="kw">virtual class="type">int Type(class="type">void) class="kw">const {class="kw">return CLASS_TYPE_STOPS;} class=class="str">"cmt">//--- 初始化 class="kw">virtual class="type">bool Init(CSymbolManager*,CAccountInfo*,CEventAggregator*); class="kw">virtual CObject *GetContainer(class="type">void); class="kw">virtual class="type">void SetContainer(CObject*); class="kw">virtual class="type">bool Validate(class="type">void) class="kw">const; class=class="str">"cmt">//--- 赋值和取值 class="kw">virtual class="type">bool Active(class="type">void) class="kw">const; class="kw">virtual class="type">void Active(class="kw">const class="type">bool); class="kw">virtual CStop *Main(class="type">void); class=class="str">"cmt">//--- 复原 class="kw">virtual class="type">bool CreateElement(class="kw">const class="type">int); };
◍ 订单与止损逻辑的实例配对关系
COrderStop 是某笔具体仓位对 CStop 规则的落地实现。一个仓位最多由 COrderStop 生成一个对应实例,但多个 COrderStop 可以共用同一个 CStop 定义。 若 EA 里挂了 3 个不同的 CStop 实例,理想状态下每笔 COrder 也应配套 3 个 COrderStop。跑过 1000 笔交易时,COrderStop 实例会膨胀到 1000 * 3 = 3000 个,而 CStop 定义始终只有 3 个,内存开销差异明显。 实际写 EA 不用手动 new 一个 COrderStop,它被 COrder 依据具体 CStop 自动实例化,开发者只管往 COrderStops 容器里塞规则。 下面这段 COrderStopBase 基类定义了单笔停止实例的核心字段与接口,开 MT5 建个类继承它就能直接调。 [CODE] 里 m_stoploss / m_takeprofit 用 CArrayDouble 存,说明支持多价位止盈止损;m_stop_type 和 m_stop_name 用来区分逻辑分支,调试时靠这两个量定位哪条规则生效。
<span class="keyword">class</span> COrderStopBase : <span class="keyword">class="kw">public</span> CObject { <span class="keyword">class="kw">protected</span>: <span class="keyword">class="type">bool</span> m_active; <span class="comment">class=class="str">"cmt">//--- 停止位参数</span> <span class="keyword">class="type">class="kw">double</span> m_volume; CArrayDouble m_stoploss; CArrayDouble m_takeprofit; <span class="keyword">class="type">class="kw">ulong</span> m_stoploss_ticket; <span class="keyword">class="type">class="kw">ulong</span> m_takeprofit_ticket; <span class="keyword">class="type">bool</span> m_stoploss_closed; <span class="keyword">class="type">bool</span> m_takeprofit_closed; <span class="keyword">class="type">bool</span> m_closed; ENUM_STOP_TYPE m_stop_type; <span class="keyword">class="type">class="kw">string</span> m_stop_name; <span class="comment">class=class="str">"cmt">//--- 主订单对象</span> COrder *m_order; <span class="comment">class=class="str">"cmt">//--- 停止位对象</span> CStop *m_stop; CStopLine *m_objentry; CStopLine *m_objsl; CStopLine *m_objtp; COrderStops *m_order_stops; <span class="keyword">class="kw">public</span>: COrderStopBase(<span class="keyword">class="type">void</span>); ~COrderStopBase(<span class="keyword">class="type">void</span>); <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">int</span> Type(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span> {<span class="keyword">class="kw">return</span> CLASS_TYPE_ORDERSTOP;} <span class="comment">class=class="str">"cmt">//--- 初始化</span> <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">void</span> Init(COrder*,CStop*,COrderStops*); <span class="keyword">class="kw">virtual</span> COrderStops *GetContainer(<span class="keyword">class="type">void</span>); <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">void</span> SetContainer(COrderStops*); <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">void</span> Show(<span class="keyword">class="type">bool</span>); <span class="comment">class=class="str">"cmt">//--- 取值和赋值 </span> <span class="keyword">class="type">bool</span> Active(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span>; <span class="keyword">class="type">void</span> Active(<span class="keyword">class="type">bool</span> active); <span class="keyword">class="type">class="kw">string</span> EntryName(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span>; <span class="keyword">class="type">class="kw">ulong</span> MainMagic(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span>; <span class="keyword">class="type">class="kw">ulong</span> MainTicket(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span>; <span class="keyword">class="type">class="kw">double</span> MainTicketPrice(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span>; <span class="macro">ENUM_ORDER_TYPE</span> MainTicketType(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span>; COrder *Order(<span class="keyword">class="type">void</span>); <span class="keyword">class="type">void</span> Order(COrder*); CStop *Stop(<span class="keyword">class="type">void</span>); <span class="keyword">class="type">void</span> Stop(CStop*); <span class="keyword">class="type">bool</span> StopLoss(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span>)
止损止盈与生命周期的接口骨架
下面这组声明来自一个持仓管理类的头文件尾部,集中暴露了止损、止盈以及对象清理的读写接口。外汇与贵金属杠杆高,SL/TP 的数值误差可能直接放大为爆仓风险,因此这类封装必须区分「取值」和「设值」两个重载。 StopLoss 有两态:无参版本返回 const double 表示当前止损价,带 const int 参数的版本用于按索引取历史止损,返回可修改的 double。StopLossClosed(bool) 与无参 bool 版本成对,控制并查询止损线是否已平仓关闭。 TakeProfit 的重载结构完全一致:TakeProfit(double) 返回 bool 代表设置是否成功,无参 const 版本拿当前值,带 int 的拿指定单的止盈。TakeProfitLast 取最后一次触发的止盈价,用于 trailing 回看。 尾部虚函数才是真正干活的地方。Check(double&) 是纯虚,派生类必须实现价格校验;Close、CheckTrailing、DeleteStopLoss、DeleteTakeProfit 都是 virtual bool,默认可能返回 false,需在策略类里重写才能真正删线平仓。开 MT5 把这段粘进自定义 CPosition 派生类,先重写 Check 再测 DeleteStopLoss 是否真从图表消失。
class="type">class="kw">double StopLoss(class="type">void) class="kw">const; class="type">class="kw">double StopLoss(class="kw">const class="type">int); class="type">void StopLossClosed(class="kw">const class="type">bool); class="type">bool StopLossClosed(class="type">void); class="type">class="kw">double StopLossLast(class="type">void) class="kw">const; class="type">class="kw">string StopLossName(class="type">void) class="kw">const; class="type">void StopLossTicket(class="kw">const class="type">class="kw">ulong); class="type">class="kw">ulong StopLossTicket(class="type">void) class="kw">const; class="type">void StopName(class="kw">const class="type">class="kw">string); class="type">class="kw">string StopName(class="type">void) class="kw">const; class="type">bool TakeProfit(class="kw">const class="type">class="kw">double); class="type">class="kw">double TakeProfit(class="type">void) class="kw">const; class="type">class="kw">double TakeProfit(class="kw">const class="type">int); class="type">void TakeProfitClosed(class="kw">const class="type">bool); class="type">bool TakeProfitClosed(class="type">void); class="type">class="kw">double TakeProfitLast(class="type">void) class="kw">const; class="type">class="kw">string TakeProfitName(class="type">void) class="kw">const; class="type">void TakeProfitTicket(class="kw">const class="type">class="kw">ulong); class="type">class="kw">ulong TakeProfitTicket(class="type">void) class="kw">const; class="type">void Volume(class="kw">const class="type">class="kw">double); class="type">class="kw">double Volume(class="type">void) class="kw">const; class=class="str">"cmt">//--- 检查 class="kw">virtual class="type">void Check(class="type">class="kw">double&)=class="num">0; class="kw">virtual class="type">bool Close(class="type">void); class="kw">virtual class="type">bool CheckTrailing(class="type">void); class="kw">virtual class="type">bool DeleteChartObject(class="kw">const class="type">class="kw">string); class="kw">virtual class="type">bool DeleteEntry(class="type">void); class="kw">virtual class="type">bool DeleteStopLines(class="type">void); class="kw">virtual class="type">bool DeleteStopLoss(class="type">void); class="kw">virtual class="type">bool DeleteTakeProfit(class="type">void);