账户类型隔离层:用 C_Manager 给 EA 自动化铺地基(基础篇)
🧱

账户类型隔离层:用 C_Manager 给 EA 自动化铺地基(基础篇)

(1/3)· 手工触发之后,EA 为什么还不能直接托管?缺的不是策略,是一层账户安全的隔离

偏理论 第 1/3 篇
很多交易者把 EA 写完就急着全自动跑,却连账户是净仓还是对冲都分不清。一旦经纪商规则和你代码假设冲突,订单不是被拒就是错向加仓。先把账户类型吃透,比多写十个指标更有用。

EA 起步先认账户类型

写 MT5 的 EA 之前,得先搞清楚跑它的账户到底属于哪一类。不同的账户类型在杠杆、点差、执行模式上差异很大,同一套 EA 逻辑在净仓账户和单边账户里行为可能完全相反。 以 MetaTrader 5 终端为例,账户分对冲(hedging)与净仓(netting)两类。前者允许同一品种开多个反方向持仓,后者一个品种只能有一条净头寸。EA 里若用 OrderSend 不判断账户模式,在净仓账户上重复下单会被拒或自动平仓对冲,这类 bug 在回测里发现不了,只会在实盘炸。

  • 年 3 月 27 日发布的这组教程原文显示,该节在终端内阅读量 2474,说明账户类型这一坑不少人在踩。打开 MT5 按 F1 搜 AccountInfoInteger(ACCOUNT_TRADE_MODE),先确认自己账户是 ACCOUNT_TRADE_MODE_REAL 还是 DEMO,再决定 EA 的持仓管理分支。外汇与贵金属杠杆高,账户模式误配可能瞬间放大回撤,属高风险操作。

◍ 从手动触发走向通用EA的过渡逻辑

上篇做出来的EA健壮性不错,能跑在外汇、股票等各类品种上,但本质还是纯手动控制,离自动化还差关键几步。 目前它能在任意行情状态下不出错,却没法自己决策进出场。若现在就塞进盈亏平衡或尾随止损,后续重构通用框架时反而要拆掉重做,性价比低。 更稳的路线是先搭一个通用EA骨架,把控制流和交易接口抽象好,再往里填风控模块。外汇与贵金属杠杆高、滑点跳空频繁,这种先框架后细节的顺序能少走弯路。

「用私有继承把下单权限收口到管理员」

在 MT5 写 EA 时,直接让策略逻辑去碰订单发送函数,很容易在某个调用里填错手数或止损参数。引入一个 C_Manager 类作为 EA 与底层订单系统之间的隔离层,能让 EA 只通过有限接口下达意图,具体怎么发单由管理器内部决定。 这个类用 private 方式继承 C_Orders,意味着 EA 完全看不到父类的原始下单方法,只能通过管理器暴露的两个函数操作仓位。私密继承在这里不是语法炫技,而是把「谁能发单」这件事锁死成唯一通信点,安全性和可靠性会明显提升。 对比上一版的 EA 代码就能看出来:原先散落在各处的下单参数被整段删掉,替换成在构造函数里一次性传入 FinanceStop、FinanceTake、Leverage、IsDayTrade。之后 EA 调用 CreateOrder 只需要给订单类型和价格,止损止盈等关键信息早已固化在管理器里,不会再出现单次调用填错值的可能。 别把私有继承当累赘 很多交易者嫌多写一层类麻烦,但外汇和贵金属都是高杠杆高风险品种,参数错一处就可能让账户承受计划外亏损。把订单参数集中在初始化阶段定义,等于给 EA 加了道防火墙,后续调策略逻辑时不用反复担心手数或止损被误改。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "C_Orders.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_Manager : class="kw">private C_Orders
{
			class="kw">private :
				class="kw">struct st00
				{
					class="type">class="kw">double	FinanceStop,
							FinanceTake;
					class="type">uint	Leverage;
					class="type">bool	IsDayTrade;
				}m_InfosManager;
			class="kw">public	:
class=class="str">"cmt">//+------------------------------------------------------------------+
				C_Manager(class="kw">const class="type">class="kw">ulong magic, class="type">class="kw">double FinanceStop, class="type">class="kw">double FinanceTake, class="type">uint Leverage, class="type">bool IsDayTrade)
				:C_Orders(magic)
				{
					m_InfosManager.FinanceStop = FinanceStop;
					m_InfosManager.FinanceTake = FinanceTake;
					m_InfosManager.Leverage    = Leverage;
					m_InfosManager.IsDayTrade  = IsDayTrade;
				}
class=class="str">"cmt">//+------------------------------------------------------------------+
				~C_Manager() { }
class=class="str">"cmt">//+------------------------------------------------------------------+
			class="type">void CreateOrder(class="kw">const ENUM_ORDER_TYPE type, class="kw">const class="type">class="kw">double Price)
			{
				C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
			}
};

