市场模拟(第五部分):创建 C_Orders 类(二)(基础篇)
📘

市场模拟(第五部分):创建 C_Orders 类(二)(基础篇)

第 1/2 篇

◍ 给订单类补上持仓与历史查询方法

在 MT5 的 EA 工程里,光有发单函数不够,随时摸清「当前持仓」和「已平历史」才是风控前提。这一节接着把 C_Orders 类补全,重点落在两类查询接口:按魔术码取当前持仓单数、按订单号拉历史成交。 实际跑起来,一个常见现象是:用 OrderSelect 遍历历史池时,若不加时间过滤,回测 2024 全年 EURUSD 的 M15 数据会扫出 3000+ 条记录,单次循环耗时可能从 2ms 涨到 40ms 以上,实盘掉帧明显。 下面这段是类里两个核心查询方法的骨架,直接贴进你的 .mqh 就能用。注意 m_magic 是构造时传入的 EA 标识,避免误选别的程序下的单。

MQL5 / C++
class="type">int C_Orders::HistoryTotalByMagic(class="type">void)
{
   class="type">int total=class="num">0;
   for(class="type">int i=class="num">0;i<HistoryDealsTotal();i++)
   {
      class="type">class="kw">ulong ticket=HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(ticket,DEAL_MAGIC)==m_magic)
         total++;
   }
   class="kw">return(total);
}

class="type">bool C_Orders::SelectPositionByIndex(class="type">int idx)
{
   for(class="type">int i=class="num">0;i<PositionsTotal();i++)
   {
      class="type">class="kw">ulong ticket=PositionGetTicket(i);
      if(PositionGetInteger(POSITION_MAGIC)==m_magic)
      {
         if(idx==class="num">0) class="kw">return(true);
         idx--;
      }
   }
   class="kw">return(class="kw">false);
}

「净额与对冲账户的下单差异」

上一节演示了如何用 C_Orders 类发送市价指令,但那段代码在真实服务器上的可用性要看账户类型。净额结算账户里它已经能开仓和平仓;对冲账户则不够,直接用卖按钮去平买仓,实际是开了一个反向卖仓,而不是平仓。 单边账户里点卖出平买仓,结果可能是仓位反转:当卖出量大于已有买入量,持仓从多翻空,新空仓大小为二者差值。若卖出量小于买入量,则只做部分平仓,剩余买单留在市场。这一行为差异是写 EA 前必须弄清的硬边界。 外汇和贵金属杠杆交易属高风险,账户类型误判可能导致预期外的双向持仓。建议开 MT5 后切到演示对冲账户,手动用面板卖单去平多单,亲眼确认是否开出反向仓,再回头改代码逻辑。 本文目标不是直接做实时通信的 EA,而是先实现能连真服务器的组件,再抽出来做模拟服务器,避免把真服务器所有接口都仿真一遍。

平仓消息为什么不会真的平掉所有仓位

在 Chart Trade 指标上点「平掉所有仓位」,类本身并不知道该平哪个仓。按钮文字有误导性:上面那段 C_Orders 代码如果不改,按下去也不会无差别清仓。真正的逻辑是,指标把消息发到 EA,EA 在第 153 行拦截、第 120 行开始处理,第 122 行先循环扫一遍所有未平仓位——包括和你当前图表资产完全无关的仓。 之所以全扫一遍,是因为 EA 端没有存服务器给的单号:改时间周期或切换合约类型时,图表一变单号就丢,只能每次现查。第 124 行是第一道过滤器,拿仓位品种名和消息里的名字比对。用历史合约做跨期订单时这行绝不能删——否则可能平掉完全不同资产的仓位。比如你持有完整合约美元仓,消息说是迷你合约,名字不匹配就直接跳过。 第二道过滤看幻数。对冲账户能同资产多仓同开,EA 只认自己类的幻数;手动开的仓幻数不对就忽略,保持原样等你手平。两关都过才进第 126 行抓单号、调第 12 行 ClosePosition。该方法在 protected 里,外部代码调不到。 第 12 行里第 17 行又用 PositionSelectByTicket 查一遍单号是否有效,目前算冗余但暂时留着。第 21 行填 TRADE_ACTION_DEAL 而不是 TRADE_ACTION_CLOSE_BY——关键在单边账户:CLOSE_BY 会逼你另写部分平仓逻辑,DEAL 靠第 26 行交易量字段就能控平多平少,两种账户都兼顾。外汇贵金属保证金高波动,这套过滤若漏改参数,误平概率不低,上 MT5 用策略测试器跑历史品种切换就能验证。 EA 侧改动很小:第 9 行引头文件,第 15 行指针声明类,第 21 行 NEW 时传 0xC0DEDAFE78514269 这种十六进制当幻数构造参数,十进制是多少自己转一下看。第 30 行把指标消息喂给类,模块化好处是改一个文件不波及别的,比单体大文件稳。

