构建自动运行的 EA(第 12 部分):自动化(IV)·综合运用
(3/3)· 每天交易量上限里藏着的致命缺陷,正是挡在100%自动化前的最后拦路虎
不少交易者以为把EA切到自动模式就能撒手,却在某个平静的交易日被一条看似无害的成交量限制悄悄拖垮。半自动时从未暴露的边界条件,一旦失去人工兜底,就会变成实时运行里的暗雷。这篇收尾把前两份铺垫过的隐患摊开,看清楚它如何被触发。
◍ 持仓校验与加仓可行性的底层判断
EA 在动手下新单前,必须先确认旧仓位还在不在。用 PositionSelectByTicket 按 ticket 回查,若查不到就把 m_Position 清零,避免拿着失效结构体继续算杠杆。 IsPossible 这个函数管的是「这口加仓到底让不让开」。它先卡时间窗口 CtrlTimeIsPassed,再卡静态杠杆上限:m_StaticLeverage 一旦大于等于账户 MaxLeverage,或对冲账户里已经有 ticket>0 的仓位,直接 return false。 把当前仓位杠杆折算成带符号整数 i0(买单为正、卖单为负),新单杠杆按方向加减得到 i1。若 i1 与 i0 异号且跨过零轴——比如多单 i0=5、i1=-2 满足 (i1<i0 && i1>=0 && i0>0)——说明这笔单在平掉部分原有暴露,函数返回 true。
| 若不是平仓式反向,则检查 m_StaticLeverage + | i1 | 是否突破 MaxLeverage,超了就 Print 拒绝原因并 return false。外汇与贵金属杠杆变异大,这种硬卡上限的逻辑能避免 EA 在极端波动下超仓,实盘前建议在 MT5 策略测试器里把 MaxLeverage 调低 20% 跑一遍边界。 |
|---|
if (m_Position.Ticket > class="num">0) bRet = PositionSelectByTicket(m_Position.Ticket); if (!bRet) ZeroMemory(m_Position); class="kw">return bRet; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">bool IsPossible(class="kw">const ENUM_ORDER_TYPE type, class="kw">const class="type">uint Leverage) { class="type">int i0, i1; if (!CtrlTimeIsPassed()) class="kw">return class="kw">false; if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > class="num">0))) class="kw">return class="kw">false; if ((m_Pending.Ticket > class="num">0) || (Leverage > INT_MAX)) class="kw">return class="kw">false; i0 = (class="type">int)(m_Position.Ticket == class="num">0 ? class="num">0 : m_Position.Leverage) * (m_Position.IsBuy ? class="num">1 : -class="num">1); i1 = i0 + ((class="type">int)(Leverage * (type == ORDER_TYPE_BUY ? class="num">1 : -class="num">1))); if (((i1 < i0) && (i1 >= class="num">0) && (i0 > class="num">0)) || ((i1 > i0) && (i1 <= class="num">0) && (i0 < class="num">0))) class="kw">return true; if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage) { Print("Request denied, as it would violate the maximum volume allowed for the EA."); class="kw">return class="kw">false;
从持仓对象里抠出杠杆与方向
这段 inline 函数负责把当前 MT5 持仓的关键字段刷进自定义结构体 m_Position,并返回杠杆变动量。它先暂存旧杠杆 tmp 与旧方向 tBuy,再用 PositionGetDouble / PositionGetInteger 把实时成交量、买卖类型、TP、SL、开仓价读进来。 杠杆不是券商给的倍数,而是用 POSITION_VOLUME 除以终端最小成交量 VolMinimal 算出的手数等效值;比如 VolMinimal=0.01、持仓 0.5 手,算出来就是 50。 EnableBreakEven 的判定值得盯:订单已了结且挂单票号为 0 时直接 true,否则沿用原值;再叠加一层——买权要求 SL 低于开仓价、卖权要求 SL 高于开仓价才允许保本。 末行返回值是重点:若方向没变,返回新杠杆减旧杠杆的差值;若方向翻转,直接返回新杠杆。把这个 int 拿去比对,就能知道 EA 本次是加仓、减仓还是反手。外汇与贵金属杠杆波动意味着风险敞口突变,实盘前务必在策略测试器跑一遍验证。
class="kw">inline class="type">int SetInfoPositions(class="type">void) { class="type">class="kw">double v1, v2; class="type">uint tmp = m_Position.Leverage; class="type">bool tBuy = m_Position.IsBuy; m_Position.Leverage = (class="type">uint)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal); m_Position.IsBuy = ((class="type">ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY; m_Position.TP = PositionGetDouble(POSITION_TP); v1 = m_Position.SL = PositionGetDouble(POSITION_SL); v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN); if (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > class="num">0) v1 = m_Pending.PriceOpen; m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == class="num">0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2)); m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage); class="kw">return (class="type">int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage); }
「画得少,看得清」
自动化 EA 写到最后,最容易被忽略的不是逻辑复杂度,而是终端里几乎不打印消息——交易者本该靠这些反馈盯住系统状态。这一节没有新策略,只补一个实打实能改的参数坑。 若 EA 报错“交易量违反用法规则”而拒发订单,根因常在 C_Manager 构造末尾那个交易量上限值。原文默认填 10,意味着阻断前最多发 10 次该手数订单;外汇市场波动下常不够用,多数人直接填 100,要扩到百倍就填 1000。 下面两段是同一处初始化的对照,仅末参不同: int OnInit(){ string szInfo; manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10); mouse = new C_Mouse(user05, user06, user07, user03, user02, user01); for(ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) int OnInit(){ string szInfo; manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000); mouse = new C_Mouse(user05, user06, user07, user03, user02, user01); for(ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) 把 10 改成 1000,发送额度从 10 次提到 1000 次,外汇和贵金属的高风险环境下订单被误拦的概率会明显下降。别拿现成代码直连实盘,先在策略测试器跑多轮再动参数。
class="type">int OnInit() { class="type">class="kw">string szInfo; manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, class="kw">false, class="num">10); mouse = new C_Mouse(user05, user06, user07, user03, user02, user01); for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) class="type">int OnInit() { class="type">class="kw">string szInfo; manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, class="kw">false, class="num">1000); mouse = new C_Mouse(user05, user06, user07, user03, user02, user01); for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)