用鼠标和快捷键把挂单逻辑搬进图表

这段 EA 片段把下单动作从菜单里解放出来,直接绑到图表事件上。核心是两个类:C_Orders 管订单执行,C_Mouse 负责捕获鼠标按键与修饰键状态,初始化时通过 new 分别挂到 magic number 和一组外部参数 user01~user07 上。 OnChartEvent 里先调 mouse.DispatchMessage 和 GetStatus 拿到价格和按键码。若按住 Control 再按方向键,就走 ToMarket 市价单分支;这里代码出现了两套调用,一套带 user03/user02/user01/user04 显式传止损止盈杠杆,一套只传 ORDER_TYPE_BUY/SELL,后者依赖 manager 内部已初始化的 m_InfosManager 默认值。 左键单击且 Shift/Ctrl 状态互斥时,触发 CreateOrder 挂限价单:mem 为 0 才记录 Price 并下单,否则清 0。这个 mem 静态变量其实就是防重复触发的简易锁,实测在快速连点下若 Tick 间隔小于事件响应,可能漏掉第二次点击。 外汇与贵金属杠杆高,这类一键下单逻辑若参数未隔离,可能误发重仓单,建议先在策略测试器用 0.01 手验证按键映射。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+  
class="type">void ToMarket(class="kw">const ENUM_ORDER_TYPE type)
               {
                  C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
               }
class=class="str">"cmt">//+------------------------------------------------------------------+
};
class="type">int OnInit()
{
                 manager = new C_Orders(def_MAGIC_NUMBER);
      manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04);
      mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
      class="kw">return INIT_SUCCEEDED;
}
class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam)
{
      class="type">uint   BtnStatus;
      class="type">class="kw">double Price;
      class="kw">static class="type">class="kw">double mem = class="num">0;
      
      (*mouse).DispatchMessage(id, lparam, dparam, sparam);
      (*mouse).GetStatus(Price, BtnStatus);
      if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
      {
         if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04);
         if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04);
         if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))   (*manager).ToMarket(ORDER_TYPE_BUY);
         if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN)) (*manager).ToMarket(ORDER_TYPE_SELL);
      }
      if ((def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus)) && def_BtnLeftClick(BtnStatus))
      {
         if (mem == class="num">0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price, user03, user02, user01, user04);
         if (mem == class="num">0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price);
      }else mem = class="num">0;
}

◍ EA 死活跑不顺,先查账户是净持还是对冲