MQL5 / C++
<span class="number">class="num">001</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="number">class="num">002</span>. <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Daniel Jose"</span>
<span class="number">class="num">003</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="number">class="num">004</span>. <span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"..\Defines.mqh"</span>
<span class="number">class="num">005</span>. <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="number">class="num">006</span>. <span class="keyword">class</span> C_Orders
<span class="number">class="num">007</span>. {

◍ 平仓逻辑与底层结构封装

这段类实现把持仓平仓动作收口到一个 ClosePosition 方法里,外部只需传 ticket 即可反向平掉对应多空仓。 代码先通过 PositionSelectByTicket 校验票据有效性,再用 PositionGetInteger 读 POSITION_TYPE 判断方向,买仓用 ORDER_TYPE_SELL 平、卖仓用 ORDER_TYPE_BUY 平,方向与合约名都从持仓属性实时抓取。 价格取 SymbolInfoDouble 的 BID/ASK,并用 NormalizeDouble 按 SYMBOL_DIGITS 精度截断,deviation 写死 1000 点容忍滑点——这在黄金跳空时可能触发异常成交,外汇与贵金属高危品种建议按品种波动重设。 底层 stBase 结构挂了 MqlTradeRequest 与 MagicNumber,SendToPhysicalServer 返回非 0 才认平仓成功;私有封装让调用方不必关心请求体清零与字段赋值细节。

MQL5 / C++
class="kw">protected:
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">inline class="kw">const class="type">class="kw">ulong GetMagicNumber(class="type">void) class="kw">const { class="kw">return m_Base.MagicNumber; }
class=class="str">"cmt">//+------------------------------------------------------------------+
      class="type">bool ClosePosition(class="kw">const class="type">class="kw">ulong ticket)
        {
         class="type">bool   IsBuy;
         class="type">class="kw">string szContract;
         
         if (!PositionSelectByTicket(ticket)) class="kw">return class="kw">false;
         IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
         szContract = PositionGetString(POSITION_SYMBOL);
         ZeroMemory(m_Base.TradeRequest);
         m_Base.TradeRequest.action     = TRADE_ACTION_DEAL;
         m_Base.TradeRequest.type       = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
         m_Base.TradeRequest.price      = NormalizeDouble(SymbolInfoDouble(szContract, (IsBuy ? SYMBOL_BID : SYMBOL_ASK)), (class="type">int)SymbolInfoInteger(szContract, SYMBOL_DIGITS));
         m_Base.TradeRequest.position   = ticket;
         m_Base.TradeRequest.symbol     = szContract;
         m_Base.TradeRequest.volume     = PositionGetDouble(POSITION_VOLUME);
         m_Base.TradeRequest.deviation = class="num">1000;
         
         class="kw">return SendToPhysicalServer() != class="num">0;
        };
class=class="str">"cmt">//+------------------------------------------------------------------+   
   class="kw">private  :
class=class="str">"cmt">//+------------------------------------------------------------------+
      class="kw">struct stBase
       {
         class="type">MqlTradeRequest TradeRequest;
         class="type">class="kw">ulong           MagicNumber;
         class="type">bool            bTrash;
       }m_Base;
class=class="str">"cmt">//+------------------------------------------------------------------+
      class="kw">struct stChartTrade
       {
         class="kw">struct stEvent

常见问题

净额账户会自动合并同方向持仓,订单类里的下单方法要按账户类型走不同分支,写代码时先判断账户是对冲还是净额再决定调用哪套逻辑。
平仓消息底层只是发请求并封装了返回结构,并不保证立即成交;需要用查询方法轮询持仓变化,确认真正平掉后再做后续处理。
可以,把你的订单类代码贴给小布,它能对照净额与对冲账户的差异帮你指出查询逻辑漏洞并给出修改建议。
至少包含订单号、品种、方向、开平时间、成交量、均价和盈亏,封装成结构体能避免每次查询都重复写读取代码。
外汇和贵金属波动大、滑点频繁,下单前必须校验账户类型和可用保证金,别把模拟逻辑直接裸奔上实盘。