轻松快捷开发 MetaTrader 程序的函数库 (第 二十七部分) :操控交易请求 - 下挂单·综合运用
(3/3)· 从交易类初始化坑到挂单延后请求落地,一套可复用的库级封装思路
- 从符号属性类里抠出可见性与会话挂单
- 从成交量到点差:符号属性的取值入口
- 从 CSymbol 基类扒出交易品种的运行参数
- 从品种属性接口读取交易约束与实时报价
- 报价区间与真实成交量的取数接口
- 品种属性里的真实成交量与Tick价值接口
- 债券与交易量属性的封装取法
- 从符号类里直接抓保证金属性
- 空头挂单的保证金与时段成交量接口
- 抓交易时段内的盘口快照
- 对冲保证金与报价尾值的取值接口
- 抓合约背景字段的只读接口
- 报价模式决定取价字段
- 正文
- 把下单请求拆成可读字符串
- 下单对象的描述读取与开仓接口
- 挂单请求类的成员布局
- 交易请求对象的存取接口
- 挂单请求对象的字段写入与描述输出
- 挂单请求的状态文本拼装
- 挂单请求对象的构造与类型判定
- 挂单请求对象的比较与日志输出
- 按交易动作拼装请求日志
- 交易请求与回执的字段拼装逻辑
- 模板化开仓函数的参数陷阱
- 卖单开仓函数的模板参数陷阱
- 买卖开仓的模板化封装
- 挂单请求超时与重试的清理逻辑
- 重试窗口与错误自纠的底层逻辑
- 挂单价格按点数偏移的模板实现
- StopLimit 挂单的价格与距离换算
- 挂单里止损止盈的两种传参逻辑
- 挂单函数里的对象获取与错误拦截
- 挂单前报价与风控的双重拦截
- 错误分支里交易指令怎么被掐断
- 正文
- 等待重试如何落为挂单请求
- 挂单请求对象的生成与入列
- 按 ID 捞挂单请求与买卖模板声明
- 挂单接口的模板化封装
- 挂单模板里的成交方式别留空
- 挂单函数的默认参数与填充策略占位
- 挂单与市价单的引擎封装差异
- 卖单与 BuyStop 挂单的模板化封装
- 限价与止损限价挂单的引擎封装
- 挂单引擎里的 SellStop 与 SellLimit 封装
- 挂单函数里的填充策略参数
- 挂卖单限价触发的底层调用
- 断网状态下验证延后挂单请求
- 把资源和交易参数一次性灌进引擎
- 断线时挂单请求如何转入待重发队列
- 挂单被拒时的日志长什么样
- 从挂单日志反推 Sell Limit 的触发结构
- 延后请求接下来的改造方向
「从符号属性类里抠出可见性与会话挂单」
在 MT5 的 CSymbol 封装里,一组 const 方法直接把底层 SYMBOL_PROP_* 属性暴露成易读接口,省去你手写 SymbolInfoInteger 的麻烦。 下面这段代码演示了背景色、图表模式、是否存在、是否被选中、是否可见,以及当前会话的成交与买卖挂单数量如何被一行返回: [CODE] color ColorBackground(void) const { return (color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR); } ENUM_SYMBOL_CHART_MODE ChartMode(void) const { return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE); } bool IsExist(void) const { return (bool)this.GetProperty(SYMBOL_PROP_EXIST); } bool IsExist(const string name) const { return this.SymbolExists(name); } bool IsSelect(void) const { return (bool)this.GetProperty(SYMBOL_PROP_SELECT); } bool IsVisible(void) const { return (bool)this.GetProperty(SYMBOL_PROP_VISIBLE); } long SessionDeals(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_DEALS); } long SessionBuyOrders(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS); } long SessionSellOrders(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS); } [/CODE] 逐行拆解:ColorBackground 强转 GetProperty 拿到的整型为背景色;ChartMode 返回枚举判断图表是蜡烛/线/砖形;两个 IsExist 重载,无参查当前符号、带名查市场是否存在该品种。 IsSelect 看品种是否在市场报价窗被点选,IsVisible 看是否显示在可见窗口。SessionDeals、SessionBuyOrders、SessionSellOrders 分别给出当日会话内的成交笔数、买挂单与卖挂单数——这些 long 值在流动性突变时可能快速跳动。 开 MT5 按 F4 把这段塞进你自己的符号工具类,盯盘时若 SessionBuyOrders 与 SessionSellOrders 差值连续 3 根柱扩大,贵金属与外汇的高波动概率会上升,请自行验证并控制仓位风险。
class="type">class="kw">color ColorBackground(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR); } ENUM_SYMBOL_CHART_MODE ChartMode(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE); } class="type">bool IsExist(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_EXIST); } class="type">bool IsExist(class="kw">const class="type">class="kw">string name) class="kw">const { class="kw">return this.SymbolExists(name); } class="type">bool IsSelect(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_SELECT); } class="type">bool IsVisible(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_VISIBLE); } class="type">long SessionDeals(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_DEALS); } class="type">long SessionBuyOrders(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS); } class="type">long SessionSellOrders(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS); }
◍ 从成交量到点差:符号属性的取值入口
在 MT5 自建指标或 EA 里,想拿当前品种的基础行情属性,通常走 CSymbolInfo 这类封装的 getter 方法,而不是反复调 SymbolInfoInteger。下面这段就是一组典型的只读访问器,全部 const 限定,返回品种级别的实时属性。 [CODE]long Volume(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME); } long VolumeHigh(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH); } long VolumeLow(void) const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW); } long Time(void) const { return (datetime)this.GetProperty(SYMBOL_PROP_TIME); } int Digits(void) const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS); } int DigitsLot(void) const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS); } int Spread(void) const { return (int)this.GetProperty(SYMBOL_PROP_SPREAD); } bool IsSpreadFloat(void) const { return (bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT); }[/CODE] 逐行看:Volume / VolumeHigh / VolumeLow 分别取当前成交量、当日最高成交量和最低成交量,类型为 long;Time 强转 datetime,拿到最后一次报价时间。Digits 是价格小数位(如 XAUUSD 常见 2 位),DigitsLot 是手数精度位,Spread 以点数返回当前点差,IsSpreadFloat 判断点差是否浮动。 实际盯盘时,Spread() 在伦敦金可能从 18 点跳到 50 点以上,浮动品种需配合 IsSpreadFloat() 判断再决定下单逻辑。外汇与贵金属杠杆交易风险高,点差突变可能直接吃掉短线策略的边际收益,建议开 MT5 用这段 getter 打印 EURUSD 与 XAUUSD 的 Spread 做对比验证。
class="type">long Volume(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME); } class="type">long VolumeHigh(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH); } class="type">long VolumeLow(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMELOW); } class="type">long Time(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_TIME); } class="type">int Digits(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_DIGITS); } class="type">int DigitsLot(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS); } class="type">int Spread(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_SPREAD); } class="type">bool IsSpreadFloat(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT); }
从 CSymbol 基类扒出交易品种的运行参数
在 MT5 的自定义类体系里,CSymbol 基类把品种的运行属性封装成一堆只读方法,调用时全部走 GetProperty 取 SYMBOL_PROP_* 宏。下面这几行是实际工程中高频用到的属性读取,建议直接丢进 EA 的 OnInit 里打印一遍,确认你的经纪商给的是哪套规则。 TicksBookdepth 返回当前品种市场深度(book)的档位数,类型是 int;如果返回 0,说明该品种不提供 DOM 数据,做贵金属或外汇 scalping 前先确认这个,不然依赖深度的小布策略会空跑。 TradeCalcMode 与 TradeMode 分别给出保证金计算模式和交易权限模式(比如是否只允许平仓),TradeStopLevel 和 TradeFreezeLevel 以点数(int)返回止损最小距离与冻结距离——外汇和贵金属杠杆高,这两个值直接决定你挂单会不会被拒。 SwapMode 配合 SwapRollover3Days 能知道隔夜费怎么算、周几收三倍;ExpirationTime 对限时合约有用,StartTime 则是品种数据起点。把这套属性读出来,你就能在策略里动态适配不同品种,而不是写死参数。
class="type">int TicksBookdepth(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TICKS_BOOKDEPTH); } ENUM_SYMBOL_CALC_MODE TradeCalcMode(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_CALC_MODE)this.GetProperty(SYMBOL_PROP_TRADE_CALC_MODE); } ENUM_SYMBOL_TRADE_MODE TradeMode(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_TRADE_MODE)this.GetProperty(SYMBOL_PROP_TRADE_MODE); } class="type">class="kw">datetime StartTime(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_START_TIME); } class="type">class="kw">datetime ExpirationTime(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SYMBOL_PROP_EXPIRATION_TIME); } class="type">int TradeStopLevel(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TRADE_STOPS_LEVEL); } class="type">int TradeFreezeLevel(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_TRADE_FREEZE_LEVEL); } ENUM_SYMBOL_TRADE_EXECUTION TradeExecutionMode(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_TRADE_EXECUTION)this.GetProperty(SYMBOL_PROP_TRADE_EXEMODE); } ENUM_SYMBOL_SWAP_MODE SwapMode(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_SWAP_MODE)this.GetProperty(SYMBOL_PROP_SWAP_MODE); } ENUM_DAY_OF_WEEK SwapRollover3Days(class="type">void) class="kw">const { class="kw">return (ENUM_DAY_OF_WEEK)this.GetProperty(SYMBOL_PROP_SWAP_ROLLOVER3DAYS); }
「从品种属性接口读取交易约束与实时报价」
在 MT5 的 CSymbol 封装里,一组 const 成员函数专门把品种的交易规则与实时行情暴露给策略层。它们底层都走同一个 GetProperty 入口,只是传入的枚举不同,返回类型做了强制转换。 像 IsMarginHedgedUseLeg 返回 bool,对应 SYMBOL_PROP_MARGIN_HEDGED_USE_LEG,用来判断锁仓时是否按单边占用保证金;ExpirationModeFlags、FillingModeFlags、OrderModeFlags 则分别返回 int 位掩码,揭示该品种允许的单子到期、成交、挂单模式。外汇与贵金属的高杠杆特性下,这些标志直接决定你的订单会不会被拒或滑点放大,属于开仓前必须确认的项。 实时侧以 Bid 和 BidHigh 为代表:Bid 取当前卖价,BidHigh 取当日卖价最高。若你在 EA 里做均值回归,BidHigh 相对 Bid 的回撤幅度就是天然的压力参考,例如欧美对日常 BidHigh 与 Bid 差值常在 0.0003~0.0012 区间波动,突破上沿可能倾向短空。 下面的片段是这组接口的原文抽取,逐行看更直观。
class="type">bool IsMarginHedgedUseLeg(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED_USE_LEG); } class="type">int ExpirationModeFlags(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_EXPIRATION_MODE); } class="type">int FillingModeFlags(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_FILLING_MODE); } class="type">int OrderModeFlags(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(SYMBOL_PROP_ORDER_MODE); } ENUM_SYMBOL_ORDER_GTC_MODE OrderModeGTC(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_ORDER_GTC_MODE)this.GetProperty(SYMBOL_PROP_ORDER_GTC_MODE); } ENUM_SYMBOL_OPTION_MODE OptionMode(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); } ENUM_SYMBOL_OPTION_RIGHT OptionRight(class="type">void) class="kw">const { class="kw">return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); } class=class="str">"cmt">//--- Real properties class="type">class="kw">double Bid(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BID); } class="type">class="kw">double BidHigh(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BIDHIGH); }
◍ 报价区间与真实成交量的取数接口
在 MT5 的自定义品种类里,Bid 和 Ask 的极值不是靠你自己算的,直接调封装好的属性接口就能拿到当前会话的边界值。下面这组方法全部标记为 const,说明它们只读取不修改对象状态,适合放在任意回调里高频调用。 GetProperty 里传的 SYMBOL_PROP_BIDLOW、SYMBOL_PROP_ASKHIGH 等宏,对应经纪商推送的当日(或当前数据窗口)最低买价、最高卖价。Last 系列则面向有真实成交的品种——外汇现货的 Last 往往等于 Bid,但贵金属 XAUUSD 在流动性断层时 Last 可能偏离 Bid 达 0.5 美元以上,这种偏离本身就是风险信号。 VolumeReal 返回的是真实交易量(手数累加),和蜱数 Volume 不是一回事;拿它做量价确认时,记得外汇和贵金属都是高杠杆品种,真实成交量突增往往伴随滑点扩大,下单前务必核对点差。
class="type">class="kw">double BidLow(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BIDLOW); } class="type">class="kw">double Ask(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_ASK); } class="type">class="kw">double AskHigh(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_ASKHIGH); } class="type">class="kw">double AskLow(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_ASKLOW); } class="type">class="kw">double Last(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_LAST); } class="type">class="kw">double LastHigh(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTHIGH); } class="type">class="kw">double LastLow(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_LASTLOW); } class="type">class="kw">double VolumeReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_REAL); }
品种属性里的真实成交量与Tick价值接口
在 MT5 的 CSymbol 类封装里,有一组直接返回品种底层数值的 const 方法,调用时不依赖当前图表,而是走 GetProperty 取经纪商下发的符号属性。对外汇和贵金属来说,这类接口拿到的才是真实可交易参数,而不是肉眼在报价栏看到的整数近似。 其中 VolumeHighReal 与 VolumeLowReal 返回的是该品种历史真实成交量的极值,和 K 线 Volume[] 数组里的显示值可能不同——部分平台会对展示量做缩放。期权品种才有意义的 OptionStrike 返回行权价,普通外汇对调用基本是 0。 Point 给出最小价格变动单位,黄金通常 0.01、欧元美元多数 0.00001;TradeTickValue / TradeTickValueProfit / TradeTickValueLoss 三者分别给出按跳动的名义、盈利、亏损金额,贵金属跳一动账户波动可能明显大于外汇。 把这些接口写进 EA 的仓位计算,比写死手数更稳。外汇与贵金属杠杆高,真实 Tick 价值随合约规模和报价浮动,实盘前务必在策略测试器用不同品种跑一遍确认数值。
class="type">class="kw">double VolumeHighReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH_REAL); } class="type">class="kw">double VolumeLowReal(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUMELOW_REAL); } class="type">class="kw">double OptionStrike(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_OPTION_STRIKE); } class="type">class="kw">double Point(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_POINT); } class="type">class="kw">double TradeTickValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE); } class="type">class="kw">double TradeTickValueProfit(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT); } class="type">class="kw">double TradeTickValueLoss(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS); } class="type">class="kw">double TradeTickSize(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_TICK_SIZE); } class="type">class="kw">double TradeContractSize(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_CONTRACT_SIZE); }
「债券与交易量属性的封装取法」
在 MT5 自建品种封装类里,有一组只读方法专门暴露债券类与交易量类的核心属性,全部走 GetProperty 取 SYMBOL_PROP_* 枚举,不另存成员变量。 下面这段是实际可用的取数方法集群,覆盖了应计利息、面值、流动性系数以及最小/最大/步进/上限交易量,还有多空掉期。 [CODE]double TradeAccuredInterest(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_ACCRUED_INTEREST); } double TradeFaceValue(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_FACE_VALUE); } double TradeLiquidityRate(void) const { return this.GetProperty(SYMBOL_PROP_TRADE_LIQUIDITY_RATE); } double LotsMin(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_MIN); } double LotsMax(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_MAX); } double LotsStep(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_STEP); } double VolumeLimit(void) const { return this.GetProperty(SYMBOL_PROP_VOLUME_LIMIT); } double SwapLong(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_LONG); } double SwapShort(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_SHORT); }[/CODE] 逐行看:TradeAccuredInterest 取债券持仓的应计利息,TradingFaceValue 取票面价值,TradeLiquidityRate 反映品种流动性折扣系数;LotsMin / LotsMax / LotsStep 直接对应 MT5 合约规格里的交易量边界与步长,VolumeLimit 则是单品种总持仓上限。 SwapLong 与 SwapShort 分别返回多空隔夜掉期点数,外汇与贵金属持仓过夜成本会随经纪商参数变动,属于高风险暴露项,实盘前应在策略测试器里核对具体数值。 开 MT5 新建一个 CSymbol 派生类把上面方法贴进去,用 Print(LotsStep()) 就能在日志看到当前品种步进,比如 XAUUSD 常见步长是 0.01 手,与你经纪商合约规格面板应对得上。
class="type">class="kw">double TradeAccuredInterest(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_ACCRUED_INTEREST); } class="type">class="kw">double TradeFaceValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_FACE_VALUE); } class="type">class="kw">double TradeLiquidityRate(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_TRADE_LIQUIDITY_RATE); } class="type">class="kw">double LotsMin(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_MIN); } class="type">class="kw">double LotsMax(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_MAX); } class="type">class="kw">double LotsStep(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_STEP); } class="type">class="kw">double VolumeLimit(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_VOLUME_LIMIT); } class="type">class="kw">double SwapLong(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_LONG); } class="type">class="kw">double SwapShort(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SWAP_SHORT); }
◍ 从符号类里直接抓保证金属性
在 MT5 的自建品种封装类里,保证金相关数值不需要调外部函数,直接走类内方法拿就行。下面这组方法全部是 const 修饰,说明只读、不改动对象状态,可以放心在回测或实时 tick 里高频调用。 每个方法对应一个 SYMBOL_PROP_MARGIN_* 枚举,覆盖初始保证金、维持保证金,以及多单(long/buy)和挂单(stop/limit/stoplimit)的细分类别。比如 MarginInitial 返回品种建仓初始保证金比例,MarginLongMaintenance 返回多单持仓维持保证金,两者在券商配置里可能不同,做仓位压力测试时要分开取。 实际验证方式:在 MT5 导航栏新建一个 EA,包含这段类方法,Print(MarginLongInitial()) 和 Print(MarginBuyStopInitial()),跑在 XAUUSD 上,你能直接看到黄金多单与买停挂单的初始保证金数值差异。外汇和贵金属杠杆波动大,保证金参数变更会直接影响强平概率,属高风险品类。
class="type">class="kw">double MarginInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_INITIAL); } class="type">class="kw">double MarginMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_MAINTENANCE); } class="type">class="kw">double MarginLongInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_INITIAL); } class="type">class="kw">double MarginBuyStopInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL); } class="type">class="kw">double MarginBuyLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL); } class="type">class="kw">double MarginBuyStopLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL); } class="type">class="kw">double MarginLongMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE); } class="type">class="kw">double MarginBuyStopMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE); } class="type">class="kw">double MarginBuyLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE); } class="type">class="kw">double MarginBuyStopLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE); }
空头挂单的保证金与时段成交量接口
做空相关的保证金查询,MT5 在 CSymbol 类里拆得很细:初始保证金和维护保证金分别给了市价卖、卖停、卖限、卖停限四个子类。 下面这截代码就是对应 getter,全部走 this.GetProperty 读符号属性枚举,返回值都是 double。 MarginShortInitial 读 SYMBOL_PROP_MARGIN_SHORT_INITIAL,是市价空单开仓占的初始保证金;MarginSellStopInitial / MarginSellLimitInitial / MarginSellStopLimitInitial 则分别对应卖停、卖限、卖停限挂单的初始保证金。 维护保证金那组把 _INITIAL 换成 _MAINTENANCE,经纪商可能在持仓过夜时按这个数值重新锁仓,外汇和贵金属杠杆高,维护保证金跳变会直接触发追加保证金,属高风险环节。 SessionVolume 和 SessionTurnover 不碰保证金,返回的是当前交易时段内的成交量和成交额,用来判断流动性窗口比看报价更实在。 开 MT5 按 F4 进 CSymbol 头文件搜这些 getter,把返回值打进 Print 就能核对你账户里黄金 XAUUSD 的卖停限维护保证金到底多少。
class="type">class="kw">double MarginShortInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_INITIAL); } class="type">class="kw">double MarginSellStopInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL); } class="type">class="kw">double MarginSellLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL); } class="type">class="kw">double MarginSellStopLimitInitial(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL); } class="type">class="kw">double MarginShortMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE); } class="type">class="kw">double MarginSellStopMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE); } class="type">class="kw">double MarginSellLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE); } class="type">class="kw">double MarginSellStopLimitMaintenance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE); } class="type">class="kw">double SessionVolume(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_VOLUME); } class="type">class="kw">double SessionTurnover(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_TURNOVER); }
「抓交易时段内的盘口快照」
在 MT5 的 CSymbol 类封装里,一组 const 方法专门读取当前交易时段的符号属性,返回的都是 double 类型,调用零开销。 下面这段代码把九个会话级属性一次性暴露出来,每个方法内部只做一件事:向 GetProperty 传对应的 SYMBOL_PROP_SESSION_* 枚举。 [CODE] double SessionInterest(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_INTEREST); } // 返回当前时段持仓利息 double SessionBuyOrdersVolume(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME); } // 返回当前时段买单总成交量 double SessionSellOrdersVolume(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME); } // 返回当前时段卖单总成交量 double SessionOpen(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_OPEN); } // 返回当前时段开盘价 double SessionClose(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_CLOSE); } // 返回当前时段收盘价(时段未结束则为最近值) double SessionAW(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_AW); } // 返回当前时段加权平均价 double SessionPriceSettlement(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT); } // 返回当前时段结算价 double SessionPriceLimitMin(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN); } // 返回当前时段最低限价 double SessionPriceLimitMax(void) const { return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX); } // 返回当前时段最高限价 [/CODE] 实盘里这些值和经纪商会话切换强相关。比如 SessionBuyOrdersVolume 与 SessionSellOrdersVolume 的差值,可能提示时段内买卖压力倾斜;外汇与贵金属杠杆高,会话边界常伴随点差扩张,读这些值前先确认 SYMBOL_TRADE_MODE 不为关闭。 开 MT5 按 F4 把上面方法塞进你自己的符号类,打印 SessionAW 和 SessionOpen 的差,就能快速看时段内均价偏离开盘多少点。
class="type">class="kw">double SessionInterest(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_INTEREST); } class="type">class="kw">double SessionBuyOrdersVolume(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME); } class="type">class="kw">double SessionSellOrdersVolume(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME); } class="type">class="kw">double SessionOpen(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_OPEN); } class="type">class="kw">double SessionClose(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_CLOSE); } class="type">class="kw">double SessionAW(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_AW); } class="type">class="kw">double SessionPriceSettlement(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT); } class="type">class="kw">double SessionPriceLimitMin(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN); } class="type">class="kw">double SessionPriceLimitMax(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX); }
◍ 对冲保证金与报价尾值的取值接口
在自定义品种类里,MarginHedged() 直接返回 SYMBOL_PROP_MARGIN_HEDGED 属性,代表锁仓状态下单手所占保证金。若经纪商对该品种设了对冲优惠,这个值会明显低于单边保证金,开 MT5 用 SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_HEDGED) 对比一下就能验证。 NormalizedPrice 与 NormalizedLot 负责把外部传入的 price、volume 按品种精度规整,避免下单价或手数带出非法小数位。实盘里 EURUSD 报价精度 5 位、手数精度 2 位,传错精度会被交易服务器拒单。 BidLast / AskLast 及其 High、Low 变体给出最近一笔买价、卖价与它们的区间极值。原文中 AskLast 系列三行被高亮,说明它是这一屏里新增或重点改动的接口,用来追踪盘口最后跳动而非实时tick。 字符串类里 Name、Basis、CurrencyBase 都是内联返回 GetProperty 的轻量封装,分别取品种名、计价基准、基础货币。写多品种遍历时直接调这些成员,比每次拼 SYMBOL_PROP_ 宏更不容易拼错。
class="type">class="kw">double MarginHedged(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED); } class="type">class="kw">double NormalizedPrice(class="kw">const class="type">class="kw">double price) class="kw">const; class="type">class="kw">double NormalizedLot(class="kw">const class="type">class="kw">double volume) class="kw">const; class="type">class="kw">double BidLast(class="type">void) class="kw">const; class="type">class="kw">double BidLastHigh(class="type">void) class="kw">const; class="type">class="kw">double BidLastLow(class="type">void) class="kw">const; class="type">class="kw">double AskLast(class="type">void) class="kw">const; class="type">class="kw">double AskLastHigh(class="type">void) class="kw">const; class="type">class="kw">double AskLastLow(class="type">void) class="kw">const; class=class="str">"cmt">//--- String properties class="type">class="kw">string Name(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_NAME); } class="type">class="kw">string Basis(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BASIS); } class="type">class="kw">string CurrencyBase(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_CURRENCY_BASE); }
抓合约背景字段的只读接口
在封装好的交易品种类里,有一组返回字符串的 const 方法,专门把品种的背景属性从底层 GetProperty 里透出来,调用时不需要传参,直接拿结果即可。 这些方法覆盖的键名包括 SYMBOL_PROP_CURRENCY_PROFIT(盈利结算币种)、SYMBOL_PROP_CURRENCY_MARGIN(保证金币种)、SYMBOL_PROP_BANK(清算行)、SYMBOL_PROP_DESCRIPTION(品种描述)、SYMBOL_PROP_FORMULA(计价公式)、SYMBOL_PROP_ISIN(国际证券编码)、SYMBOL_PROP_PAGE(信息页)、SYMBOL_PROP_PATH(分类路径)。 外汇与贵金属品种的高杠杆特征意味着这些字段若读错(例如保证金币种非账户币种),可能直接误导仓位计算,实盘前应在 MT5 里逐一对疑似品种打印验证。 下面这段就是上述接口的完整声明,复制进 EA 的 CSymbol 类即可直接用。
class="type">class="kw">string CurrencyProfit(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_CURRENCY_PROFIT); } class="type">class="kw">string CurrencyMargin(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_CURRENCY_MARGIN); } class="type">class="kw">string Bank(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_BANK); } class="type">class="kw">string Description(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_DESCRIPTION); } class="type">class="kw">string Formula(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_FORMULA); } class="type">class="kw">string ISIN(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_ISIN); } class="type">class="kw">string Page(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_PAGE); } class="type">class="kw">string Path(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_PATH); }
「报价模式决定取价字段」
CSymbol 类里先暴露了两个轻量属性接口:Category() 和 Exchange(),都只是对 GetProperty 的薄封装,分别回传品种分类与交易所标识,用来在脚本里快速区分现货金属和外汇对。 真正的取价逻辑在 BidLast / BidLastHigh / BidLastLow 三个方法。它们都遵循同一规则:若图表构造模式为 SYMBOL_CHART_MODE_BID,直接取 Bid 系字段;否则当 Bid 值为 0(即当前无做市买卖报价,多见于交易所品种)时回退到 Last 系字段,否则仍取 Bid。 这套分支对外汇/贵金属交易者很实用:MT5 上 XAUUSD 通常走 Bid 模式,而部分交易所金属合约在休市时段 Bid 为 0,此时用 Last 才拿得到最后成交价。外汇与贵金属杠杆高、跳空频繁,直接硬取 Bid 可能在非交易时段得到 0 导致指标算崩,用 BidLast 系列能规避这类坑。 下面三段是核心实现的原文,注意三元嵌套里 0 的判断就是「无 Bid 报价」的兜底信号。
class="type">class="kw">string Category(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_CATEGORY); } class="type">class="kw">string Exchange(class="type">void) class="kw">const { class="kw">return this.GetProperty(SYMBOL_PROP_EXCHANGE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return Bid or Last price | class=class="str">"cmt">//| depending on the chart construction method and price availability| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSymbol::BidLast(class="type">void) class="kw">const { class="kw">return ( this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BID) : (this.GetProperty(SYMBOL_PROP_BID)==class="num">0 ? this.GetProperty(SYMBOL_PROP_LAST) : this.GetProperty(SYMBOL_PROP_BID)) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return maximum Bid or Last price for a day | class=class="str">"cmt">//| depending on the chart construction method and price availability| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSymbol::BidLastHigh(class="type">void) class="kw">const { class="kw">return ( this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BIDHIGH) : (this.GetProperty(SYMBOL_PROP_BIDHIGH)==class="num">0 ? this.GetProperty(SYMBOL_PROP_LASTHIGH) : this.GetProperty(SYMBOL_PROP_BIDHIGH)) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return minimum Bid or Last price for a day | class=class="str">"cmt">//| depending on the chart construction method and price availability| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSymbol::BidLastLow(class="type">void) class="kw">const { class="kw">return ( this.ChartMode()==SYMBOL_CHART_MODE_BID ? this.GetProperty(SYMBOL_PROP_BIDLOW) : (this.GetProperty(SYMBOL_PROP_BIDLOW)==class="num">0 ? this.GetProperty(SYMBOL_PROP_LASTLOW) : this.GetProperty(SYMBOL_PROP_BIDLOW)) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return Ask or Last price | class=class="str">"cmt">//| depending on the chart construction method and price availability| class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 正文
<span class="keyword">double</span> CSymbol::AskLast(<span class="keyword">void</span>) <span class="keyword">const</span> { <span class="keyword">return</span> ( <span class="keyword">this</span>.ChartMode()==<span class="macro">SYMBOL_CHART_MODE_BID</span> ? <span class="keyword">this</span>.GetProperty(SYMBOL_PROP_ASK) : (<span class="keyword">this</span>.GetProperty(SYMBOL_PROP_ASK)==<span class="number">0</span> ? <span class="keyword">this</span>.GetProperty(SYMBOL_PROP_LAST) : <span class="keyword">this</span>.GetProperty(SYMBOL_PROP_ASK)) ); } <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | Return maximum Ask or Last price for a day | </span> |
|---|---|---|
| <span class="comment">// | depending on the chart construction method and price availability | </span> |
<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">double</span> CSymbol::AskLastHigh(<span class="keyword">void</span>) <span class="keyword">const</span> { <span class="keyword">return</span> ( <span class="ke
把下单请求拆成可读字符串
在封装交易请求的类里,给每个字段配一个 Get 方法是省脑子的事。下面这组方法全部标记为 const,说明只读取 this.m_request 并转成字符串,不改动任何成员状态。 它们分别把订单描述、品种、成交量、挂单价、STOP_LIMIT 距离、SL、TP、允许偏差、订单类型、成交方式、有效期类型,委托给对应的自由函数生成人类能读的说明文本。回测或日志里直接拼这些字符串,比盯结构体字段快得多。 注意外汇与贵金属点差跳动快,m_request 里的偏差和价格字段若来自旧 tick,生成的说明可能已偏离盘口,打印前最好确认请求刚组装不久。
class="type">class="kw">string GetRequestOrderDescription(class="type">void) class="kw">const { class="kw">return RequestOrderDescription(this.m_request); } class="type">class="kw">string GetRequestSymbolDescription(class="type">void) class="kw">const { class="kw">return RequestSymbolDescription(this.m_request); } class="type">class="kw">string GetRequestVolumeDescription(class="type">void) class="kw">const { class="kw">return RequestVolumeDescription(this.m_request); } class="type">class="kw">string GetRequestPriceDescription(class="type">void) class="kw">const { class="kw">return RequestPriceDescription(this.m_request); } class="type">class="kw">string GetRequestStopLimitDescription(class="type">void) class="kw">const { class="kw">return RequestStopLimitDescription(this.m_request); } class="type">class="kw">string GetRequestStopLossDescription(class="type">void) class="kw">const { class="kw">return RequestStopLossDescription(this.m_request); } class="type">class="kw">string GetRequestTakeProfitDescription(class="type">void) class="kw">const { class="kw">return RequestTakeProfitDescription(this.m_request); } class="type">class="kw">string GetRequestDeviationDescription(class="type">void) class="kw">const { class="kw">return RequestDeviationDescription(this.m_request); } class="type">class="kw">string GetRequestTypeDescription(class="type">void) class="kw">const { class="kw">return RequestTypeDescription(this.m_request); } class="type">class="kw">string GetRequestTypeFillingDescription(class="type">void) class="kw">const { class="kw">return RequestTypeFillingDescription(this.m_request); } class="type">class="kw">string GetRequestTypeTimeDescription(class="type">void) class="kw">const { class="kw">return RequestTypeTimeDescription(this.m_request); }
「下单对象的描述读取与开仓接口」
CTradeObj 里先挂了四个 const 方法,把内部 m_request 结构里的过期、注释、持仓、按持仓描述直接吐成字符串,方便在日志或面板里看单子状态。 真正的下单入口是 OpenPosition,参数表里 sl、tp 默认 0,magic 与 deviation 默认 ULONG_MAX,意味着不填就走品种默认或不过滤魔术码。 注意 type_filling 默认值写成 WRONG_VALUE,这是故意留的坑:调用方必须显式传 ORDER_FILLING_FOK / IOC / RETURN 等,否则下单请求会直接被判非法。外汇与贵金属杠杆高,填错填充策略可能让订单在剧震中全部撤单。 下面这段声明就是接口骨架,复制进 EA 前先把 WRONG_VALUE 那行改掉。
class="type">class="kw">string GetRequestExpirationDescription(class="type">void) class="kw">const { class="kw">return RequestExpirationDescription(this.m_request); } class="type">class="kw">string GetRequestCommentDescription(class="type">void) class="kw">const { class="kw">return RequestCommentDescription(this.m_request); } class="type">class="kw">string GetRequestPositionDescription(class="type">void) class="kw">const { class="kw">return RequestPositionDescription(this.m_request); } class="type">class="kw">string GetRequestPositionByDescription(class="type">void) class="kw">const { class="kw">return RequestPositionByDescription(this.m_request); } class=class="str">"cmt">//--- Open a position class="type">bool OpenPosition(class="kw">const class="type">ENUM_POSITION_TYPE type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">double sl=class="num">0, class="kw">const class="type">class="kw">double tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
◍ 挂单请求类的成员布局
在 MT5 的 EA 架构里,把挂单逻辑封装成一个独立的请求类,能显著降低主循环里的耦合度。下面这段声明里,CPendingReq 继承自 CObject,私有区保留了 MqlTradeRequest 结构体和一组跟踪字段。 其中 m_type 用 int 承接挂单类型,m_retcode 记录上一次请求返回码,m_price_create 与 m_time_create 锁定发单瞬间的价格和时间,便于后续比对滑点。m_waiting_msc 和 m_current_attempt、m_total_attempts 三者配合,可实现失败重试的节奏控制——比如设 m_total_attempts=3、m_waiting_msc=500 即代表最多补单 3 次、每次间隔 500 毫秒。 外汇与贵金属杠杆高,这类自动补单机制在跳空行情中可能放大敞口,实盘前务必在策略测试器里用历史数据跑过极端点差时段。
class="kw">const class="type">class="kw">double sl=class="num">0, class="kw">const class="type">class="kw">double tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Pending request object class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CPendingReq : class="kw">public CObject { class="kw">private: class="type">MqlTradeRequest m_request; class=class="str">"cmt">// Trade request structure class="type">uchar m_id; class=class="str">"cmt">// Trading request ID class="type">int m_type; class=class="str">"cmt">// Pending request type class="type">int m_retcode; class=class="str">"cmt">// Result a request is based on class="type">class="kw">double m_price_create; class=class="str">"cmt">// Price at the moment of a request generation class="type">class="kw">ulong m_time_create; class=class="str">"cmt">// Request generation time class="type">class="kw">ulong m_time_activate; class=class="str">"cmt">// Next attempt activation time class="type">class="kw">ulong m_waiting_msc; class=class="str">"cmt">// Waiting time between requests class="type">uchar m_current_attempt; class=class="str">"cmt">// Current attempt index class="type">uchar m_total_attempts; class=class="str">"cmt">// Number of attempts class="kw">public: class=class="str">"cmt">//--- Return(class="num">1) the request structure, (class="num">2) the price at the moment of the request generation, class=class="str">"cmt">//--- (class="num">3) request generation time, (class="num">4) current attempt time,
交易请求对象的存取接口
在 EA 封装类里,每一笔挂单或市价单请求都被收进一个结构体对象,对外只暴露只读 getter 和显式 setter。这样外部逻辑不能直接改请求核心字段,只能在创建时由类内部赋值,降低误改 m_request 导致经纪商拒单的概率。 下面这段是 getter 集合:MqlRequest() 回传完整的 MqlTradeRequest 结构;PriceCreate() 返回建仓报价;TimeCreate() 与 TimeActivate() 分别给出请求生成时间和激活时间;WaitingMSC() 是两次重试之间的毫秒间隔。 重试控制由 CurrentAttempt() 与 TotalAttempts() 提供,分别是当前第几次尝试和总尝试次数(类型 uchar,上限 255 次);ID() 返回请求自增编号。Retcode() 返回最近一次服务器回码,是判断 10009(成交)还是 10035(重试)的关键字段。 Setter 只开放七个可变项:SetPriceCreate() 改建仓价,SetTimeCreate() / SetTimeActivate() 写时间戳。值得注意的是,WaitingMSC、Attempt 系列和 ID 没有 setter,说明这些由类内部在运行期自维护,调用方不应从外部强行干预。 实盘里若你发现某次黄金挂单反复触发 10035,可先在 Retcode() 上打断点,看 CurrentAttempt() 是否逼近 TotalAttempts() 上限,再决定是否放大 WaitingMSC()。外汇与贵金属杠杆高,重试参数设太激进可能在不流动时段连续踩坑。
class=class="str">"cmt">//--- (class="num">5) waiting time between requests, (class="num">6) current attempt index, class=class="str">"cmt">//--- (class="num">7) number of attempts, (class="num">8) request ID class="type">MqlTradeRequest MqlRequest(class="type">void) class="kw">const { class="kw">return this.m_request; } class="type">class="kw">double PriceCreate(class="type">void) class="kw">const { class="kw">return this.m_price_create; } class="type">class="kw">ulong TimeCreate(class="type">void) class="kw">const { class="kw">return this.m_time_create; } class="type">class="kw">ulong TimeActivate(class="type">void) class="kw">const { class="kw">return this.m_time_activate;} class="type">class="kw">ulong WaitingMSC(class="type">void) class="kw">const { class="kw">return this.m_waiting_msc; } class="type">uchar CurrentAttempt(class="type">void) class="kw">const { class="kw">return this.m_current_attempt; } class="type">uchar TotalAttempts(class="type">void) class="kw">const { class="kw">return this.m_total_attempts; } class="type">uchar ID(class="type">void) class="kw">const { class="kw">return this.m_id; } class="type">int Retcode(class="type">void) class="kw">const { class="kw">return this.m_retcode; } class=class="str">"cmt">//--- Set(class="num">1) the price when creating a request, (class="num">2) request creation time, class=class="str">"cmt">//--- (class="num">3) current attempt time, (class="num">4) waiting time between requests, class=class="str">"cmt">//--- (class="num">5) current attempt index, (class="num">6) number of attempts, (class="num">7) request ID class="type">void SetPriceCreate(class="kw">const class="type">class="kw">double price) { this.m_price_create=price; } class="type">void SetTimeCreate(class="kw">const class="type">class="kw">ulong time) { this.m_time_create=time; } class="type">void SetTimeActivate(class="kw">const class="type">class="kw">ulong time) { this.m_time_activate=time; }
「挂单请求对象的字段写入与描述输出」
在 MT5 的挂单请求封装类里,有一组 setter 负责把运行时参数写进私有成员:等待毫秒数、当前尝试序号、总尝试次数、请求 ID 都通过 this 指针直接赋值,类型分别限定为 ulong 与 uchar。 这类写法没有返回值,调用开销极低,适合在重试逻辑里高频设置状态。 MqlRequestDescription 只回传请求动作描述,而 TypeDescription 会根据 Type() 判断是错误触发还是普通请求触发,拼接不同的本地化文本常量。 PriceCreateDescription 则把生成挂单时的价格按品种小数位格式化输出,SymbolInfoInteger 取 SYMBOL_DIGITS 保证 XAUUSD 这类 2~3 位、外汇 3~5 位品种都不会错位。 把这段直接塞进你的 EA 调试模块,能在日志里看清每一次挂单重试的价格与间隔,外汇和贵金属波动剧烈,这类微观追踪对排查滑点有帮助,但任何信号都有失效概率。
class="type">void SetWaitingMSC(class="kw">const class="type">class="kw">ulong miliseconds) { this.m_waiting_msc=miliseconds;} class="type">void SetCurrentAttempt(class="kw">const class="type">uchar number) { this.m_current_attempt=number; } class="type">void SetTotalAttempts(class="kw">const class="type">uchar number) { this.m_total_attempts=number; } class="type">void SetID(class="kw">const class="type">uchar id) { this.m_id=id; } class=class="str">"cmt">//--- Return the description of the(class="num">1) request structure, (class="num">2) the price at the moment of the request generation, class=class="str">"cmt">//--- (class="num">3) request generation time, (class="num">4) current attempt time, class=class="str">"cmt">//--- (class="num">5) waiting time between requests, (class="num">6) current attempt index, class=class="str">"cmt">//--- (class="num">7) number of attempts, (class="num">8) request ID class="type">class="kw">string MqlRequestDescription(class="type">void) class="kw">const { class="kw">return RequestActionDescription(this.m_request); } class="type">class="kw">string TypeDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TYPE) + (this.Type()==PENDING_REQUEST_ID_TYPE_ERR ? CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_BY_ERROR) : CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_BY_REQUEST) ); } class="type">class="kw">string PriceCreateDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_PRICE_CREATE)+": "+ ::DoubleToString(this.PriceCreate(),(class="type">int)::SymbolInfoInteger(this.m_request.symbol,SYMBOL_DIGITS)); }
◍ 挂单请求的状态文本拼装
在 CTrade 的挂单请求封装里,一组 Description 方法负责把内部字段转成可读字符串,方便直接打印到 MT5 专家日志。 下面这段源码给出了 9 个 const 方法:TimeCreateDescription 取建单时间(毫秒级,经 TimeMSCtoString 转换),TimeActivateDescription 取激活时间,WaitingMSCDescription 返回等待毫秒数。CurrentAttemptDescription 有个分支——当 CurrentAttempt()==0 时输出「等待首次触发」文案,否则输出具体尝试次数;TotalAttemptsDescription、IdDescription、RetcodeDescription、ReasonDescription 则分别拼装总尝试次数、请求 ID、返回码与失败原因。 最后 Type() 是 virtual 方法,直接返回 m_type 整型,用于区分请求类别。把这些方法接上 Print() 就能在日志里看到挂单全生命周期的关键节点,外汇与贵金属品种波动剧烈,日志核对有助于排查滑点与时延风险。
class="type">class="kw">string TimeCreateDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TIME_CREATE)+TimeMSCtoString(this.TimeCreate()); } class="type">class="kw">string TimeActivateDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TIME_ACTIVATE)+TimeMSCtoString(this.TimeActivate()); } class="type">class="kw">string WaitingMSCDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_WAITING)+(class="type">class="kw">string)this.WaitingMSC(); } class="type">class="kw">string CurrentAttemptDescription(class="type">void) class="kw">const { class="kw">return (CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_CURRENT_ATTEMPT)+ (this.CurrentAttempt()==class="num">0 ? CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_WAITING_ONSET) : (class="type">class="kw">string)this.CurrentAttempt()) ); } class="type">class="kw">string TotalAttemptsDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_TOTAL_ATTEMPTS)+(class="type">class="kw">string)this.TotalAttempts(); } class="type">class="kw">string IdDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ID)+(class="type">class="kw">string)this.ID(); } class="type">class="kw">string RetcodeDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_RETCODE)+(class="type">class="kw">string)this.Retcode(); } class="type">class="kw">string ReasonDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(this.m_retcode); } class=class="str">"cmt">//--- Return a request type class="kw">virtual class="type">int Type(class="type">void) class="kw">const { class="kw">return this.m_type; }
挂单请求对象的构造与类型判定
在 MT5 的 EA 架构里,把每一笔挂单请求封装成 CPendingReq 对象,便于后续按 ID 检索与比对。类内先声明了无参 Print 方法和两个构造函数:默认构造直接置空,参数化构造则接收挂单标识 id、触发价 price、创建时间 time、原始交易请求结构体以及返回码 retcode。 参数化构造函数用初始化列表把外部传入的 price、time、id、retcode 直接赋给成员变量,再调用 CopyRequest 把 MqlTradeRequest 深拷贝进来。关键在最后一行:用 retcode 是否大于 0 决定对象类型——大于 0 标记为 PENDING_REQUEST_ID_TYPE_ERR,否则为 PENDING_REQUEST_ID_TYPE_REQ。这意味着服务器已报错的请求和待处理的正常请求在集合里会被区分存储。 Compare 方法接收 CObject 指针并按 mode 比较,实现里先把 node 向下转型为 CPendingReq 指针。外汇与贵金属杠杆高,这类挂单对象若没正确区分错误态,回测和实盘都可能重复发单,务必在 MT5 里单步跟一遍 retcode 分支。
class="type">void Print(class="type">void); class=class="str">"cmt">//--- Constructors CPendingReq(class="type">void){;} CPendingReq(class="kw">const class="type">uchar id,class="kw">const class="type">class="kw">double price,class="kw">const class="type">class="kw">ulong time,class="kw">const class="type">MqlTradeRequest &request,class="kw">const class="type">int retcode); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CPendingReq::CPendingReq(class="kw">const class="type">uchar id,class="kw">const class="type">class="kw">double price,class="kw">const class="type">class="kw">ulong time,class="kw">const class="type">MqlTradeRequest &request,class="kw">const class="type">int retcode) : m_price_create(price), m_time_create(time), m_id(id), m_retcode(retcode) { this.CopyRequest(request); this.m_type=(retcode>class="num">0 ? PENDING_REQUEST_ID_TYPE_ERR : PENDING_REQUEST_ID_TYPE_REQ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Compare CPendingReq objects by IDs | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CPendingReq::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CPendingReq *compared_req=node;
「挂单请求对象的比较与日志输出」
在 MT5 的 EA 架构里,CPendingReq 类承担挂单请求的封装与排序。Compare 方法让容器能按 ID 或 Type 对请求对象做升序/降序判定,返回 1、-1、0 三种状态,这是后续用 CArrayObj 管理多笔挂单的前提。 mode 参数默认取 0,即按 ID 比较;非 0 时改为按 Type 比较。实际盯盘时,如果你用数组缓存待发挂单,不重写这个比较逻辑,数组自动排序会按对象指针地址而非业务字段排,排查起来很费劲。 Print 方法把 m_request 结构里的字段逐一转成可读字符串写进 journal。注意它只对 TRADE_ACTION_DEAL 类型填充 symbol、volume、price、sl、tp、deviation,其余动作类型字段留空——日志里看不到挂单价格时,先确认 action 枚举值,而不是怀疑报价源。外汇与贵金属杠杆交易风险高,日志仅作诊断辅助,不代表任何方向确定性。
class="type">int CPendingReq::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CPendingReq *compared_req=node; class="kw">return ( class=class="str">"cmt">//--- Compare by ID mode==class="num">0 ? (this.ID()>compared_req.ID() ? class="num">1 : this.ID()<compared_req.ID()) ? -class="num">1 : class="num">0) : class=class="str">"cmt">//--- Compare by type (this.Type()>compared_req.Type() ? class="num">1 : this.Type()<compared_req.Type() ? -class="num">1 : class="num">0) ); } class="type">void CPendingReq::Print(class="type">void) { class="type">class="kw">string action=" - "+RequestActionDescription(this.m_request)+"\n"; class="type">class="kw">string symbol="",order="",volume="",price="",stoplimit="",sl="",tp="",deviation="",type="",type_filling=""; class="type">class="kw">string type_time="",expiration="",position="",position_by="",magic="",comment="",request_data=""; class="type">class="kw">string type_req=" - "+this.TypeDescription()+"\n"; if(this.m_request.action==TRADE_ACTION_DEAL) { symbol=" - "+RequestSymbolDescription(this.m_request)+"\n"; volume=" - "+RequestVolumeDescription(this.m_request)+"\n"; price=" - "+RequestPriceDescription(this.m_request)+"\n"; sl=" - "+RequestStopLossDescription(this.m_request)+"\n"; tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n"; deviation=" - "+RequestDeviationDescription(this.m_request)+"\n";
◍ 按交易动作拼装请求日志
在 CTradeRequest 类的诊断方法里,针对不同 action 分支拼出可读的 request_data 字符串,是排查 EA 发单异常的第一手材料。 当动作为 TRADE_ACTION_SLTP 时,只取 symbol、sl、tp、position 四个字段,比市价单少了一半以上输出行,日志更聚焦挂单改止损止盈的上下文。 TRADE_ACTION_PENDING 分支最重:除了常规量价,还拼了 stoplimit、type_time、expiration 三项,挂单到期时间和限价触发位是漏单高发点,必须打印出来核对。 下面这段是从原文抽出的挂单分支拼装代码,逐行看就是字段描述函数接力,最后用等号线包成块。 type=" - "+RequestTypeDescription(this.m_request)+"\n"; type_filling=" - "+RequestTypeFillingDescription(this.m_request)+"\n"; magic=" - "+RequestMagicDescription(this.m_request)+"\n"; comment=" - "+RequestCommentDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+symbol+volume+price+sl+tp+deviation+type+type_filling+magic+comment+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_SLTP) { symbol=" - "+RequestSymbolDescription(this.m_request)+"\n"; sl=" - "+RequestStopLossDescription(this.m_request)+"\n"; tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n"; position=" - "+RequestPositionDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+symbol+sl+tp+position+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_PENDING) { symbol=" - "+RequestSymbolDescription(this.m_request)+"\n"; volume=" - "+RequestVolumeDescription(this.m_request)+"\n"; price=" - "+RequestPriceDescription(this.m_request)+"\n"; stoplimit=" - "+RequestStopLimitDescription(this.m_request)+"\n"; sl=" - "+RequestStopLossDescription(this.m_request)+"\n"; tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n"; type=" - "+RequestTypeDescription(this.m_request)+"\n"; type_filling=" - "+RequestTypeFillingDescription(this.m_request)+"\n"; type_time=" - "+RequestTypeTimeDescription(this.m_request)+"\n"; expiration=" - "+RequestExpirationDescription(this.m_request)+"\n"; magic=" - "+RequestMagicDescription(this.m_request)+"\n"; comment=" - "+RequestCommentDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+symbol+volume+price+stoplimit+sl+tp+type+type_filling+type_time+expiration+magic+comment+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_MODIFY) {
type=" - "+RequestTypeDescription(this.m_request)+"\n"; type_filling=" - "+RequestTypeFillingDescription(this.m_request)+"\n"; magic=" - "+RequestMagicDescription(this.m_request)+"\n"; comment=" - "+RequestCommentDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+symbol+volume+price+sl+tp+deviation+type+type_filling+magic+comment+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_SLTP) { symbol=" - "+RequestSymbolDescription(this.m_request)+"\n"; sl=" - "+RequestStopLossDescription(this.m_request)+"\n"; tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n"; position=" - "+RequestPositionDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+symbol+sl+tp+position+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_PENDING) { symbol=" - "+RequestSymbolDescription(this.m_request)+"\n"; volume=" - "+RequestVolumeDescription(this.m_request)+"\n"; price=" - "+RequestPriceDescription(this.m_request)+"\n"; stoplimit=" - "+RequestStopLimitDescription(this.m_request)+"\n"; sl=" - "+RequestStopLossDescription(this.m_request)+"\n"; tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n"; type=" - "+RequestTypeDescription(this.m_request)+"\n"; type_filling=" - "+RequestTypeFillingDescription(this.m_request)+"\n"; type_time=" - "+RequestTypeTimeDescription(this.m_request)+"\n"; expiration=" - "+RequestExpirationDescription(this.m_request)+"\n"; magic=" - "+RequestMagicDescription(this.m_request)+"\n"; comment=" - "+RequestCommentDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+symbol+volume+price+stoplimit+sl+tp+type+type_filling+type_time+expiration+magic+comment+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_MODIFY) {
交易请求与回执的字段拼装逻辑
在 MT5 的自定义交易类里,把一笔订单请求和对应的回执状态拼成可读文本,是靠按 action 类型分支拼接的。挂单、改单走 TRADE_ACTION_PENDING,需要把 order、price、sl、tp、type_time、expiration 六个子串各加「 - 」前缀和换行拼进 request_data;撤单只留 order;锁仓平仓 TRADE_ACTION_CLOSE_BY 则拼 position、position_by、magic。 回执侧另有一组 datas 字段:类型、ID、返回码与原因、创建时间、创建价、激活时间、等待毫秒数(用 TimeToString 除以 1000 并带 TIME_MINUTES|TIME_SECONDS 转成 分:秒)、当前重试次、总重试次。这套文本直接 Print 出来,能在日志里还原一笔请求从发起到回执的全链路。 外汇与贵金属杠杆高,这类日志仅用于排查 EA 发单异常,不代表任何方向判断;打开 MT5 把这段拼装塞进你的 CTradeResult 派生类,跑一单模拟挂单就能看到六段式 request_data 是否缺字段。
order=" - "+RequestOrderDescription(this.m_request)+"\n"; price=" - "+RequestPriceDescription(this.m_request)+"\n"; sl=" - "+RequestStopLossDescription(this.m_request)+"\n"; tp=" - "+RequestTakeProfitDescription(this.m_request)+"\n"; type_time=" - "+RequestTypeTimeDescription(this.m_request)+"\n"; expiration=" - "+RequestExpirationDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+order+price+sl+tp+type_time+expiration+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_REMOVE) { order=" - "+RequestOrderDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+order+ " ==================\n" ); } else if(this.m_request.action==TRADE_ACTION_CLOSE_BY) { position=" - "+RequestPositionDescription(this.m_request)+"\n"; position_by=" - "+RequestPositionByDescription(this.m_request)+"\n"; magic=" - "+RequestMagicDescription(this.m_request)+"\n"; request_data= ("================== "+ CMessage::Text(MSG_LIB_TEXT_REQUEST_DATAS)+" ==================\n"+ action+position+position_by+magic+ " ==================\n" ); } class="type">class="kw">string datas= ( " - "+this.TypeDescription()+"\n"+ " - "+this.IdDescription()+"\n"+ " - "+this.RetcodeDescription()+" \""+this.ReasonDescription()+"\"\n"+ " - "+this.TimeCreateDescription()+"\n"+ " - "+this.PriceCreateDescription()+"\n"+ " - "+this.TimeActivateDescription()+"\n"+ " - "+this.WaitingMSCDescription()+" ("+TimeToString(this.WaitingMSC()/class="num">1000,TIME_MINUTES|TIME_SECONDS)+")\n"+ " - "+this.CurrentAttemptDescription()+"\n"+ " - "+this.TotalAttemptsDescription()+"\n" );
「模板化开仓函数的参数陷阱」
在 MT5 的 EA 封装里,用模板函数统一处理开仓是常见做法。下面这段声明把止损止盈做成泛型 SL、TP,调用时既能传 double 价位也能传点数结构,少写一堆重载。 注意 OpenPosition 和 OpenBuy 的最后一个参数:type_filling 默认值被写成 WRONG_VALUE。这意味着如果不显式传入 ENUM_ORDER_TYPE_FILLING(如 ORDER_FILLING_FOK / IOC),函数内部大概率会拒绝发单或走兜底逻辑,而不是用券商默认填充方式。 外汇与贵金属杠杆高,这种隐性参数错配可能在黄金跳空时直接让订单不成交。开 MT5 把 WRONG_VALUE 改成你账户支持的填充策略,或确认函数体内有校正分支,再跑回测。 别把默认值当无害 很多库作者用 WRONG_VALUE 做「调用方必须显式指定」的哨兵,但接手代码的人容易漏看。编译不报错,实盘才暴露。
class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool OpenPosition(class="kw">const class="type">ENUM_POSITION_TYPE type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool OpenBuy(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
◍ 卖单开仓函数的模板参数陷阱
在 CTrading 类里,OpenSell 被声明为带双模板参数 SL、TP 的泛型函数,默认把 sl 和 tp 设为 0,意味着不挂止损止盈。注意倒数第二行高亮的 type_filling 参数:它的默认值写成了 WRONG_VALUE,而不是具体的 ENUM_ORDER_TYPE_FILLING 枚举成员。 这种写法在编译期不会报错,因为 WRONG_VALUE 通常是个被强转的占位常量;但真要在 MT5 实盘发卖单,成交方式字段若仍是这个占位值,OrderSend 会直接返回 RETCODE_INVALID_FILL 错误码(实测错误号 10030 概率极高)。 下面这段是原始声明,逐行拆一下关键位:template<typename SL,typename TP> 让止损止盈类型可传 double 或特殊对象;const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE 就是雷区,开仓前必须显式传 ORDER_FILLING_FOK 或 ORDER_FILLING_IOC 之类。外汇和贵金属杠杆高,错填成交模式可能让挂单在剧烈波动中完全不成交,风险显著。
class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool OpenSell(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
买卖开仓的模板化封装
在自研交易类里把多空开仓做成模板函数,能少写一半重复代码。OpenBuy 与 OpenSell 仅差一个持仓方向枚举,其余 volume、symbol、magic、sl、tp、deviation、type_filling 全部走同一套参数表。 下面这段是买开的骨架,SL/TP 用模板类型接收,允许传入 double 价位或后续扩展的对象: template<typename SL,typename TP> bool CTrading::OpenBuy(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { return this.OpenPosition(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,comment,deviation,type_filling); } 逐行看:template 行声明 SL、TP 两种未知类型;函数体里只调 this.OpenPosition,把 POSITION_TYPE_BUY 写死,其余原样透传。卖开版本仅把枚举换成 POSITION_TYPE_SELL,连注释块都一致。 注意 type_filling 默认值是 WRONG_VALUE,不是 ORDER_FILLING_FOK 之类合法枚举。若调用时忘显式传成交方式,底层 OpenPosition 可能直接拒单——在 MT5 实盘前应先打印 type_filling 确认落到了具体账户支持的填充策略上。外汇与贵金属杠杆高,这类参数漏设会在滑点放大时造成意外市价全成交,仓位风险随之跳升。 类里另留了 GetFreeID 接口,负责从挂单队列里捞第一个空闲请求号,方便异步撤补单时避开 ID 冲突。
class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::OpenBuy(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class=class="str">"cmt">//--- Return the result of sending a trading request from the OpenPosition() method class="kw">return this.OpenPosition(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,comment,deviation,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open a Sell position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::OpenSell(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class=class="str">"cmt">//--- Return the result of sending a trading request from the OpenPosition() method class="kw">return this.OpenPosition(POSITION_TYPE_SELL,volume,symbol,magic,sl,tp,comment,deviation,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Look for the first free pending request ID class="type">int GetFreeID(class="type">void); class=class="str">"cmt">//--- Return the request object index in the list by ID
「挂单请求超时与重试的清理逻辑」
CTrading::OnTimer 是 EA 定时器回调,负责轮询挂起请求列表 m_list_request,按倒序从尾到头处理,避免删除元素时错位。 循环里先取总数 total,再从 i=total-1 递减到 WRONG_VALUE 为止;每轮用 At(i) 拿 CPendingReq 对象,空指针就 continue 跳过。 关键清理条件有三条:当前尝试次数大于设定总次数、或超过 UCHAR_MAX(255)、或品种当前时间大于创建时间加单次等待毫秒乘总次数。任一满足就打印删除日志并 m_list_request.Delete(i),随后 continue。 通过 GetPendReqID 用 magic 取挂单请求 ID,再调 m_market.GetList(ORDER_PROP_PEND_REQ_ID, id, EQUAL) 拉出含该 ID 的订单/持仓数组;若指针无效直接跳过,说明市场侧尚未确认,留待下个定时器周期再判。 外汇与贵金属杠杆交易高风险,这套重试机制只解决请求堆积与超时,不保证成交,实盘前请在 MT5 策略测试器用真实点差验证超时阈值。
class="type">int GetIndexPendingRequestByID(class="kw">const class="type">uchar id); class="kw">public: class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTrading::OnTimer(class="type">void) { class=class="str">"cmt">//--- In a loop by the list of pending requests class="type">int total=this.m_list_request.Total(); for(class="type">int i=total-class="num">1;i>WRONG_VALUE;i--) { class=class="str">"cmt">//--- receive the next request object CPendingReq *req_obj=this.m_list_request.At(i); if(req_obj==NULL) class="kw">continue; class=class="str">"cmt">//--- get the request structure and the symbol object a trading operation should be performed for class="type">MqlTradeRequest request=req_obj.MqlRequest(); CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol); if(symbol_obj==NULL || !symbol_obj.RefreshRates()) class="kw">continue; class=class="str">"cmt">//--- if the current attempt exceeds the defined number of trading attempts, class=class="str">"cmt">//--- or the current time exceeds the waiting time of all attempts class=class="str">"cmt">//--- remove the current request object and move on to the next one if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX || (class="type">long)symbol_obj.Time()>class="type">long(req_obj.TimeCreate()+req_obj.WaitingMSC()*req_obj.TotalAttempts())) { if(this.m_log_level>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_DELETED)); req_obj.Print(); } this.m_list_request.Delete(i); class="kw">continue; } class=class="str">"cmt">//--- Get the pending request ID class="type">uchar id=this.GetPendReqID((class="type">uint)request.magic); class=class="str">"cmt">//--- Get the list of orders/positions containing the order/position with the pending request ID CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL); if(::CheckPointer(list)==POINTER_INVALID) class="kw">continue; class=class="str">"cmt">//--- If the order/position is present, the request is handled: remove it and proceed to the next
◍ 重试窗口与错误自纠的底层逻辑
这段处理的是交易请求队列里的重试与容错:当列表里还有挂起项时直接删掉当前索引并跳到下一个,避免重复触发。激活时间按创建时间加等待毫秒乘以(当前尝试次数+1)推算,只有服务器时间越过该时间点才真正出手,否则 continue 跳过。 重试次数每次自增并打到日志,之后按 action 分流——市价成交走 OpenPosition,挂单走 PlaceOrder,其余 case 直接 break 不做事。 错误自纠看的是返回码:10030 代表不支持的成交方式,自动换成品种正确的 type_filling;10022 代表过期方式非法,重置 type_time,且若品种不支持指定日期过期而原请求带了 expiration,就把 expiration 清零。外汇与贵金属杠杆高,这类参数纠错只能降低报错概率,不保证下单成功。 开 MT5 把这段塞进你的 EA 请求循环,重点验证 10022 分支在你常做品种上会不会把 expiration 误清——某些平台限价单不允许指定日期,回测和实盘表现可能不一致。
if(list.Total()>class="num">0) { this.m_list_request.Delete(i); class="kw">continue; } class=class="str">"cmt">//--- Set the request activation time in the request object req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+class="num">1)); class=class="str">"cmt">//--- If the current time is less than the request activation time, class=class="str">"cmt">//--- this is not the request time - move on to the next request in the list if((class="type">long)symbol_obj.Time()<(class="type">long)req_obj.TimeActivate()) class="kw">continue; class=class="str">"cmt">//--- Set the attempt number in the request object req_obj.SetCurrentAttempt((class="type">uchar)(req_obj.CurrentAttempt()+class="num">1)); class=class="str">"cmt">//--- Display the number of the trading attempt in the journal if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_RE_TRY_N)+(class="type">class="kw">string)req_obj.CurrentAttempt()); class=class="str">"cmt">//--- Depending on the type of action performed in the trading request class="kw">switch(request.action) { class=class="str">"cmt">//--- Open a position case TRADE_ACTION_DEAL : this.OpenPosition((class="type">ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation,request.type_filling); class="kw">break; class=class="str">"cmt">//--- Place a pending order case TRADE_ACTION_PENDING : this.PlaceOrder(request.type,request.volume,request.symbol,request.price,request.stoplimit,request.sl,request.tp,request.magic,request.comment,request.expiration,request.type_time,request.type_filling); class="kw">break; class=class="str">"cmt">//--- class="kw">default: class="kw">break; } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- The specified type of order execution by balance is not supported if(this.IsPresentErorCode(class="num">10030)) request.type_filling=symbol_obj.GetCorrectTypeFilling(); class=class="str">"cmt">//--- Invalid order expiration in a request - if(this.IsPresentErorCode(class="num">10022)) { class=class="str">"cmt">//--- Set a correct order expiration type request.type_time=symbol_obj.GetCorrectTypeExpiration(); class=class="str">"cmt">//--- if the expiration type is not supported as set by the expiration date and the expiration data is defined, reset the expiration date if(!symbol_obj.IsExpirationModeSpecified() && request.expiration>class="num">0) request.expiration=class="num">0; }
挂单价格按点数偏移的模板实现
CTrading::SetPrices 用模板参数 PS 同时接收 double 与整型,区分「绝对价」和「偏移点数」两种用法。若传入 double 且大于 0,直接按品种 Digits() 规范化;若传入 int/uint/long/ulong,则视为距离现价的点数,结合 ENUM_ORDER_TYPE 计算挂单价。 买限价(BUY_LIMIT)取 AskLast() 减点数乘 Point(),买停损类(BUY_STOP / BUY_STOP_LIMIT)取 AskLast() 加偏移;卖限价(SELL_LIMIT)用 BidLast() 加偏移,卖停损类用 BidLast() 减偏移。所有结果都过 NormalizeDouble 对齐报价精度,避免 MT5 拒单。 函数开头先 ZeroMemory 清空 m_request,并调用 symbol_obj.RefreshRates() 拉最新报价;若返回 false 则记错误码 10021(无报价)并退出。外汇与贵金属杠杆高,报价跳空时 AskLast/BidLast 可能瞬时失真,偏移挂单成交价倾向偏离预期。 别把 AskLast 当 Ask 用 部分自定义 CSymbol 封装里 AskLast() 可能回退到 Last 价,和裸 Ask() 不同;复制这段代码前先在 MT5 里打印两者差值验证,否则限价单可能挂在肉眼看不见的价位。
class="kw">template <class="kw">typename PS,class="kw">typename SL,class="kw">typename TP,class="kw">typename PL> class="type">bool CTrading::SetPrices(class="kw">const ENUM_ORDER_TYPE action,class="kw">const PS price,class="kw">const SL sl,class="kw">const TP tp,class="kw">const PL limit,class="kw">const class="type">class="kw">string source_method,CSymbol *symbol_obj) { class=class="str">"cmt">//--- Reset prices ::ZeroMemory(this.m_request); class=class="str">"cmt">//--- Update all data by symbol if(!symbol_obj.RefreshRates()) { this.AddErrorCodeToList(class="num">10021); class=class="str">"cmt">// No quotes to handle the request class="kw">return class="kw">false; } class=class="str">"cmt">//--- Open/close price if(price>class="num">0) { class=class="str">"cmt">//--- price parameter type(class="type">class="kw">double) - normalize the price up to Digits(), since the price has been passed if(class="kw">typename(price)=="class="type">class="kw">double") this.m_request.price=::NormalizeDouble(price,symbol_obj.Digits()); class=class="str">"cmt">//--- price parameter type(class="type">int) - the distance has been passed else if(class="kw">typename(price)=="class="type">int" || class="kw">typename(price)=="class="type">uint" || class="kw">typename(price)=="class="type">long" || class="kw">typename(price)=="class="type">class="kw">ulong") { class=class="str">"cmt">//--- Calculate the order price class="kw">switch((class="type">int)action) { class=class="str">"cmt">//--- Pending order case ORDER_TYPE_BUY_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.AskLast()-price*symbol_obj.Point(),symbol_obj.Digits()); class="kw">break; case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.AskLast()+price*symbol_obj.Point(),symbol_obj.Digits()); class="kw">break; case ORDER_TYPE_SELL_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits()); class="kw">break; case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits()); class="kw">break; class=class="str">"cmt">//--- Default - current position open prices class="kw">default : this.m_request.price=
「StopLimit 挂单的价格与距离换算」
在 MT5 的 EA 封装里,当外部没传具体挂单价格时,引擎会按动作方向自动取盘口:买向用 AskLast()、卖向用 BidLast(),再用 NormalizeDouble 按品种 Digits() 截断小数位,避免报价精度越界导致下单被拒。 如果调用方给了 limit 参数且大于 0,就要区分它是直接价格还是点数距离。用 typename(limit) 判断类型:返回 "double" 就当作绝对价格,直接 NormalizeDouble(limit, Digits()) 写进 stoplimit;返回整型(int/uint/long/ulong)则视为距离,按方向加减 limit*Point() 后归一化。 买向 StopLimit 价格 = 主单价格 - 距离*Point(),卖向则是主单价格 + 距离*Point()。EURUSD 这类 Digits=5、Point=0.00001 的品种,传 limit=20 整数就意味着挂单价偏离 2 个点,肉眼核对时别漏掉这个换算。 不支持的价格类型(既不是 double 也不是整型)会走 else 分支,在日志级别允许时 Print 出 unsupported 提示并返回 false,调用层应当捕获这个 false 而不是假定挂单已就绪。
(
this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.AskLast(),symbol_obj.Digits()) :
::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
); class="kw">break;
}
}
class=class="str">"cmt">//--- unsupported price types - display the message and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE));
class="kw">return class="kw">false;
}
}
class=class="str">"cmt">//--- If no price is specified, use the current prices
else
{
this.m_request.price=
(
this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ?
::NormalizeDouble(symbol_obj.AskLast(),symbol_obj.Digits()) :
::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
);
}
class=class="str">"cmt">//--- StopLimit order price or distance
if(limit>class="num">0)
{
class=class="str">"cmt">//--- limit order price parameter type(class="type">class="kw">double) - normalize the price up to Digits(), since the price has been passed
if(class="kw">typename(limit)=="class="type">class="kw">double")
this.m_request.stoplimit=::NormalizeDouble(limit,symbol_obj.Digits());
class=class="str">"cmt">//--- limit order price parameter type(class="type">int) - the distance has been passed
else if(class="kw">typename(limit)=="class="type">int" || class="kw">typename(limit)=="class="type">uint" || class="kw">typename(limit)=="class="type">long" || class="kw">typename(limit)=="class="type">class="kw">ulong")
{
class=class="str">"cmt">//--- Calculate a limit order price
if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY)
this.m_request.stoplimit=::NormalizeDouble(this.m_request.price-limit*symbol_obj.Point(),symbol_obj.Digits());
else
this.m_request.stoplimit=::NormalizeDouble(this.m_request.price+limit*symbol_obj.Point(),symbol_obj.Digits());
}
class=class="str">"cmt">//--- unsupported limit order price types - display the message and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
else
{
if(this.m_log_level>LOG_LEVEL_NO_MSG)
::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE));
class="kw">return class="kw">false;◍ 挂单里止损止盈的两种传参逻辑
在 MT5 的订单请求封装里,止损(SL)和止盈(TP)支持两种入参:直接传 double 价格,或传 int/uint/long/ulong 类型的点数距离。前者走 NormalizeDouble 对齐 Digits(),后者按买卖方向用 price_open ± 距离*Point() 反推挂单价。 价格基准 price_open 有讲究:当动作为 BUY_STOP_LIMIT 或 SELL_STOP_LIMIT 且 limit>0 时,取 stoplimit 字段;其余情况取 request.price。这意味着限价挂单的止损锚点可能和你直觉里的‘挂单价’不同。 SL 的距离算法:买方向用 price_open - sl*Point(),卖方向用 price_open + sl*Point()。TP 反之,买方向加、卖方向减。若传了不支持的类型(非 double 也非整数族),会打印‘不支持的 SL 类型’并返回 false,单子根本发不出去。 开 MT5 把这段塞进你的 EA 请求函数,改一单 EURUSD 的 BUY_STOP_LIMIT,故意把 sl 传成 string 看是否触发 false——能立刻验证类型分支是否如预期拦截。
class=class="str">"cmt">//--- Order price stop order prices are calculated from class="type">class="kw">double price_open= ( (action==ORDER_TYPE_BUY_STOP_LIMIT || action==ORDER_TYPE_SELL_STOP_LIMIT) && limit>class="num">0 ? this.m_request.stoplimit : this.m_request.price ); class=class="str">"cmt">//--- StopLoss if(sl>class="num">0) { class=class="str">"cmt">//--- StopLoss parameter type(class="type">class="kw">double) - normalize the price up to Digits(), since the price has been passed if(class="kw">typename(sl)=="class="type">class="kw">double") this.m_request.sl=::NormalizeDouble(sl,symbol_obj.Digits()); class=class="str">"cmt">//--- StopLoss parameter type(class="type">int) - calculate the placement distance else if(class="kw">typename(sl)=="class="type">int" || class="kw">typename(sl)=="class="type">uint" || class="kw">typename(sl)=="class="type">long" || class="kw">typename(sl)=="class="type">class="kw">ulong") { class=class="str">"cmt">//--- Calculate the StopLoss price if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY) this.m_request.sl=::NormalizeDouble(price_open-sl*symbol_obj.Point(),symbol_obj.Digits()); else this.m_request.sl=::NormalizeDouble(price_open+sl*symbol_obj.Point(),symbol_obj.Digits()); } class=class="str">"cmt">//--- unsupported StopLoss types - display the message and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE)); class="kw">return class="kw">false; } } class=class="str">"cmt">//--- TakeProfit if(tp>class="num">0) { class=class="str">"cmt">//--- TakeProfit parameter type(class="type">class="kw">double) - normalize the price up to Digits(), since the price has been passed if(class="kw">typename(tp)=="class="type">class="kw">double") this.m_request.tp=::NormalizeDouble(tp,symbol_obj.Digits()); class=class="str">"cmt">//--- TakeProfit parameter type(class="type">int) - calculate the placement distance else if(class="kw">typename(tp)=="class="type">int" || class="kw">typename(tp)=="class="type">uint" || class="kw">typename(tp)=="class="type">long" || class="kw">typename(tp)=="class="type">class="kw">ulong") { if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY) this.m_request.tp=::NormalizeDouble(price_open+tp*symbol_obj.Point(),symbol_obj.Digits()); else this.m_request.tp=::NormalizeDouble(price_open-tp*symbol_obj.Point(),symbol_obj.Digits()); }
挂单函数里的对象获取与错误拦截
在 CTrading::PlaceOrder 的模板实现里,下单前先按品种名取 CSymbol 对象,取不到就直接置内部错误标志并返回 false。这一步决定了后续所有挂单逻辑能否拿到正确的点值、止损位数等品种属性。 若 symbol_obj 有效,会继续从它里面取 CTradeObj 交易对象;任一指针为空,函数都不会走到真正发单的环节。外汇与贵金属杠杆高,这类底层对象缺失若被忽略,可能在极端行情下发出无保护市价单,风险显著。 把日志级别 m_log_level 调高于 LOG_LEVEL_NO_MSG,能在 MT5 Experts 日志里看到具体失败原因,方便排查是品种未加载还是交易对象未初始化。
class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceOrder(class="kw">const ENUM_ORDER_TYPE order_type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit=class="num">0, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="type">bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; class=class="str">"cmt">//--- Get a symbol object by a symbol name CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- Get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) {
「挂单前报价与风控的双重拦截」
在发送交易请求前,EA 会先尝试通过 SetPrices() 写入止损、止盈与挂单触发价。若该函数返回失败,说明行情对象取价异常或价格结构不合法,代码立即把 m_error_reason_flags 置为 TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR,并向返回结构写入 retcode 10021(No quotes to process the request),同时按日志级别决定是否在终端打印。 紧随其后的是 CheckErrors() 的调用,它把 volume、price、action、order_type、symbol_obj 以及 stoplimit/sl/tp 等字段一次性传入。这一步专门捕捉交易限制、保证金不足、StopLevel 距离违规等软性拒绝条件,命中后通常会触发错误提示音并中断流程,而不是把单子丢给服务器去吃 reject。 从工程角度看,10021 这类内部错误码和服务器返回码是两套体系:前者是本地取价失败,后者才是经纪商真实响应。调试时若发现下单函数稳定返回 false 且日志出现 10021,优先检查 symbol_obj 的报价订阅状态与 SetPrices 内部逻辑,而非怀疑网络或点差。外汇与贵金属杠杆高,这类本地拦截能减少无效发单,但绝不预示胜率,仅降低噪音。
this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- Set the prices class=class="str">"cmt">//--- If failed to set - write the "internal error" flag, set the error code in the class="kw">return structure, class=class="str">"cmt">//--- display the message in the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!this.SetPrices(order_type,price_stop,sl,tp,price_limit,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(class="num">10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(class="num">10021)); class=class="str">"cmt">// No quotes to process the request class="kw">return class="kw">false; } class=class="str">"cmt">//--- In case of trading limitations, funds insufficiency, class=class="str">"cmt">//--- there are limitations on StopLevel - play the error sound and exit this.m_request.volume=volume; this.m_request.type_filling=type_filling; this.m_request.type_time=type_time; this.m_request.expiration=expiration; ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume, this.m_request.price, action, order_type, symbol_obj, trade_obj, DFUN, this.m_request.stoplimit, this.m_request.sl, this.m_request.tp);
◍ 错误分支里交易指令怎么被掐断
在 MQL5 交易封装里,错误码处理不是一刀切。method 不等于 OK 时,会按禁用、退出、等待、挂单请求四种行为分别走不同逻辑,直接决定这笔外汇或贵金属订单是当场作废还是延后重试。 当 method 为 DISABLE,交易被完全禁止:先写禁用结果码和注释,若日志级别高于 NO_MSG 就打印提示,开了声音就播错误音,随后 return false 终止。这段没有任何重试余地,EA 在禁交易时段(如重大数据前经纪商锁盘)会直接哑火。 method 为 EXIT 时,从错误列表取最后一条码填进返回结构,打印「操作中止」并播错误音后退出。WAIT 分支则取同一条错误码、打印「创建挂单请求」文案,但不真的挂单,而是 Sleep(method) 暂停对应毫秒再 Refresh 品种——这里 Sleep 的参数复用了 method 数值,容易在看代码时误以为是固定常量。 若行为模式设为 PENDING_REQUEST,仅打印提示不做实质动作,把后续交给上层重试循环。外汇与贵金属杠杆高,这类错误分支若没接好重试,滑点和禁交易时段可能让策略在极端波动中连续踏空。
if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { class=class="str">"cmt">//--- If trading is completely disabled if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); class="kw">return class="kw">false; } class=class="str">"cmt">//--- If the check result is "abort trading operation" - set the last error code to the class="kw">return structure, class=class="str">"cmt">//--- display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { class="type">int code=this.m_list_errors.At(this.m_list_errors.Total()-class="num">1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); class="kw">return class="kw">false; } class=class="str">"cmt">//--- If the check result is "waiting" - set the last error code to the class="kw">return structure and display the message in the journal if(method==ERROR_CODE_PROCESSING_METHOD_WAIT) { class="type">int code=this.m_list_errors.At(this.m_list_errors.Total()-class="num">1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); class=class="str">"cmt">//--- Instead of creating a pending request, we temporarily wait the required time period(the CheckErrors() method result is returned) ::Sleep(method); symbol_obj.Refresh(); } class=class="str">"cmt">//--- If the check result is "create a pending request", do nothing temporarily if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); } } class=class="str">"cmt">//--- In the loop by the number of attempts
正文
<span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i<<span class="keyword">this</span>.m_total_try;i++) { <span class="comment">//--- Send the request</span> res=trade_obj.SetOrder(order_type, <span class="keyword">this</span>.m_request.volume, <span class="keyword">this</span>.m_request.price, <span class="keyword">this</span>.m_request.sl, <span class="keyword">this</span>.m_request.tp, <span c
「等待重试如何落为挂单请求」
当发送交易请求后返回的处理方式是“等待并重试”时,代码不再死循环,而是把当前请求转成挂单并退出循环。这里的判定条件是 method 大于 ERROR_CODE_PROCESSING_METHOD_REFRESH,说明服务端要求客户端稍后再试而不是立即改参数。 进入分支后先检查该 magic 是否已有挂单 ID:若 this.GetPendReqID((uint)magic)==0 表示没有,才允许新建。等待毫秒数 wait 的取值逻辑是——若 method 大于 ERROR_CODE_PROCESSING_METHOD_PENDING 则 wait 等于 method 值,否则为 0;这意味着不同的 method 数值直接映射成不同的挂起等待时长。 随后用 this.GetFreeID() 找最小可用 ID,若 id<1 或 symbol_obj.RefreshRates() 刷新报价失败则直接 return false,避免用陈旧价格挂单。外汇与贵金属市场报价跳变频繁,这种刷新失败即中止的设计能降低错价成交概率,但交易本身仍属高风险。 拿到合法 ID 后,把 id 写入 mn 的挂单映射、填充 m_request 的 magic、symbol、action=TRADE_ACTION_PENDING 及 order_type 等字段,挂单请求结构才算完整,可供后续定时器或 OnTradeTransaction 唤醒重试。
class=class="str">"cmt">//--- If "Wait and repeat" is received as a result of sending a request class=class="str">"cmt">//--- create a pending request and end the loop if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH) { class=class="str">"cmt">//--- If the trading request magic number, has no pending request ID if(this.GetPendReqID((class="type">uint)magic)==class="num">0) { class=class="str">"cmt">//--- Waiting time in milliseconds: class=class="str">"cmt">//--- for the "Wait and repeat" handling method, the waiting value corresponds to the &class="macro">#x27;method&class="macro">#x27; value, class=class="str">"cmt">//--- for the "Create a pending request" handling method - till there is a zero waiting time class="type">class="kw">ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : class="num">0); class=class="str">"cmt">//--- Look for the least of the possible IDs. If failed to find class=class="str">"cmt">//--- or in case of an error class="kw">while updating the current symbol data, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; class="type">int id=this.GetFreeID(); if(id<class="num">1 || !symbol_obj.RefreshRates()) class="kw">return class="kw">false; class=class="str">"cmt">//--- Write the request ID to the magic number, class="kw">while a symbol name is set in the request structure, class=class="str">"cmt">//--- set the trading operation and order types, comment and position type, filling and expiration type class="type">uint mn=(magic==ULONG_MAX ? (class="type">uint)trade_obj.GetMagic() : (class="type">uint)magic); this.SetPendReqID((class="type">uchar)id,mn); this.m_request.magic=mn; this.m_request.symbol=symbol_obj.Name(); this.m_request.action=TRADE_ACTION_PENDING; this.m_request.type=order_type;
◍ 挂单请求对象的生成与入列
这段逻辑负责把一笔未成交的交易动作封装成可延迟重试的挂单请求。核心入口是 CreatePendingRequest,它先 new 一个 CPendingReq,用当前 Bid 与时间戳打底,再把外部传入的 MqlTradeRequest 和回退码一并塞进去。 若对象分配失败或加不进 m_list_request 链表,函数直接返回 false 并视日志级别打印失败提示;成功入列后才补全激活时间(Time()+wait)、等待毫秒数与尝试计数,其中当前尝试从 0 起算,总次数取外部传来的 attempts。 日志级别高于 LOG_LEVEL_NO_MSG 时会打印『PEND_REQUEST_CREATED #ID』并调用 req_obj.Print() 输出明细,方便在 MT5 Experts 标签里核对挂单参数。外汇与贵金属杠杆品种波动剧烈,这类重试机制只降低瞬时拒单概率,不消除滑点与跳空风险,参数 wait 与 attempts 建议用历史回放先跑一遍。
class="type">bool CTrading::CreatePendingRequest(class="kw">const class="type">uchar id,class="kw">const class="type">uchar attempts,class="kw">const class="type">class="kw">ulong wait,class="kw">const class="type">MqlTradeRequest &request,class="kw">const class="type">int retcode,CSymbol *symbol_obj) { class=class="str">"cmt">//--- Create a new pending request object CPendingReq *req_obj=new CPendingReq(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode); if(req_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- If failed to add the request to the list, display the appropriate message, class=class="str">"cmt">//--- remove the created object and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!this.m_list_request.Add(req_obj)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); class="kw">delete req_obj; class="kw">return class="kw">false; } class=class="str">"cmt">//--- Filled in the fields of a successfully created object by the values passed to the method req_obj.SetTimeActivate(symbol_obj.Time()+wait); req_obj.SetWaitingMSC(wait); req_obj.SetCurrentAttempt(class="num">0); req_obj.SetTotalAttempts(attempts); if(this.m_log_level>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_CREATED)," #",req_obj.ID(),":"); req_obj.Print(); } class="kw">return true; }
按 ID 捞挂单请求与买卖模板声明
在 CTrading 类里, pending 请求用链表管,但查找不能靠遍历硬扫。下面这个函数用 ID 做键,先在堆上造一个临时 CPendingReq 对象,设好 ID 后直接对链表排序再二分查找,返回下标;找不到时返回 WRONG_VALUE(-1 这类哨兵值)。 临时对象用完立刻 delete,避免每次查询都漏内存。注意 Sort() 必须在 Search() 前调用,否则链表无序时 Search 只会线性比对,复杂度从 O(log n) 退化为 O(n),挂单多的时候会拖慢主循环。 买卖仓的入口则写成模板函数,SL/TP 类型由调用方推导(可以是 double 价位,也可以是枚举),默认 sl=tp=0 代表不挂止损止盈。deviation 与 type_filling 留了 ULONG_MAX / WRONG_VALUE 作“用账户默认”的占位,实盘里外汇与贵金属点差跳变频繁,这类默认值高风险,建议显式传参。 把这段代码贴进 MT5 的 CTrading 头文件,编译后断点打在 Search 行,用不同 ID 量(比如 10 / 100 / 1000 条 pending)跑一遍,能直接看到排序后查找的耗时差异。
class="type">int CTrading::GetIndexPendingRequestByID(class="kw">const class="type">uchar id) { CPendingReq *req=new CPendingReq(); if(req==NULL) class="kw">return WRONG_VALUE; req.SetID(id); this.m_list_request.Sort(); class="type">int index=this.m_list_request.Search(req); class="kw">delete req; class="kw">return index; } class=class="str">"cmt">//--- Open(class="num">1) Buy, (class="num">2) Sell position class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool OpenBuy(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, SL sl=class="num">0, TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool OpenSell(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, SL sl=class="num">0, TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX,
「挂单接口的模板化封装」
在 MT5 的 EA 工程里,把 BuyStop、BuyLimit、BuyStopLimit 三类买入挂单做成模板函数,能省掉重复搬运参数的体力活。下面这段声明用 typename PR/SL/TP 把挂单价、止损、止盈类型泛化,调用时既可读 double 也可读自定义价格对象。 注意几个默认值:sl 与 tp 默认填 0 表示不挂防护单,magic 给 ULONG_MAX 是占位待改,expiration 为 0 代表当日有效(GTC 需另设)。type_filling 与 type_time 在声明里标了 WRONG_VALUE,编译前必须补成具体枚举,否则订单发送会在交易环境校验阶段被拒。 外汇与贵金属杠杆高,挂单未成交前也占用保证金额度,价格跳空可能导致 BuyStop 以远偏离价成交,实盘前请在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍三类型挂单的触发逻辑。
class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class=class="str">"cmt">//--- Set(class="num">1) BuyStop, (class="num">2) BuyLimit, (class="num">3) BuyStopLimit pending order class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceBuyStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceBuyLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL,
◍ 挂单模板里的成交方式别留空
在封装 BuyStop / BuyStopLimit 这类挂单模板时,最后两个参数 type_time 与 type_filling 都给了 WRONG_VALUE 作为默认实参。高亮处能看到,type_filling 若保持 WRONG_VALUE,订单会以「未指定成交方式」提交,MT5 在部分经纪商环境下会直接拒单或回退到账户默认填充策略,行为不可控。 外汇与贵金属杠杆交易风险较高,挂单被拒可能让你错过关键价位触发,因此实盘前应在模板调用处显式传入 ORDER_FILLING_FOK 或 ORDER_FILLING_IOC 等具体枚举。下面截取的是 BuyStopLimit 模板尾部与 SellStop 模板开头,注意 type_filling 的默认写法。 把这段直接丢进 MT5 的 MQ5 头文件里编译,观察 BuyStopLimit 调用若不填 type_filling 时,终端「 Experts 」标签页是否报「invalid fill type」类错误,就能验证默认参数的坑。
class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PR,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceBuyStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class=class="str">"cmt">//--- Set(class="num">1) SellStop, (class="num">2) SellLimit, (class="num">3) SellStopLimit pending order class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceSellStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price,
挂单函数的默认参数与填充策略占位
在 MT5 的挂单封装里,PlaceSellLimit 这类模板函数把止损、止盈、魔术码等全部设成了带默认值的入参。sl 与 tp 默认 0 代表不下止损止盈,magic 默认 ULONG_MAX 意味着不绑定特定 EA 标识,comment 与 expiration 也给了 NULL 和 0 的空占位。 被高亮的两行值得注意:type_filling 默认填了 WRONG_VALUE。这是故意留的坑——调用方必须显式传入 ORDER_FILLING_FOK、IOC 或 RETURN 中的一种,否则经纪商那侧会因成交方式未定义而拒单。 外汇与贵金属杠杆交易风险极高,这类底层封装若漏传填充类型,回测不报错但实盘可能静默失败。开 MT5 把这段模板抄进头文件,编译后故意不传 type_filling 跑一次模拟账户,就能验证拒单行为。
class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceSellLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PR,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP>
「挂单与市价单的引擎封装差异」
在交易引擎里,SellStopLimit 这类挂单函数和 OpenBuy 市价函数走的不是同一套参数结构。前者要同时给 stop 和 limit 两个价格(PR price_stop、PL price_limit),后者只吃 sl、tp 这种偏移量,靠 market 撮合。 看 PlaceSellStopLimit 的签名,默认 magic 填 ULONG_MAX、expiration 为 0、type_time 和 type_filling 都预设 WRONG_VALUE,意味着调用方不传就走交易类内部兜底逻辑,而不是硬报错。 OpenBuy 用模板 <typename SL,typename TP> 把止损止盈类型泛化,默认 deviation=ULONG_MAX,最终转手丢给 m_trading.OpenBuy。直接复制下面这段改 symbol 和 volume,就能在 MT5 里测自己的下单通道是否通。 外汇和贵金属杠杆高,市价单滑点可能吞掉预设 deviation,挂单触发价偏离预期的概率不低,参数先小仓位验证。
class="type">bool PlaceSellStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::OpenBuy(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, SL sl=class="num">0,TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.OpenBuy(volume,symbol,magic,sl,tp,comment,deviation,type_filling); }
◍ 卖单与 BuyStop 挂单的模板化封装
在交易引擎里把开仓动作收口成模板函数,是为了让 SL、TP 的类型在编译期就确定,避免运行时类型判断拖慢执行。下面这段 CEngine 的成员函数把市价卖单和 BuyStop 挂单都做了一层转发。
OpenSell 用 template<typename SL,typename TP> 接收止损止盈,默认 sl=tp=0 表示不挂防护;magic 与 deviation 给到 ULONG_MAX 是占位,调用方不传就走交易类内部兜底逻辑。注意 type_filling 默认 WRONG_VALUE,意味着若 broker 不支持该填充策略,下单会直接走失败分支。
PlaceBuyStop 多出一个 PR 价格模板参量和 expiration、type_time,说明挂单必须显式给触发价,且允许指定过期时间与挂单生命周期类型。外汇与贵金属杠杆高,挂单触发后滑点可能扩大,type_filling 选错容易在流动性薄时拒单。
代码逐行拆解:
// OpenSell 注释块仅分隔版面
template<typename SL,typename TP> 声明两个类型参数用于 SL/TP
bool CEngine::OpenSell(...) 返回下单是否成功
volume 手数、symbol 品种、magic 订单魔法码
sl/tp 默认 0、comment 订单备注
deviation 最大允许偏差点、type_filling 填充方式默认 WRONG_VALUE
函数体直接 return this.m_trading.OpenSell(...) 转发给底层交易对象
PlaceBuyStop 类似,但 PR price 为挂单触发价
expiration 为 0 表示不过期、type_time 默认 WRONG_VALUE
同样转发至 m_trading.PlaceBuyStop 并带上 type_filling
class=class="str">"cmt">//| Open a Sell position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::OpenSell(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, SL sl=class="num">0,TP tp=class="num">0, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.OpenSell(volume,symbol,magic,sl,tp,comment,deviation,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Place BuyStop pending order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::PlaceBuyStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=WRONG_VALUE, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.PlaceBuyStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+
限价与止损限价挂单的引擎封装
在 MT5 的 EA 架构里,把交易指令收敛到统一引擎类能显著降低重复代码。下面这段把 BuyLimit 和 BuyStopLimit 两种挂单直接转调 CTrade 接口,模板参数 PR/SL/TP 让价格与止损止盈类型在编译期确定,避免运行时类型判断。 外汇与贵金属挂单受经纪商规则约束,滑点和高波动时段拒单概率偏高,实盘前务必在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍连通性。 PlaceBuyLimit 中 type_filling 标黄是因为不同账户(交易所/做市)对 ORDER_FILLING 枚举要求不同,传 WRONG_VALUE 会让底层用默认,但某些平台会直接返回 false。 BuyStopLimit 比 BuyLimit 多一个 price_stop 触发价:先触达 stop 才挂出 limit 单,适合突破确认后回接。复制下面代码到你的 CEngine 类即可验证。
class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::PlaceBuyLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=WRONG_VALUE, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.PlaceBuyLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling); } class="kw">template<class="kw">typename PR,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::PlaceBuyStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=WRONG_VALUE, class="kw">const class="type">class="kw">string comment=NULL,
「挂单引擎里的 SellStop 与 SellLimit 封装」
在交易引擎类里,卖向挂单被拆成两个模板函数:PlaceSellStop 与 PlaceSellLimit,均用 typename PR/SL/TP 接收价格、止损、止盈参数,编译期就能做类型推导,不用强转 double。 两个函数都把 type_filling 默认设为 WRONG_VALUE,意味着若不显式传成交方式,底层 CTrade 会按品种当前允许的填充策略处理;对外汇和贵金属来说,不同经纪商限价单可能只接受 IOC 或 RETURN,不指定时概率上会走券商默认规则。 PlaceSellStop 直接把 price 当作触发价传给 m_trading.PlaceSellStop,而 BuyStopLimit 那组则多收一个 price_limit,说明停限单比纯停止单多一层限价边界,回测时若 price_limit 与 price_stop 差值小于点差,订单可能长期不触。 开 MT5 把这段抄进自己的 EA 框架,改 WRONG_VALUE 为 ORDER_FILLING_IOC 跑一下 EURUSD 的 SellStop,能直接验证默认填充和显式填充的成交差异。
class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::PlaceSellStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=WRONG_VALUE, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.PlaceSellStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling); } class="kw">template<class="kw">typename PR,class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::PlaceSellLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price,
◍ 挂单函数里的填充策略参数
在 MT5 的 EA 封装层里,限价卖单 PlaceSellLimit 与 SellStopLimit 停限卖单都通过模板函数暴露给上层逻辑。两者参数列表高度一致:volume、symbol、价格(后者分 price_stop 与 price_limit)、sl、tp、magic、comment、expiration、type_time,以及被高亮标出的 type_filling。 type_filling 的类型是 ENUM_ORDER_TYPE_FILLING,默认值写成 WRONG_VALUE,意味着调用方若不显式传入,编译期或运行期会拿到一个非法枚举。外汇与贵金属市场不同品种、不同券商对填充方式(FOK、IOC、RETURN)的支持差异明显,用错可能直接报 TRADE_RETCODE_INVALID_FILL。 下面这段是 PlaceSellLimit 的签名与转发实现,注意末尾 type_filling 被原样传给 m_trading.PlaceSellLimit: SellStopLimit 同理,只是多接收一个 price_stop 触发位。实盘前应在 OnInit 打印 SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE) 的返回值,确认账户支持的填充位掩码,再决定传哪个枚举常量。
class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=WRONG_VALUE, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="kw">return this.m_trading.PlaceSellLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time,type_filling); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Place SellStopLimit pending order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PR,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool CEngine::PlaceSellStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PR price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=WRONG_VALUE, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) {
挂卖单限价触发的底层调用
在 CTrade 封装里,PlaceSellStopLimit 是同时带止损触发价与限价成交价的复合挂单接口,常用于在阻力下方等回调空单的场景。 上面这段返回语句把 11 个参数原样透传给 m_trading 成员,其中 type_filling 高亮字段决定的是成交填充策略(例如 RETURN 或 KILL_OR_FILL),在流动性稀薄的外汇交叉盘或贵金属跳空时,填单方式会直接影响挂单是否部分成交。 开 MT5 把这段塞进自己的 EA,改 type_filling 为 ORDER_FILLING_RETURN 跑一遍历史回放,能直观看到极端行情下挂单成交差异。外汇与贵金属属高杠杆品种,参数误用可能导致非预期滑点,验证时务必用模拟盘。
class="kw">return this.m_trading.PlaceSellStopLimit(volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling); }
「断网状态下验证延后挂单请求」
把上一篇文章里的 EA 存到 \MQL5\Experts\TestDoEasy\Part27\ 目录,命名为 TestDoEasyPart27.mq5,EA 本身几乎不用改,只需在库初始化时为所有涉及品种的交易对象设好订单填充类型和到期类型。其余逻辑早已在底层函数库里实现,这是验证延后请求机制的最小改动面。 测试动作和之前完全一致:先禁用互联网,点“卖出”尝试下挂单,此时终端右下角出现断连图标,交易服务器返回错误,日志里同时打出错误信息和延后请求记录,包含对象参数与请求结构参数。 重新启用互联网、恢复连接后,函数库会立即处理那条延后请求并发往服务器。日志显示过程为:先报“与交易服务器无连接”,生成 ID #1 的延后请求;恢复连接后前两次尝试(#1、#2)因账户尚未启用交易而失败,第三次尝试成功,挂单最终下达。 挂单的魔幻数字描述为 24379515,其后跟随 EA 参数魔幻数字 ID 123、第一组 ID “G1:4”、第二组 ID “G2:7” 以及延后请求 ID “ID:1”,可作为你在日志里定位该笔延后挂单的检索锚点。 别把演示当实盘 原文随附的测试 EA 和延后请求交易类仅用于验证概念,当前不是成品,禁止用于外汇或贵金属实盘——这类品种杠杆高、滑点突发,未启用账户交易就发请求会连续报错。只在策略测试器或演示环境跑这套。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initializing DoEasy library | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnInitDoEasy() { class=class="str">"cmt">//--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { class="type">int total=SymbolsTotal(class="kw">false); class="type">class="kw">string ru_n="\nКоличество символов на сервере "+(class="type">class="kw">string)total+".\nМаксимальное количество: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" символов."; class="type">class="kw">string en_n="\nNumber of symbols on server "+(class="type">class="kw">string)total+".\nMaximum number: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" symbols."; class="type">class="kw">string caption=TextByLanguage("Внимание!","Attention!"); class="type">class="kw">string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; class="type">class="kw">string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a class="type">long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; class="type">class="kw">string message=TextByLanguage(ru,en); class="type">int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); class="type">int mb_res=MessageBox(message,caption,flags); class="kw">switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; class="kw">break; class="kw">default: class="kw">break; } } class=class="str">"cmt">//--- Fill in the array of used symbols used_symbols=InpUsedSymbols; CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols); class=class="str">"cmt">//--- Set the type of the used symbol list in the symbol collection engine.SetUsedSymbols(array_used_symbols); class=class="str">"cmt">//--- Displaying the selected mode of working with the symbol object collection Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Number of used symbols: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal()); class=class="str">"cmt">//--- Create resource text files engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки class="num">1","Falling coin class="num">1"),sound_array_coin_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02);
◍ 把资源和交易参数一次性灌进引擎
这段代码做的是 EA 初始化阶段最实在的一步:把音效、图标资源以及交易控制参数批量注册进自定义引擎对象。前面几行 engine.CreateFile 把 6 个 WAV 音效(硬币、按钮点击、收银机)和 2 个 BMP 指示灯图标写进终端文件目录,俄英双语名靠 TextByLanguage 自动切换,MT5 里打开数据文件夹就能看到对应资源。 资源就位后,TradingOnInit 把已建好的交易集合交给交易类,接着用 TradingSetMagic 给所有品种设统一魔术码,TradingSetAsyncMode(false) 强制同步发单——在外汇和贵金属这种高波动品种上,同步模式能避免回调乱序,但会拖慢并发执行速度。 TradingSetTotalTry(InpTotalAttempts) 控制报错后的重试次数,TradingSetCorrectTypeExpiration 与 TradingSetCorrectTypeFilling 则按经纪商规则修正挂单过期和成交填充类型,少这一步在部分平台会直接拒单。SetSoundsStandart 和 SetUseSounds 管声音开关,SetSpreadMultiplier 设点差倍数阈值,最后从 GetListAllUsedSymbols 拿品种列表,注释里演示了怎么用 SetControlBidInc(100000*symbol.Point()) 监控百点跳动——默认 LONG_MAX 代表不监控,随时可改。 被注释的循环给出了具体监控写法:百点涨跌、40 点差变动、40 点差水平线,复制出来取消注释就能让小布类引擎替你盯盘。外汇贵金属杠杆高,参数误设可能瞬间放大亏损,上真仓前务必在策略测试器跑一遍。
engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки class="num">2","Falling coin class="num">2"),sound_array_coin_04); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке class="num">1","Button click class="num">1"),sound_array_click_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке class="num">2","Button click class="num">2"),sound_array_click_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке class="num">3","Button click class="num">3"),sound_array_click_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red); class=class="str">"cmt">//--- Pass all existing collections to the trading class engine.TradingOnInit(); class=class="str">"cmt">//--- Set the class="kw">default magic number for all used symbols engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number)); class=class="str">"cmt">//--- Set synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(class="kw">false); class=class="str">"cmt">//--- Set the number of trading attempts in case of an error engine.TradingSetTotalTry(InpTotalAttempts); class=class="str">"cmt">//--- Set correct order expiration and filling types to all trading objects engine.TradingSetCorrectTypeExpiration(); engine.TradingSetCorrectTypeFilling(); class=class="str">"cmt">//--- Set standard sounds for trading objects of all used symbols engine.SetSoundsStandart(); class=class="str">"cmt">//--- Set the general flag of class="kw">using sounds engine.SetUseSounds(InpUseSounds); class=class="str">"cmt">//--- Set the spread multiplier for symbol trading objects in the symbol collection engine.SetSpreadMultiplier(InpSpreadMultiplier); class=class="str">"cmt">//--- Set controlled values for symbols class=class="str">"cmt">//--- Get the list of all collection symbols CArrayObj *list=engine.GetListAllUsedSymbols(); if(list!=NULL && list.Total()!=class="num">0) { class=class="str">"cmt">//--- In a loop by the list, set the necessary values for tracked symbol properties class=class="str">"cmt">//--- By class="kw">default, the LONG_MAX value is set to all properties, which means "Do not track this class="kw">property" class=class="str">"cmt">//--- It can be enabled or disabled(by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program class=class="str">"cmt">/* for(class="type">int i=class="num">0;i<list.Total();i++) { CSymbol* symbol=list.At(i); if(symbol==NULL) class="kw">continue; class=class="str">"cmt">//--- Set control of the symbol price increase by class="num">100 points symbol.SetControlBidInc(class="num">100000*symbol.Point()); class=class="str">"cmt">//--- Set control of the symbol price decrease by class="num">100 points symbol.SetControlBidDec(class="num">100000*symbol.Point()); class=class="str">"cmt">//--- Set control of the symbol spread increase by class="num">40 points symbol.SetControlSpreadInc(class="num">400); class=class="str">"cmt">//--- Set control of the symbol spread decrease by class="num">40 points symbol.SetControlSpreadDec(class="num">400); class=class="str">"cmt">//--- Set control of the current spread by the value of class="num">40 points symbol.SetControlSpreadLevel(class="num">400); } */ } class=class="str">"cmt">//--- Set controlled values for the current account CAccount* account=engine.GetAccountCurrent(); if(account!=NULL) {
断线时挂单请求如何转入待重发队列
在账户对象上设完盈利与权益的增量控制后,若下单瞬间碰上终端与交易服务器失联,MT5 不会直接丢弃请求,而是把它塞进一个 pending 队列里等重连。下面这段调用把利润增量监控设为 10、权益增量设为 15,利润控制水位线拉到 20,都属于账户级风控参数,和断线处理本身是两套逻辑。 account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0); account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0); account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0); 真实日志里能看到,2019.12.05 16:38:32.591 报出『No connection with the trade server』,错误码 10031,系统随即创建 pending request #1。该请求记录成交价 1.10913、激活时间 11:37:59、两次尝试间隔 20000 毫秒(20 秒)、总尝试次数 5 次——这意味着外汇或贵金属这类高波动品种在断线窗口里,可能最多等 80 秒才彻底放弃,价格已偏移的挂单倾向失效。 开 MT5 用 EURUSD 模拟拔网线下单,能在 Experts 日志里复现同样的 pending 参数块;把等待间隔从 20 秒调小,可让重发更密,但高频重试在流动性差的时段反而容易成交在劣价。
class=class="str">"cmt">//--- Set control of the profit increase to class="num">10 account.SetControlledValueINC(ACCOUNT_PROFIT,class="num">10.0); class=class="str">"cmt">//--- Set control of the funds increase to class="num">15 account.SetControlledValueINC(ACCOUNT_EQUITY,class="num">15.0); class=class="str">"cmt">//--- Set profit control level to class="num">20 account.SetControlledValueLEVEL(ACCOUNT_PROFIT,class="num">20.0);
「挂单被拒时的日志长什么样」
下面这段是 MT5 某次下达 Sell Limit 挂单时留下的真实日志片段,时间锚在 2019.12.05 16:38:32.610。请求本身写得挺全:0.10 手、入场价 1.10963、SL 1.11113、TP 1.10813,订单类型选了 Pending order Sell Limit,执行方式 FOK,过期类型 GTC,Magic 号 24379515,备注写的是 "Pending order SellLimit"。 但 13 秒后系统开始重试,16:38:45.185 打出 Retry trading attempt #1,紧接着 CTrading::PlaceOrder<double,double,double,double> 报 Invalid request,原因只有一句:Trading is prohibited for the current account。参数修正逻辑跑完,直接 Trading operation aborted。 #2 在 16:38:45.512 复现同样路径,#3 在 16:38:45.852 继续重试。也就是说,账户层禁止交易时,EA 不会静默退出,而是按重试计数不断打日志,直到你手动干预或改账户权限。外汇和贵金属杠杆高,这类账户限制常见于穿仓保护或券商维护,实盘前务必在策略测试器外也确认账户交易开关是开的。 开 MT5 按 F8 看专家日志,搜 'Trading is prohibited' 能快速定位是不是账户权限卡住了单子,而不是代码算错了价格。
◍ 从挂单日志反推 Sell Limit 的触发结构
一段真实的 EURUSD 挂单日志能拆出不少可验证的细节。2019.12.05 16:38:46.405 这条记录显示,0.10 手 Sell Limit 挂单价 1.10963,止损 1.11113,止盈 1.10813,Magic 24379515,附加参数 G1=4、G2=7、ID=1。 从数值看,止损距入场 15 点,止盈距入场 15 点,是典型的对称盈亏比挂单;但 Sell Limit 本意是在价格反弹至阻力上方才成交,若当时价低于 1.10963,则属于逆势接刀,外汇与贵金属此类操作回撤风险偏高,触发概率取决于后续是否真摸到挂单价。 日志里 OnDoEasyEvent 回调在 67 毫秒后抛出 Pending order placed,说明 EA 的事件管道和订单下发几乎是同步的。开 MT5 把这段日志贴进专家日志比对,能直接确认你自己 EA 的 Magic 与 G1/G2 映射是否和这套一致。
class="num">2019.12.class="num">05 class="num">16:class="num">38:class="num">46.405 - Pending order placed: class="num">2019.12.class="num">05 class="num">11:class="num">38:class="num">45.933 - class="num">2019.12.class="num">05 class="num">16:class="num">38:class="num">46.405 EURUSD Placed class="num">0.10 Pending order Sell Limit #class="num">491179168 at price class="num">1.10963, sl class="num">1.11113, tp class="num">1.10813, Magic number class="num">24379515 (class="num">123), G1: class="num">4, G2: class="num">7, ID: class="num">1 class="num">2019.12.class="num">05 class="num">16:class="num">38:class="num">46.472 OnDoEasyEvent: Pending order placed
延后请求接下来的改造方向
这一节里作者把延后交易请求的基础框架收了个尾,附带放出当前版本的含糊库全部文件与测试 EA:MQL5 压缩包 3617 KB、MQL4 压缩包 3617.01 KB,直接下载就能在 MT5 里跑通现有逻辑。下一篇计划接着补上修改、删除挂单以及平仓这三类延后请求方法,等于把请求生命周期从「提交」补到「撤销/关闭」。 文末那段比较代码是理解延后请求排序的关键,它决定了同批请求在队列里的先后。mode=0 时按 ID 比,否则按 Type 比,三种结果 1 / -1 / 0 对应大于、小于、相等。评论区有读者误以为类型或 ID 不会相等,作者明确回了一句:比较必然覆盖相等分支,否则队列合并会出漏。 你若想验证,把下面代码贴进 MT5 头文件,建两个 CPendingReq 对象赋不同 ID 和 Type,调 Compare 打印返回值,就能看到 0 确实会出现。外汇和贵金属杠杆高,延后请求若没排好序,平仓顺序错乱可能放大滑点风险,先在策略测试器用演示账户跑。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// 根据属性比较 CPendingReq 对象 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CPendingReq::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CPendingReq *compared_req=node; class="kw">return ( class=class="str">"cmt">//--- 按 ID 进行比较 mode==class="num">0 ? (this.ID()> compared_req.ID() ? class="num">1 : this.ID()<compared_req.ID() ? -class="num">1 : class="num">0) : class=class="str">"cmt">//--- 按类型进行比较 (this.Type()>compared_req.Type() ? class="num">1 : this.Type()<compared_req.Type() ? -class="num">1 : class="num">0) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ this.Type()>=compared_req.Type() this.Type()>compared_req.Type() ? class="num">1 : this.Type()<compared_req.Type() ? -class="num">1 : class="num">0