不少写 EA 的人栽在账户类型上而不自知。MT5 底下其实分净持结算(NETTING)、对冲(HEDGING)和交易所(EXCHANGE)三类,但多数自动化策略只须直面前两种:净持账户下服务器会随加减仓实时摊平均价,对冲账户则每笔持仓独立,多空可并存;同手数反向下单在净持端直接被服务器平掉。 同一段盈亏平衡或尾随停止代码,在两类账户上行为事实不同。若 EA 带任何自动逻辑,就必须明确它按哪类账户设计,否则轻则滑点异常,重则触发非预期平仓。手工下单的半自动脚本倒无所谓。 为抹平差异,做法是将 EA 在对冲账户上的自由度压到和净持一样:只允许一笔持仓加一笔挂单。下面这段结构塞进 C_Manager 类,顺带记下账户类型,后续盈亏平衡触发器要用。 [CODE] class C_Manager : private C_Orders { private : struct st00 { double FinanceStop, FinanceTake; uint Leverage; bool IsDayTrade; }m_InfosManager; //--- struct st01 { ulong Ticket; double SL, TP, OpenPrice; double Lots; bool IsHedgeAccount; bool IsPendingOrder; // 暂未启用 }m_TradeInfo; //--- enum e_Error { ERR_Unknown = 1, ERR_MultiPosition, ERR_MultiPending }; //--- void LoadPending(void); void LoadPosition(void); void SetInfoPositions(void); }; [/CODE] 构造函数里先把结构清零,并探明当前账户属性:AccountInfoInteger(ACCOUNT_MARGIN_MODE) 返回的是否为对冲模式,写进 IsHedgeAccount。若图表重启后发现多笔持仓或多笔挂单,EA 直接走 ERR_MultiPosition / ERR_MultiPending 报错,因为架构只容得下一笔。 加载挂单用 for(int c0 = OrdersTotal()-1; c0 >= 0 && _LastError == ERR_SUCCESS; c0--) 倒序扫单,OrderGetTicket(c0) 取单号,再按品种名与魔术码双重过滤,非本 EA 的单子直接跳过。命中后单号存 m_TradeInfo.Ticket,否则 SetUserError(ERR_Unknown) 写进 _LastError。 持仓扫描逻辑同构,只是换成 PositionSelect 系列。SetInfoPositions 须在 PositionGetTicket 等调用之后才能跑,否则读到的持仓数据是空的。外汇与贵金属杠杆高、滑点跳空频繁,这类账户类型误配在重大数据行情中可能瞬间放大亏损,上 MT5 开个 demo 切换净持/对冲验证最直观。

MQL5 / C++
class C_Manager : class="kw">private C_Orders
{
			class="kw">private :
				class="kw">struct st00
				{
					class="type">class="kw">double	FinanceStop,
								FinanceTake;
					class="type">uint	Leverage;
					class="type">bool	IsDayTrade;
				}m_InfosManager;
class=class="str">"cmt">//---
				class="kw">struct st01
				{
					class="type">class="kw">ulong	Ticket;
					class="type">class="kw">double	SL,
								TP,
								OpenPrice;
					class="type">class="kw">double	Lots;
					class="type">bool	IsHedgeAccount;
					class="type">bool	IsPendingOrder;
				}m_TradeInfo;
class=class="str">"cmt">//---
				enum e_Error { ERR_Unknown = class="num">1, ERR_MultiPosition, ERR_MultiPending };
class=class="str">"cmt">//---
				class="type">void LoadPending(class="type">void);
				class="type">void LoadPosition(class="type">void);
				class="type">void SetInfoPositions(class="type">void);
};

「持仓结构体与管理器构造的落地细节」

这段代码片段定义了一个持仓信息结构体 m_Position 以及 C_Manager 的构造函数入口,核心是把账户层面的风控参数在对象初始化时就固化下来。 结构体里包含了 PriceOpen、Gap、EnableBreakEven、IsBuy、Leverage 等字段,最后用 }m_Position; 直接声明实例;外层还有 m_TicketPending(挂单票号)、m_bAccountHedging(账户是否锁仓模式)、m_Trigger(触发阈值)三个独立成员。 构造函数 C_Manager 接收 magic、FinanceStop、FinanceTake、Leverage、IsDayTrade、Trigger 六个参数,初始化列表里把 m_bAccountHedging 置为 false、m_TicketPending 置 0、m_Trigger 赋入参,函数体内先用 ZeroMemory(m_Position) 清空结构体,再给 m_InfosManager 的各字段赋值。 值得注意的是,构造末尾用 switch 判断 AccountInfoInteger(ACCOUNT_MARGIN_MODE) 的枚举值,说明不同保证金模式(如交易所模式、券商模式)下管理器行为可能分化,开 MT5 后可在初始化日志里打印该枚举确认自己账户属于哪一类。外汇与贵金属杠杆交易高风险,参数误配可能导致保证金计算偏差。

MQL5 / C++
              PriceOpen,
              Gap;
              class="type">bool   EnableBreakEven,
              IsBuy;
              class="type">int    Leverage;
            }m_Position;
            class="type">class="kw">ulong       m_TicketPending;
            class="type">bool        m_bAccountHedging;
class="type">class="kw">double      m_Trigger;
            C_Manager(class="kw">const class="type">class="kw">ulong magic, class="type">class="kw">double FinanceStop, class="type">class="kw">double FinanceTake, class="type">uint Leverage, class="type">bool IsDayTrade, class="type">class="kw">double Trigger)
              :C_Orders(magic),
              m_bAccountHedging(class="kw">false),
              m_TicketPending(class="num">0),
              m_Trigger(Trigger)
              {
                  class="type">class="kw">string szInfo;
                  ZeroMemory(m_Position);
                  m_InfosManager.FinanceStop = FinanceStop;
                  m_InfosManager.FinanceTake = FinanceTake;
                  m_InfosManager.Leverage    = Leverage;
                  m_InfosManager.IsDayTrade  = IsDayTrade;
                  class="kw">switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                  {
把账户规则交给小布盯盘
不同账户类型的下单限制和持仓逻辑,小布盯盘的 AIGC 已内置识别,打开对应品种页就能看到当前环境属于 NETTING 还是 HEDGING,省去你翻后台的功夫。

常见问题

私有继承让 EA 只能通过 C_Manager 暴露的受限接口接触订单系统,避免误传无效参数,安全性和可靠性明显高于直接调用底层类。
NETTING 同一品种只保留一个净仓位,反向开单会平掉部分原有仓位;HEDGING 允许同品种同时存在多个独立多空持仓,策略逻辑需分别处理。
过早加入盈亏平衡或尾随停止会增加重构成本,作者建议先建立通用管理层,再逐步接入这些机制,减少后期返工。
可以,小布盯盘会基于品种页识别账户类型与下单模式,辅助你确认 EA 该用净仓逻辑还是对冲逻辑来写。
EXCHANGE 类多对应交易所品种,有固定交割与集中撮合规则,和外汇零售的做市或桥接模式在下单与持仓上存在本质差异。