跨平台的EA交易: 资金管理·综合运用
⚙️

跨平台的EA交易: 资金管理·综合运用

(3/3)·固定手数到固定风险只是开始,真正难的是让EA在MQL4与MQL5间无缝换算法

实战向进阶 第 3/3 篇
很多跨平台EA写死一种仓位逻辑,换品种或换经纪商规则就手数错乱。把资金管理硬编码进主循环,后期加一种算法要改半套代码,回测也跟着崩。

双平台跑出来的手数差异在哪

把前一篇的订单管理例子接着改:在 OnInit 里建一个 CMoneys 容器,把固定手数和固定比例两种资金管理方法塞进去,再挂到订单管理器上。固定分数、固定风险、固定每点风险这三类因为要求非零止损,而当前 EA 进场止损还是 0,暂时只能注释掉;万一它们返回了小于 0 的无效止损,订单管理器会回退到 m_lotsize 默认的 0.1 手。 OnTick 里做了一点区分:买入仓走固定手数(0.05),卖出仓走固定比例。切换动作发生在调用 TradeOpen 之前,用 CMoneys 的 Selected 方法改一下当前选中项即可。因为资金管理只是纯算术,MT4 和 MT5 算出的手数应当一致——差异只可能来自订单管理器自身对两个平台的适配。 回测数据能印证这一点。MT4 前 10 笔里,卖出仓全是 1.00 手、买入仓全是 0.05 手;2017.01.02 至 01.06 的 EURUSD 测试,余额从 10000 走到 10401.84,第 9 笔卖出平仓亏了 170.00。MT5 对冲模式同周期前 10 笔订单,Volume 列显示卖出 1.00 / 买入 0.05 的节奏完全一样,开启价也基本重合(如 01.03 03:00 买入 1.04669 对 MT4 的 1.04679,微小滑点级偏差)。 外汇与贵金属杠杆交易风险较高,0.1 手默认仓在 170 点不利波动下可能吞掉账户 1.7% 以上,实盘前务必在策略测试器里重跑这套 OnInit 逻辑。

MQL5 / C++
<span class="keyword">class="type">int</span> <span class="functions">OnInit</span>()
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; order_manager=<span class="keyword">new</span> COrderManager();
&nbsp;&nbsp; money_manager = <span class="keyword">new</span> CMoneys();
&nbsp;&nbsp; CMoney *money_fixed= <span class="keyword">new</span> CMoneyFixedLot(<span class="number">class="num">0.05</span>);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//CMoney *money_ff= new CMoneyFixedFractional(class="num">5);</span>
&nbsp;&nbsp; CMoney *money_ratio= <span class="keyword">new</span> CMoneyFixedRatio(<span class="number">class="num">0</span>,<span class="number">class="num">0.1</span>,<span class="number">class="num">1000</span>);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//CMoney *money_riskperpoint= new CMoneyFixedRiskPerPoint(class="num">0.1);</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//CMoney *money_risk= new CMoneyFixedRisk(class="num">100);</span>
&nbsp;&nbsp;
&nbsp;&nbsp; money_manager.Add(money_fixed);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//money_manager.Add(money_ff);</span>
&nbsp;&nbsp; money_manager.Add(money_ratio);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//money_manager.Add(money_riskperpoint);</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//money_manager.Add(money_risk);</span>
&nbsp;&nbsp; order_manager.AddMoneys(money_manager);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//order_manager.Account(money_manager);</span>
&nbsp;&nbsp; symbol_manager=<span class="keyword">new</span> CSymbolManager();
&nbsp;&nbsp; symbol_info=<span class="keyword">new</span> CSymbolInfo();
&nbsp;&nbsp; <span class="keyword">if</span>(!symbol_info.Name(<span class="functions">Symbol</span>()))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"没有设置交易品种"</span>);
&nbsp;&nbsp; symbol_manager.Add(<span class="functions">GetPointer</span>(symbol_info));
&nbsp;&nbsp; order_manager.Init(symbol_manager,<span class="keyword">new</span> CAccountInfo());
&nbsp;&nbsp; <span class="predefines">class="type">MqlParam</span> params[<span class="number">class="num">1</span>];
&nbsp;&nbsp; params[<span class="number">class="num">0</span>].type=<span class="macro">TYPE_STRING</span>;
<span class="preprocessor">class="macro">#ifdef </span><span class="macro">__MQL5__</span>
&nbsp;&nbsp; params[<span class="number">class="num">0</span>].string_value=<span class="class="type">class="kw">string">"Examples\\Heiken_Ashi"</span>;
<span class="preprocessor">class="macro">#else
&nbsp;&nbsp; </span>params[<span class="number">class="num">0</span>].string_value=<span class="class="type">class="kw">string">"Heiken Ashi"</span>;
<span class="preprocessor">class="macro">#endif
&nbsp;&nbsp; </span>SignalHA *signal_ha=<span class="keyword">new</span> SignalHA(<span class="functions">Symbol</span>(),<span class="number">class="num">0</span>,<span class="number">class="num">1</span>,params,signal_bar);

