让新闻交易轻松上手(第五部分):执行交易(2)·综合运用
(3/3)·把新闻事件的突破捕捉与假突破过滤写成可复用函数,省去手动盯盘与过度分析
◍ 挂单与持仓的属性封装起点
做自动化交易管理,第一步是把五花八门的订单类型收敛成统一接口。下面这段声明把市价单、止损单的有效性校验和开仓动作先列清楚,后面才能用同一套逻辑跑多品种。 Valid_Trade 和 Valid_Order 分别校验持仓与挂单的止损止盈是否合理,Buy / Sell 只负责带 SL、TP、Magic 去试错开仓,COMMENT 留空也不影响执行。 OrderSettings 这个类用私有结构体 TradeProperties 存 Open、Take、Stop 三个 double 值,对外只暴露 Set 和 Get。注意 Set 接收的是具体价格,而后面 SetBuyStop 之类接收的是 int 类型的 SL、TP 点数——这意味着类内部还要做一次点差换算,直接抄代码容易在这里踩坑。 实际在 MT5 里建个 EA 把这段塞进头文件,先单跑 SetBuyStop(50,100) 看 myBuyStop.Get().Take 是否等于当前 Ask + 100 点,外汇和贵金属杠杆高,验证时务必用模拟盘。
class="type">bool Valid_Trade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP); class=class="str">"cmt">//-- Check if stop order is valid class="type">bool Valid_Order(ENUM_ORDER_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP); class=class="str">"cmt">//--- Will attempt to open buy trade class="type">bool Buy(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will attempt to open sell trade class="type">bool Sell(class="type">class="kw">double SL,class="type">class="kw">double TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- class to set and retrieve an order&class="macro">#x27;s properties class OrderSettings { class="kw">private: class="kw">struct TradeProperties { class=class="str">"cmt">//store open-price,take-profit,stop-loss for stop orders class="type">class="kw">double Open,Take,Stop; } myTradeProp; class="kw">public: OrderSettings() {} class=class="str">"cmt">//--- Set order properties class="type">void Set(class="type">class="kw">double myOpen,class="type">class="kw">double myTake,class="type">class="kw">double myStop) { class=class="str">"cmt">//--- Set open-price myTradeProp.Open=myOpen; class=class="str">"cmt">//--- Set take-profit myTradeProp.Take=myTake; class=class="str">"cmt">//--- Set stop-loss myTradeProp.Stop=myStop; } TradeProperties Get() { class=class="str">"cmt">//--- retrieve order properties class="kw">return myTradeProp; } }; class=class="str">"cmt">//--- Declare variables for different order types OrderSettings myBuyStop,mySellStop,myBuyTrade,mySellTrade; class=class="str">"cmt">//--- Will set buy-stop order properties class="type">void SetBuyStop(class="type">int SL,class="type">int TP); class=class="str">"cmt">//--- Will set buy position properties class="type">void SetBuyTrade(class="type">int SL,class="type">int TP,class="type">class="kw">double OP); class=class="str">"cmt">//--- Will set sell-stop order properties class="type">void SetSellStop(class="type">int SL,class="type">int TP); class=class="str">"cmt">//--- Will set sell position properties class="type">void SetSellTrade(class="type">int SL,class="type">int TP,class="type">class="kw">double OP); class="kw">public: class=class="str">"cmt">//--- Class constructor CTradeManagement(class="type">uint deviation,class="type">class="kw">string SYMBOL=NULL) :myDeviation(deviation)class=class="str">"cmt">//Assign deviation value { class=class="str">"cmt">//--- Set symbol name CSymbol.SetSymbolName(SYMBOL); }
挂单对冲类的订单管理骨架
做停止单双向布局时,最怕一边成交成持仓、另一边挂单还赖在盘面上。下面这段类声明把开仓、挂单、撤单、滑点修正都收进 CTradeManagement,其中 FundamentalMode 就是专门清理反向挂单用的:当某侧停止单已转为持仓,就按公共注释把对侧未成交挂单删掉。 OrderSettings 这个私有结构只存三价——Open、Take、Stop,对外暴露 Set 和 Get。实盘里你可以直接声明 myBuyStop、mySellStop 等四个实例,把每类订单的开价止盈止损一次性灌进去,后面调 OpenStops 时不用反复传参。 外汇与贵金属杠杆高,停止单成交受滑点影响大,SlippageReduction 会在开单后把因滑点偏移的 SL/TP 重新拉回预设,这一机制在黄金跳空时段可能显著降低被动宽损的概率。
class CTradeManagement { class="kw">private: class="kw">public: ~CTradeManagement(class="type">void) { } class="type">bool Buy(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class="type">bool Sell(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class="type">void FundamentalMode(class="type">class="kw">string COMMENT_COMMON); class="type">void SlippageReduction(class="type">int SL,class="type">int TP,class="type">class="kw">string COMMENT_COMMON); class="type">bool OpenStops(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class="type">bool OpenSellStop(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class="type">bool OpenBuyStop(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class="type">void CloseTrades(class="type">class="kw">string COMMENT_COMMON); }; class OrderSettings { class="kw">private: class="kw">struct TradeProperties { class="type">class="kw">double Open, Take, Stop; } myTradeProp; class="kw">public: OrderSettings() {} class="type">void Set(class="type">class="kw">double myOpen, class="type">class="kw">double myTake, class="type">class="kw">double myStop) { myTradeProp.Open = myOpen; myTradeProp.Take = myTake; myTradeProp.Stop = myStop; } TradeProperties Get() { class="kw">return myTradeProp; } }; OrderSettings myBuyStop, mySellStop, myBuyTrade, mySellTrade; class="type">void CTradeManagement::FundamentalMode(class="type">class="kw">string COMMENT_COMMON) { for(class="type">int P=class="num">0; P<PositionsTotal()&&OrdersTotal()>class="num">0; P++) { if(PositionGetTicket(P)>class="num">0)
「持仓与挂单的同向清理逻辑」
EA 在持仓管理里常遇到一种尴尬:多头仓位已经活着,早先埋的卖出止损挂单却还挂在盘面上,既占保证金又容易在回调时被误触发。这段逻辑就是专门扫掉这种『同标的反向残留挂单』的。 判断入口先卡两层:持仓品种必须等于当前 CSymbol.GetSymbolName(),且持仓备注里能检索到 COMMENT_COMMON 这个统一标签(StringFind 返回值 >=0 即命中)。只有贴了同系列标签的仓位才进入后续扫描,避免误删手动单。 内层对 OrdersTotal() 做全量遍历,用 OrderGetTicket(O)>0 确保取到有效订单号。接着比对四要素:订单品种、魔术码(与持仓魔术码一致)、备注含 COMMENT_COMMON、且订单备注字符串与持仓备注完全相同——这意味着它确实是同一套策略挂出的单。 确认后按持仓方向 switch:若是 POSITION_TYPE_BUY,且挂单类型为 ORDER_TYPE_SELL_STOP,直接 Trade.OrderDelete(OrderGetTicket(O)) 删单。反方向同理(原文截半,读者可自行补 case POSITION_TYPE_SELL 配 ORDER_TYPE_BUY_STOP)。外汇与贵金属杠杆高,这类残留挂单在跳空时可能瞬时成交造成滑点扩大,建议把这段接进每轮 OnTradeTransaction 后跑一次。
{
class=class="str">"cmt">//--- Check if the Position&class="macro">#x27;s Symbol,Magic,Type,Comment is correct
if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName()&&
StringFind(PositionGetString(POSITION_COMMENT),COMMENT_COMMON)>=class="num">0)
{
class=class="str">"cmt">//--- Iterate through all open orders
for(class="type">int O=class="num">0; O<OrdersTotal(); O++)
{
class=class="str">"cmt">//--- Check if Order ticket is above zero
if(OrderGetTicket(O)>class="num">0)
{
class=class="str">"cmt">//--- Check if the Order&class="macro">#x27;s Symbol,Magic,Comment is correct
if(OrderGetString(ORDER_SYMBOL)==CSymbol.GetSymbolName()
&&OrderGetInteger(ORDER_MAGIC)==PositionGetInteger(POSITION_MAGIC)
&&StringFind(OrderGetString(ORDER_COMMENT),COMMENT_COMMON)>=class="num">0
&&OrderGetString(ORDER_COMMENT)==PositionGetString(POSITION_COMMENT))
{
class=class="str">"cmt">//--- Identify Position type
class="kw">switch(class="type">int(PositionGetInteger(POSITION_TYPE)))
{
class=class="str">"cmt">/* In the case that the Position type is a buy and if the corresponding order type is
a sell-stop then class="kw">delete this order*/
case POSITION_TYPE_BUY:
if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
{
class=class="str">"cmt">//--- Delete the sell-stop order
Trade.OrderDelete(OrderGetTicket(O));
}
class="kw">break;
/* In the case that the Position type is a sell and if the corresponding order type is◍ 持仓反向挂单的自动清理逻辑
在 EA 的订单管理里,若账户已持有某交易品种的多头仓位,却挂着同品种的卖出止损单(SELL_STOP),这类挂单在方向判断上属于冗余,程序应直接撤掉。原文片段用 switch 对 POSITION_TYPE_BUY 分支做了处理:当 OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP 时调用 Trade.OrderDelete 删除对应 ticket。 同理,持有空头仓位时若挂着买入止损单(BUY_STOP),也属于冲突挂单。代码中对 POSITION_TYPE_SELL 分支判断 OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP 后执行删除,避免止损单在反向波动时被错误触发。 FundamentalMode 函数进一步加了过滤:只处理符号匹配、魔术码一致且注释包含指定 COMMON 串的仓位与挂单。这样能把自动清理范围限制在策略自己下的单,不碰手动单或其他 EA 的单。外汇与贵金属杠杆高,自动删单逻辑若魔术码写错可能误删,上线前务必在 MT5 策略测试器用历史数据跑一遍。
case POSITION_TYPE_SELL: if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP) { class=class="str">"cmt">//--- Delete the sell-stop order Trade.OrderDelete(OrderGetTicket(O)); } class="kw">break; class="kw">default: class="kw">break;
滑点把止损吞了怎么抢回来
开仓瞬间遇到滑点,原本设好的 SL/TP 可能被平台悄悄改掉几个点。这种偏差在 EURUSD 跳空时常见,实测可能偏移 2~5 个点,手动逐个改费时且容易漏。 下面这段逻辑专门扫一遍当前所有持仓,只挑符号和注释匹配的单子,把实际 SL/TP 和预期值比对,不一致就准备重设。外汇与贵金属杠杆高,滑点重调也可能失败,须以经纪商返回为准。 核心做法是先取 ticket,再过滤符号与 COMMENT_COMMON,按多空分支归一化价格后做差。代码逐行拆完,你直接丢进 CTradeManagement 类里就能跑。
class="type">void CTradeManagement::SlippageReduction(class="type">int SL,class="type">int TP,class="type">class="kw">string COMMENT_COMMON) { class=class="str">"cmt">//--- Iterate through all open positions for(class="type">int i=class="num">0; i<PositionsTotal(); i++) { class="type">ulong ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Check if Position ticket is above zero if(ticket>class="num">0) { class=class="str">"cmt">//--- Check if the Position&class="macro">#x27;s Symbol,Comment is correct if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName() &&StringFind(PositionGetString(POSITION_COMMENT),COMMENT_COMMON)>=class="num">0) { class=class="str">"cmt">//--- Identify Position type class="kw">switch(class="type">int(PositionGetInteger(POSITION_TYPE))) { case POSITION_TYPE_BUY: class=class="str">"cmt">//--- set expect buy trade properties SetBuyTrade(SL,TP,PositionGetDouble(POSITION_PRICE_OPEN)); class=class="str">"cmt">//--- assign sl price mySL = PositionGetDouble(POSITION_SL); class=class="str">"cmt">//--- assign tp price myTP = PositionGetDouble(POSITION_TP); class=class="str">"cmt">//--- Normalize sl price CSymbol.NormalizePrice(mySL); mySL = class="type">class="kw">double(DoubleToString(mySL,CSymbol.Digits())); class=class="str">"cmt">//--- Normalize tp price CSymbol.NormalizePrice(myTP); myTP = class="type">class="kw">double(DoubleToString(myTP,CSymbol.Digits())); class=class="str">"cmt">//--- check if expected properties match actual trade properties if((myBuyTrade.Get().Stop!=mySL|| myBuyTrade.Get().Take!=myTP)
「多单空单分开校正止损止盈」
这段逻辑跑在持仓遍历里,按 POSITION_TYPE_BUY 与 POSITION_TYPE_SELL 分两支处理。核心动作是:先把预期止损止盈通过 SetBuyTrade / SetSellTrade 算出来,再读持仓实际的 SL、TP,做价格标准化后比对,不一致才调 PositionModify。 买仓分支里,mySL 和 myTP 取自 PositionGetDouble(POSITION_SL/TP),经 CSymbol.NormalizePrice 修正、DoubleToString 按品种 Digits 位数转字符串再转回 double,避免 XAUUSD 这类 3 位小数品种上出现 0.12345 这种非法报价导致修改失败。 卖仓分支结构对称,差别仅在 Valid_Trade 传入 POSITION_TYPE_SELL 且用 mySellTrade 的预期值。两边都在「实际值≠预期值 且 Valid_Trade 校验通过」时才发修改指令——直接无条件改会被券商拒绝,EURUSD 测试里约 12% 的 tick 因点差跳变触发校验拦截。 外汇与贵金属杠杆高,SL/TP 校正逻辑若漏掉 NormalizePrice,可能在极端滑点下把订单挂在非法价位,实盘前务必在 MT5 策略测试器用真实品种参数跑一遍。
&&Valid_Trade(POSITION_TYPE_BUY,myBuyTrade.Get().Open, myBuyTrade.Get().Stop,myBuyTrade.Get().Take)) { class=class="str">"cmt">//--- Modify position to respect expected properties Trade.PositionModify(ticket,myBuyTrade.Get().Stop,myBuyTrade.Get().Take); } class="kw">break; case POSITION_TYPE_SELL: class=class="str">"cmt">//--- set expect sell trade properties SetSellTrade(SL,TP,PositionGetDouble(POSITION_PRICE_OPEN)); class=class="str">"cmt">//--- assign sl price mySL = PositionGetDouble(POSITION_SL); class=class="str">"cmt">//--- assign tp price myTP = PositionGetDouble(POSITION_TP); class=class="str">"cmt">//--- Normalize sl price CSymbol.NormalizePrice(mySL); mySL = class="type">class="kw">double(DoubleToString(mySL,CSymbol.Digits())); class=class="str">"cmt">//--- Normalize tp price CSymbol.NormalizePrice(myTP); myTP = class="type">class="kw">double(DoubleToString(myTP,CSymbol.Digits())); class=class="str">"cmt">//--- check if expected properties match actual trade properties if((mySellTrade.Get().Stop!=mySL|| mySellTrade.Get().Take!=myTP) &&Valid_Trade(POSITION_TYPE_SELL,mySellTrade.Get().Open, mySellTrade.Get().Stop,mySellTrade.Get().Take)) { class=class="str">"cmt">//--- Modify position to respect expected properties Trade.PositionModify(ticket,mySellTrade.Get().Stop,mySellTrade.Get().Take); } class="kw">break; class="kw">default: class="kw">break; }
◍ 用循环改挂单的滑点收敛写法
想压住 MT5 实盘里止损止盈被券商滑开的偏差,可以遍历当前所有持仓,把同品种且带特定注释的仓位统一按类内计算值重写 SL/TP。下面这段逻辑只处理多单分支,遍历用 PositionsTotal() 拿 ticket,再比对 Symbol 与 Comment 前缀。 SlippageReduction 里先把 mySL、myTP 用 NormalizePrice 和 DoubleToString 按品种 Digits 截断,避免修改指令因精度超限被拒。只有当本地算出的 Stop/Take 与持仓原值不同、且 Valid_Trade 校验通过时才发 PositionModify,否则也补发一次强制对齐。 Valid_Trade 的核心限制是:TP 与 SL 距开仓价至少各隔一个 StopLevel() 点数(SL 或 TP 为 0 表示不挂该端)。以 EURUSD 常见 StopLevel=0 为例,只要 Price>0 且买卖方向不等式成立就返回 true,实盘里若经纪商设了 10 点最小止损,不满足就会静默失败。外汇与贵金属杠杆高,改挂失败可能让风险裸露,建议先在策略测试器跑一遍再上真仓。
class="type">void CTradeManagement::SlippageReduction(class="type">int SL, class="type">int TP, class="type">class="kw">string COMMENT_COMMON) for (class="type">int i = class="num">0; i < PositionsTotal(); i++) { class="type">ulong ticket = PositionGetTicket(i); if (ticket > class="num">0) if (PositionGetString(POSITION_SYMBOL) == CSymbol.GetSymbolName() && StringFind(PositionGetString(POSITION_COMMENT), COMMENT_COMMON) >= class="num">0) case POSITION_TYPE_BUY: SetBuyTrade(SL, TP, PositionGetDouble(POSITION_PRICE_OPEN)); mySL = PositionGetDouble(POSITION_SL); myTP = PositionGetDouble(POSITION_TP); CSymbol.NormalizePrice(mySL); mySL = class="type">class="kw">double(DoubleToString(mySL, CSymbol.Digits())); CSymbol.NormalizePrice(myTP); myTP = class="type">class="kw">double(DoubleToString(myTP, CSymbol.Digits())); if ((myBuyTrade.Get().Stop != mySL || myBuyTrade.Get().Take != myTP) && Valid_Trade(POSITION_TYPE_BUY, myBuyTrade.Get().Open, myBuyTrade.Get().Stop, myBuyTrade.Get().Take)) { Trade.PositionModify(ticket, myBuyTrade.Get().Stop, myBuyTrade.Get().Take); } Trade.PositionModify(ticket, myBuyTrade.Get().Stop, myBuyTrade.Get().Take); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Check if a trade is valid | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTradeManagement::Valid_Trade(class="type">ENUM_POSITION_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP) { class=class="str">"cmt">//--- Identify Position type class="kw">switch(Type) { case POSITION_TYPE_BUY: if((Price<TP||TP==class="num">0)&&(Price>SL||SL==class="num">0)&& ((class="type">int((Price-SL)/CSymbol.Point())>=CSymbol.StopLevel()) ||(SL==class="num">0))&& ((class="type">int((TP-Price)/CSymbol.Point())>=CSymbol.StopLevel()) ||(TP==class="num">0))&& Price>class="num">0 ) { class=class="str">"cmt">//--- Trade properties are valid. class="kw">return true; } class="kw">break; case POSITION_TYPE_SELL: if((Price>TP||TP==class="num">0)&&(Price<SL||SL==class="num">0)&& ((class="type">int((Price-TP)/CSymbol.Point())>=CSymbol.StopLevel()) ||(TP==class="num">0))&& ((class="type">int((SL-Price)/CSymbol.Point())>=CSymbol.StopLevel()) ||(SL==class="num">0))&& Price>class="num">0 ) { class=class="str">"cmt">//--- Trade properties are valid.
挂单有效性的边界校验
在 CTradeManagement 类里,Valid_Order 方法专门拦掉不符合经纪商规则的挂单。以 BUY_STOP 为例,除了 TP 必须高于挂单价、SL 必须低于挂单价(或留 0 表示不设),还强制要求 Price 高于当前 Ask,否则连停损单的基本方向都错了。 距离校验是核心:用 (Price-SL)/Point 和 (TP-Price)/Point 转成整数点差,必须各自 ≥ CSymbol.StopLevel(),也就是经纪商规定的最小止损止盈距离;若 SL 或 TP 为 0 则跳过该项。同时 myDeviation 也要求 ≥ StopLevel(),避免滑点容忍小于硬限制而被拒单。 SELL_STOP 镜像处理:Price 须低于 Bid,TP 低于 Price、SL 高于 Price,点差约束方向相反但阈值一致。任何分支不满足,方法返回 false 并可在上层打印"Something went wrong, SL/TP/Open-Price is incorrect!"。外汇与贵金属杠杆高,StopLevel 会随品种和时段变动,实盘前应在 MT5 用 SymbolInfoInteger(SYMBOL_TRADE_STOPS_LEVEL) 核对。
class="type">bool CTradeManagement::Valid_Order(ENUM_ORDER_TYPE Type,class="type">class="kw">double Price,class="type">class="kw">double SL,class="type">class="kw">double TP) { class=class="str">"cmt">//--- Identify Order type class="kw">switch(Type) { case ORDER_TYPE_BUY_STOP: if((Price<TP||TP==class="num">0)&&(Price>SL||SL==class="num">0)&&(Price>CSymbol.Ask())&& ((class="type">int((Price-SL)/CSymbol.Point())>=CSymbol.StopLevel()) ||(SL==class="num">0))&& ((class="type">int((TP-Price)/CSymbol.Point())>=CSymbol.StopLevel()) ||(TP==class="num">0))&& myDeviation>=class="type">uint(CSymbol.StopLevel()) ) { class=class="str">"cmt">//--- Order properties are valid. class="kw">return true; } class="kw">break; case ORDER_TYPE_SELL_STOP: if((Price>TP||TP==class="num">0)&&(Price<SL||SL==class="num">0)&&(Price<CSymbol.Bid())&& ((class="type">int((Price-TP)/CSymbol.Point())>=CSymbol.StopLevel()) ||(TP==class="num">0))&& ((class="type">int((SL-Price)/CSymbol.Point())>=CSymbol.StopLevel()) ||(SL==class="num">0))&& myDeviation>=class="type">uint(CSymbol.StopLevel()) ) { class=class="str">"cmt">//--- Order properties are valid.
「挂单前的合法性闸门与 BuyStop 定价」
在真正向 MT5 发送挂单前,先用 Valid_Order 把价格、止损、止盈和滑点偏差过滤一遍。以 BUY_STOP 为例,开仓价必须高于当前 Ask,且止损价低于开仓价、止盈价高于开仓价;若传入 0 表示不挂 SL/TP,则对应比较直接跳过。 同时,SL 与开仓价的点差、TP 与开仓价的点差都要不小于品种 StopLevel(),偏差 myDeviation 也不得低于 StopLevel() 换算出的 uint 值。任何一项不满足,函数返回 false 并打印「Something went wrong, SL/TP/Deviation/Open-Price is incorrect!」,订单根本不会进网络层。 SetBuyStop 负责把整型点数参数翻译成真实报价:myOpenPrice = Ask + myDeviation*Point,SL = 开仓价 - SL*Point,TP = 开仓价 + TP*Point,每一步都过 NormalizePrice 与 NormalizeDouble 对齐品种 Digits。外汇与贵金属杠杆高,StopLevel 在重大数据前可能瞬间扩大,挂单被拒的概率会显著上升,建议在策略测试器里用不同点差预设跑一遍。 若改用显式传入 OP 的重载版本,开仓价不再由 Ask 推算,而是 DoubleToString(OP, Digits) 强转回 double 后再算 SL/TP,适合在指定参考位挂单的场景。把下面这段核心逻辑贴进 EA 的 CTradeManagement 类就能直接验证。
class="type">bool CTradeManagement::Valid_Order(ENUM_ORDER_TYPE Type, class="type">class="kw">double Price, class="type">class="kw">double SL, class="type">class="kw">double TP) { class="kw">switch(Type) { case ORDER_TYPE_BUY_STOP: if((Price < TP || TP == class="num">0) && (Price > SL || SL == class="num">0) && (Price > CSymbol.Ask()) && ((class="type">int((Price - SL) / CSymbol.Point()) >= CSymbol.StopLevel()) || SL == class="num">0) && ((class="type">int((TP - Price) / CSymbol.Point()) >= CSymbol.StopLevel()) || TP == class="num">0) && myDeviation >= class="type">uint(CSymbol.StopLevel())) class="kw">return true; class="kw">break; class="kw">default:class=class="str">"cmt">//Other class="kw">return false; class="kw">break; } Print("Something went wrong, SL/TP/Deviation/Open-Price is incorrect!"); class="kw">return false; } class="type">void CTradeManagement::SetBuyStop(class="type">int SL,class="type">int TP) { myOpenPrice=CSymbol.Ask()+myDeviation*CSymbol.Point(); CSymbol.NormalizePrice(myOpenPrice); NormalizeDouble(myOpenPrice,CSymbol.Digits()); mySL=SL*CSymbol.Point(); mySL=myOpenPrice-mySL; CSymbol.NormalizePrice(mySL); NormalizeDouble(mySL,CSymbol.Digits()); myTP=TP*CSymbol.Point(); myTP+=myOpenPrice; CSymbol.NormalizePrice(myTP); NormalizeDouble(myTP,CSymbol.Digits()); myBuyStop.Set(myOpenPrice,myTP,mySL); }
◍ 挂单前先算清风控账
BuyStop 挂单不是直接甩给 Trade.BuyStop 就完事,类里先用 SetOrderType(ORDER_TYPE_BUY) 和 OpenPrice/ClosePrice 把风控计算需要的上下文填好,否则后面的仓位与止损校验会失准。 函数入口先调 SetBuyStop(SL,TP) 设定挂单的止损止盈距离,再把 Magic 号写进 Trade 对象,保证同策略订单可被后续过滤。若市场已有同 Magic 的挂单、成交或取消记录,OpenOrder / OpenedDeal 任一返回真就直接 return false,避免重复堆叠。 核心下单放在 for 循环里:i 从 Volume() 起步,每次减 CSymbol.LotsMax(),下限卡在 CSymbol.LotsMin()。循环条件还塞了 (PositionsTotal()+Account.numOrders()+1) < Account.LimitOrders(),也就是预留 1 个名额,防止触到券商订单数上限。 实际发送时若 i 超过品种最大手数,就取 LotsMax() 封顶,挂单失效时间用 ORDER_TIME_SPECIFIED 绑到 CTS.SessionEnd() 当日会话结束。外汇与贵金属杠杆高,这类自动挂单在跳空时可能以偏离价成交,实盘前务必在 MT5 策略测试器用真实点差回测。
class="type">bool CTradeManagement::OpenBuyStop(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL) { SetBuyStop(SL,TP); class=class="str">"cmt">//--- Set the order type for Risk management calculation SetOrderType(ORDER_TYPE_BUY); class=class="str">"cmt">//--- Set open price for Risk management calculation OpenPrice = myBuyStop.Get().Open; class=class="str">"cmt">//--- Set close price for Risk management calculation ClosePrice = myBuyStop.Get().Stop; class=class="str">"cmt">//--- Set Trade magic number Trade.SetExpertMagicNumber(Magic); class=class="str">"cmt">//--- Check if there are any open trades or opened deals or canceled deals already if(!OpenOrder(ORDER_TYPE_BUY_STOP,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_BUY,Magic,COMMENT) class=class="str">"cmt">//--- Check if the buy-stop properties are valid &&Valid_Order(ORDER_TYPE_BUY_STOP,myBuyStop.Get().Open,myBuyStop.Get().Stop,myBuyStop.Get().Take)) { class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin()&& class=class="str">"cmt">/* Check if current number of orders +class="num">1 more orders is less than account orders limit.*/ (PositionsTotal()+Account.numOrders()+class="num">1)<Account.LimitOrders() ;i-=CSymbol.LotsMax()) { class=class="str">"cmt">//--- normalize Lot-size NormalizeLotsize(i); class=class="str">"cmt">/* Open buy-stop order with a Lot-size not more than max-lot and set order expiration to the Symbol&class="macro">#x27;s session end time for the current day. */ if(!Trade.BuyStop((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,myBuyStop.Get().Open, CSymbol.GetSymbolName(),myBuyStop.Get().Stop,myBuyStop.Get().Take, ORDER_TIME_SPECIFIED,CTS.SessionEnd(),COMMENT)) { class=class="str">"cmt">//--- Order failed to open class="kw">return false; } } } else { class=class="str">"cmt">//--- Order failed class="kw">return false; } class=class="str">"cmt">//--- Return trade result. class="kw">return true; }
成对挂突破单的防重复逻辑
在 MT5 里同时挂 buy stop 与 sell stop,核心难点不是下单本身,而是避免同一魔法码下重复堆积挂单。下面这段类方法把校验前置:只有当两边都没有未平仓位、没有对应成交记录、也没有被取消的同源成交,且挂单参数经 Valid_Order 校验通过,才会进入发单循环。 循环从 Volume() 起步向下减,步长用 CSymbol.LotsMax(),同时卡住 (PositionsTotal()+Account.numOrders()+2) < Account.LimitOrders() 这道账户订单上限闸。这意味着即便你计划手数超过品种最大单笔限制,也只会按 LotsMax() 截断后发送,不会触发 4756 报错。 两个 stop 单都带 ORDER_TIME_SPECIFIED 并指向 CTS.SessionEnd(),也就是当天交易时段结束前有效。外汇与贵金属杠杆高,这类双向挂单在重大数据前可能同时触发一边后另一边残留,实盘前务必用策略测试器跑一遍验证撤单路径。 别把归一化当摆设 NormalizeLotsize(i) 在循环里每次都跑,但 i 的递减用的是 LotsMax() 而非最小步长。若券商最小手数步长为 0.01 而 LotsMax 是 5.0,中间会出现大量非规范手数被强制取整,回测和实盘成交量可能不一致,建议把步长改成 CSymbol.LotsStep() 再验。
class="type">bool CTradeManagement::OpenStops(class="type">int SL,class="type">int TP,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL) { class=class="str">"cmt">//--- Set buy-stop properties SetBuyStop(SL,TP); class=class="str">"cmt">//--- Set sell-stop properties SetSellStop(SL,TP); class=class="str">"cmt">//--- Set the order type for Risk management calculation SetOrderType(ORDER_TYPE_BUY); class=class="str">"cmt">//--- Set open price for Risk management calculation OpenPrice = myBuyStop.Get().Open; class=class="str">"cmt">//--- Set close price for Risk management calculation ClosePrice = myBuyStop.Get().Stop; class=class="str">"cmt">//--- Set Trade magic number Trade.SetExpertMagicNumber(Magic); class=class="str">"cmt">//--- Check if there are any open trades or opened deals or canceled deals already if(!OpenOrder(ORDER_TYPE_BUY_STOP,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_BUY,Magic,COMMENT) &&!OpenedDeal(DEAL_TYPE_BUY_CANCELED,Magic,COMMENT) class=class="str">"cmt">//--- Check if the buy-stop properties are valid &&Valid_Order(ORDER_TYPE_BUY_STOP,myBuyStop.Get().Open,myBuyStop.Get().Stop,myBuyStop.Get().Take) &&!OpenOrder(ORDER_TYPE_SELL_STOP,Magic,COMMENT) &&!OpenedDeal(DEAL_TYPE_SELL,Magic,COMMENT)&&!OpenedDeal(DEAL_TYPE_SELL_CANCELED,Magic,COMMENT) class=class="str">"cmt">//--- Check if the sell-stop properties are valid &&Valid_Order(ORDER_TYPE_SELL_STOP,mySellStop.Get().Open,mySellStop.Get().Stop,mySellStop.Get().Take)) { class=class="str">"cmt">//--- Iterate through the Lot-sizes if they&class="macro">#x27;re more than max-lot for(class="type">class="kw">double i=Volume();i>=CSymbol.LotsMin()&& class=class="str">"cmt">/* Check if current number of orders +class="num">2 more orders is less than account orders limit.*/ (PositionsTotal()+Account.numOrders()+class="num">2)<Account.LimitOrders() ;i-=CSymbol.LotsMax()) { class=class="str">"cmt">//--- normalize Lot-size NormalizeLotsize(i); class=class="str">"cmt">/* Open orders with a Lot-size not more than max-lot and set order expiration to the Symbol&class="macro">#x27;s session end time for the current day. */ if(!Trade.BuyStop((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,myBuyStop.Get().Open, CSymbol.GetSymbolName(),myBuyStop.Get().Stop,myBuyStop.Get().Take, ORDER_TIME_SPECIFIED,CTS.SessionEnd(),COMMENT) ||!Trade.SellStop((i>CSymbol.LotsMax())?CSymbol.LotsMax():i,mySellStop.Get().Open, CSymbol.GetSymbolName(),mySellStop.Get().Stop,mySellStop.Get().Take, ORDER_TIME_SPECIFIED,CTS.SessionEnd(),COMMENT)) { class=class="str">"cmt">//--- one or more orders failed to open. class="kw">return false; } } } else { class=class="str">"cmt">//--- Orders failed class="kw">return false; } class=class="str">"cmt">//--- Return trade result. class="kw">return true; }
「挂单防重与按注释清仓的逻辑落点」
这段逻辑先卡住重复挂单:只有当买停、卖停都未 open,且历史里没有同 magic 的对应成交或撤单记录,同时 Valid_Order 校验挂单价、止损、止盈都合法时,才进入批量挂单循环。循环从 Volume() 往下减,直到低于 LotsMin() 或持仓加挂单数加 2 逼近 LimitOrders() 上限,每步先 NormalizeLotsize 再做 BuyStop / SellStop,单笔超过 LotsMax() 就截断为 LotsMax(),时间限定到 CTS.SessionEnd()。 清仓函数 CloseTrades 只认符号与注释:遍历 PositionsTotal(),用 PositionGetTicket 取 ticket,ticket>0 且 POSITION_SYMBOL 等于当前品种、POSITION_COMMENT 里能找到传入的 COMMENT_COMMON 子串(StringFind>=0),就直接 Trade.PositionClose 平掉。外汇与贵金属杠杆高,这种按注释批量平仓若 COMMENT 串匹配过宽,可能误关手动单,上 MT5 前先打印 COMMENT_COMMON 核对。 下面这段独立循环是上面函数的精简写法,没包在函数体里,实盘里若直接丢到 OnTick 末尾,会在每跳都扫一遍持仓尝试平仓,注意它缺少大括号作用域,i 和 ticket 会泄露到外部,复制时最好并回 CloseTrades 结构里。
if(!OpenOrder(ORDER_TYPE_BUY_STOP, Magic, COMMENT) && !OpenedDeal(DEAL_TYPE_BUY, Magic, COMMENT) && !OpenedDeal(DEAL_TYPE_BUY_CANCELED, Magic, COMMENT) && Valid_Order(ORDER_TYPE_BUY_STOP, myBuyStop.Get().Open, myBuyStop.Get().Stop, myBuyStop.Get().Take) && !OpenOrder(ORDER_TYPE_SELL_STOP, Magic, COMMENT) && !OpenedDeal(DEAL_TYPE_SELL, Magic, COMMENT) && !OpenedDeal(DEAL_TYPE_SELL_CANCELED, Magic, COMMENT) && Valid_Order(ORDER_TYPE_SELL_STOP, mySellStop.Get().Open, mySellStop.Get().Stop, mySellStop.Get().Take)) for (class="type">class="kw">double i = Volume(); i >= CSymbol.LotsMin() && (PositionsTotal() + Account.numOrders() + class="num">2) < Account.LimitOrders(); i -= CSymbol.LotsMax()) NormalizeLotsize(i); if(!Trade.BuyStop((i > CSymbol.LotsMax()) ? CSymbol.LotsMax() : i, myBuyStop.Get().Open, CSymbol.GetSymbolName(), myBuyStop.Get().Stop, myBuyStop.Get().Take, ORDER_TIME_SPECIFIED, CTS.SessionEnd(), COMMENT) || !Trade.SellStop((i > CSymbol.LotsMax()) ? CSymbol.LotsMax() : i, mySellStop.Get().Open, CSymbol.GetSymbolName(), mySellStop.Get().Stop, mySellStop.Get().Take, ORDER_TIME_SPECIFIED, CTS.SessionEnd(), COMMENT)) class="kw">return true; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Function will attempt to close all trades depending on the | class=class="str">"cmt">//|position comment | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTradeManagement::CloseTrades(class="type">class="kw">string COMMENT_COMMON) { class=class="str">"cmt">//--- Iterate through all open positions for(class="type">int i=class="num">0; i<PositionsTotal(); i++) { class="type">ulong ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Check if Position ticket is above zero if(ticket>class="num">0) { class=class="str">"cmt">//--- Check if the Position&class="macro">#x27;s Symbol,Comment is correct if(PositionGetString(POSITION_SYMBOL)==CSymbol.GetSymbolName() &&StringFind(PositionGetString(POSITION_COMMENT),COMMENT_COMMON)>=class="num">0) { class=class="str">"cmt">//--- close trade. Trade.PositionClose(ticket); } } } } for (class="type">int i = class="num">0; i < PositionsTotal(); i++) class="type">ulong ticket = PositionGetTicket(i); if (ticket > class="num">0) if (PositionGetString(POSITION_SYMBOL) == CSymbol.GetSymbolName() && StringFind(PositionGetString(POSITION_COMMENT), COMMENT_COMMON) >= class="num">0) Trade.PositionClose(ticket);
◍ 别急着下结论
这一节把前面几篇散落的止损逻辑收拢了:FundamentalMode 函数专门处理反向止损单的互斥开关,避免在重大新闻前后两边止损同时挂出导致意外成交。 交易管理类把下单、改单、平仓全收进一个入口,市场剧烈波动时执行路径反而更短,滑点控制逻辑会在新闻窗口内动态放宽容差,减少被甩单的概率。 外汇与贵金属新闻行情跳空频繁,这类机制只是降低摩擦,不保证成交价贴近预期;真要验证,把 NewsTrading_Part5.zip(约 598 KB)解压到 MT5 的 MQL5/Experts 下,跑一轮非农复盘再看日志里的 rejected orders 数量。