「把 HA 与均线信号塞进同一引擎」

这段初始化把 Heikin Ashi 信号和一条自定义均线信号挂到同一个 CSignals 容器里,两个信号对象都用 GetPointer 传引用,避免 EA 在 OnTick 里反复 new 对象造成内存碎片。signals.Init 接的是 symbol_manager 指针,第二参传 NULL 表示不绑定外部时间过滤器。 OnTick 里先 RefreshRates 再 signals.Check(),顺序不能反——汇率没刷新的话 CheckOpenLong / CheckOpenShort 读到的仍是上一 tick 的卖买价,可能在 XAUUSD 这种点差跳变快的品种上误触发。 进场分支里 close_last() 在开新单前平掉旧仓,money_manager.Selected(0/1) 切换多空对应的仓位比例模板。外汇与贵金属杠杆高,这种无止损挂单逻辑在重大数据行情中可能因滑点扩大而浮亏加速,仅适合在 MT5 策略测试器里先跑历史验证。

MQL5 / C++
  SignalMA *signal_ma=new SignalMA(Symbol(),(ENUM_TIMEFRAMES) Period(),maperiod,class="num">0,mamethod,maapplied,signal_bar);
  signals=new CSignals();
  signals.Add(GetPointer(signal_ha));
  signals.Add(GetPointer(signal_ma));
  signals.Init(GetPointer(symbol_manager),NULL);
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  if(symbol_info.RefreshRates())
    {
      signals.Check();
      if(signals.CheckOpenLong())
        {
         close_last();
         class=class="str">"cmt">//Print("买入交易进场..");
         money_manager.Selected(class="num">0);
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
        }
      else if(signals.CheckOpenShort())
        {
         close_last();
         class=class="str">"cmt">//Print("卖出交易进场..");
         money_manager.Selected(class="num">1);
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
        }
    }
  }

◍ 别急着下结论

这套跨平台 EA 把五种资金管理方法封进统一的指针容器,运行时能动态切换,省去重写下单逻辑的麻烦。附带的 mm_ha_ma.zip 约 1.04 MB、tester_results.zip 约 0.29 MB,可直接丢进 MT5 回测验证各类方法在 EURUSD 15M 上的权益曲线差异。 手数公式里 base_volume 是底仓,balance_increase 定义资金阶梯,volume_increment 控制每跨一阶加多少量,调这三个数就能改变激进程度。外汇和贵金属杠杆高,杠杆放大盈亏,回测好看不等于实盘稳,先在策略测试器跑三个月再上真金。 读者问过按净值百分比算手数、或按连亏次数加仓的变体,原类留了接口,自己继承改几行就能接上。代码跑通之前,别假定某种方法永远优于另一种。

MQL5 / C++
Volume = base_volume + (balance / balance_increase) * volume_increment
让小布替你跑这套
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到不同资金管理方法下的手数推演,把重复劳动交给小布,你专注决策。

常见问题

基类把它作为虚方法占位,只返回true,真正的交易量计算留给派生类重写,这样容器调用时不需要关心具体是哪个算法。
它需要非零止损才能工作,因为最大亏损是按进场时账户余额的百分比和止损距离推算的,MQL4与MQL5下都同理。
可以,小布的品种页已内置多种仓位算法的推演视图,切换CMoney派生类就能看到对应手数变化,不用自己写回测前端。
它会自动触发UpdateLotSize做计算,再调OnLotSizeUpdated更新后续变量,最后返回实际交易量,调用方只需拿结果。