在 MQL5 中构建自优化智能交易系统(第六部分):防止爆仓(基础篇)
📘

在 MQL5 中构建自优化智能交易系统(第六部分):防止爆仓(基础篇)

第 1/3 篇

◍ 盈利单为何总死在止损上

EURUSD 小时图上常出现一种尴尬:方向看对了,单子却先被扫损。图 1 里白虚线框出交易时段,红色高亮区是价格上涨刺穿止损的位置——交易者判跌没错,但价格在下行前先大幅拉升,止损若落在红区,亏损平仓后行情才真正掉头。 被止损出局的根源是价格不按直线走,波动中途的逆向刺探会清洗掉正确头寸。31 十月 2025 发布的示例中指出,这类现象直接拉低交易时段盈利能力,还让风险管理变混乱。 圈子里两种老办法都不管用:一是「放宽止损」,波动大日子就把止损拉远,这等于同一类交易承担不同风险,却没有明确规则约束;二是「进場前等确认」,只是把被扫的时间推后,刺探一来照样出局。 我们的思路反着来:止损规模固定,不随波动随便放宽,用明确规则压低盈利单被误扫的概率。外汇与贵金属杠杆高,逆向刺探频繁,固定止损也需配合仓位上限,风险仍不可小视。

用前日高低点做均值回归入场

这套逻辑本质是均值回归:把前一天的 High、Low 当成关键价位,等当天价格先突破再去赌它回吐。突破不是信号,回归动作才是。 具体落地时,以前一日高点为界:若当天创出更高价并收在前高上方,不追多,而是等价格再站上移动平均线后,空头触发条件才算清晰。也就是说,破高+收均线上,是做空的观察窗口,不是破高即空。 外汇与贵金属波动受消息驱动,假突破概率不低,用这套规则须接受多次止损的高风险现实,任何信号都只是概率倾向。 图 2 展示了该策略在K线上的实际标记方式,开 MT5 把前日高低画成水平线,叠加一条 MA,就能肉眼核对信号出现的位置。

「固定回测窗口怎么设」

要验证策略改动能不能跑赢原版,先得锁死一段可比的行情区间。这里直接定死:2022.01.01 到 2025.01.01,月线图上 EURUSD 这段已经被标出来,后面所有改动都只在这块历史里比。 实盘级回测放在 M30 跑,品种锁 EURUSD(你换自己惯用的也行,但别中途换)。账户设置里把建模模式选成“基于真实 tick 的每个 tick”,这样是用经纪商实际 tick 重演市场,误差最小;代价是拉数据慢,网络差的时候等得久。 这两组参数(时间窗 + M30 + 真实 tick)全文不再动,你开 MT5 跟着复现的话,先照这个把测试器配置好,不然后面数字对不上。

◍ 搭一个突破基线EA先测水温

做外汇或贵金属策略验证,第一步不是优化,而是先跑通一个最朴素的基线。这里用 MQL5 写了一个只做前日高低突破的 EA:日线取昨日高低,M30 周期监突破,辅以 14 周期 EMA 确认。 系统常量把行为锁死——MA_PERIOD=14、MA_TYPE=MODE_EMA、TF_1=PERIOD_D1、TF_2=PERIOD_M30、VOL=0.1、SL_SIZE=1000*_Point。全局变量里 yesterday_high / yesterday_low 跟踪前日极值,ma_handler 管指标句柄,逻辑分离靠两个时间框架常量天然切分:日线任务一天一次,持仓扫描三十分钟一次。 回测 3 年,这版裸策略亏掉超 1000 美元,平均亏损和最大亏损均大于平均盈利和最大盈利。资金曲线单调向下,没有任何随时间的增长倾向。外汇贵金属本身高杠杆高风险,这种基线绝不能直接上实盘,但它是后续加过滤器的对照锚。 下一步思路很直接:开仓规则不动,尝试滤掉那些大概率触止损的单。先有烂基线,才有比较基准。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//|                  Baseline Model.mq5                              |
class=class="str">"cmt">//|                  Gamuchirai Ndawana                             |
class=class="str">"cmt">//|                  [MQL5官方文档]  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Ndawana"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Libraries                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade/Trade.mqh>
CTrade Trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| System constants                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define MA_PERIOD class="num">14            class=class="str">"cmt">//--- Moving Average Period
class="macro">#define MA_TYPE   MODE_EMA      class=class="str">"cmt">//--- Type of moving average we have
class="macro">#define MA_PRICE  PRICE_CLOSE   class=class="str">"cmt">//---- Applied Price of Moving Average
class="macro">#define TF_1      PERIOD_D1     class=class="str">"cmt">//--- Our time frame for technical analysis
class="macro">#define TF_2      PERIOD_M30    class=class="str">"cmt">//--- Our time frame for managing positions
class="macro">#define VOL       class="num">0.1           class=class="str">"cmt">//--- Our trading volume
class="macro">#define SL_SIZE   class="num">1e3 * _Point  class=class="str">"cmt">//--- The size of our stop loss
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Our global variables                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int ma_handler,system_state;
class="type">class="kw">double ma[];
class="type">class="kw">double bid,ask,yesterday_high,yesterday_low;

用静态时间戳切分日线与M30的更新节奏

EA 骨架里最容易被忽略的是更新频率控制。上面这段把日线(TF_1)和 M30(TF_2)的例程拆进两个独立代码块,各自用 static datetime time_stamp 记住上一根 K 线的开盘时间,只有 iTime 返回的值变了才重算,避免每个 tick 都刷指标。 日线块里,一旦检测到新日线,就把前一根(shift=1)的 iHigh / iLow 读进 yesterday_high / yesterday_low,然后删掉旧的 LAST_HIGH / LAST_LOW 水平线再重画 OBJ_HLINE。这意味着图表上始终只留昨日高低点参考,不会叠一堆垃圾线。 M30 块逻辑类似,但只更新 bid / ask 和后续技术指标,不碰日线水平线。实盘里这种分离能明显降低 CPU 占用——尤其同时挂多个品种时,tick 级重复计算会拖慢 MT5 执行。 开 MT5 把这段粘进专家顾问,把 TF_1 设成 PERIOD_D1、TF_2 设成 PERIOD_M30,切到 EURUSD 看昨日高低线是否每天 0 点重绘;外汇和贵金属杠杆高,回测前先确认点差和滑点设定,否则画线对了信号也可能是假的。

MQL5 / C++
class="kw">const class="type">class="kw">string last_high = "LAST_HIGH";
class="kw">const class="type">class="kw">string last_low = "LAST_LOW";
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   setup();
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
   release();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
   update();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Perform our update routines                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void update()
  {
class=class="str">"cmt">//--- Daily procedures
    {
      class="kw">static class="type">class="kw">datetime time_stamp;
      class="type">class="kw">datetime current_time = iTime(Symbol(),TF_1,class="num">0);
      if(time_stamp != current_time)
        {
         yesterday_high = iHigh(Symbol(),TF_1,class="num">1);
         yesterday_low = iLow(Symbol(),TF_1,class="num">1);
         class=class="str">"cmt">//--- Mark yesterday&class="macro">#x27;s levels
         ObjectDelete(class="num">0,last_high);
         ObjectDelete(class="num">0,last_low);
         ObjectCreate(class="num">0,last_high,OBJ_HLINE,class="num">0,class="num">0,yesterday_high);
         ObjectCreate(class="num">0,last_low,OBJ_HLINE,class="num">0,class="num">0,yesterday_low);
        }
    }
class=class="str">"cmt">//--- M30 procedures
    {
      class="kw">static class="type">class="kw">datetime time_stamp;
      class="type">class="kw">datetime current_time = iTime(Symbol(),TF_2,class="num">0);
      if(time_stamp != current_time)
        {
         time_stamp = current_time;
         class=class="str">"cmt">//--- Get updated prices
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         class=class="str">"cmt">//--- Update our technical indicators

「把均线和昨日区间塞进开仓判定」

这段自定义函数把前面攒下的指标句柄和价格边界真正用起来。setup() 只在初始化阶段跑一次,给二级周期挂上一条 MA_PERIOD 参数的均线,句柄存进 ma_handler,后续不再重复申请。 find_setup() 是裸信号过滤器:先比昨低,若二级周期前一根 K 线最高价低于 yesterday_low,且收盘价落在 ma[0] 下方,才用 Trade.Buy 以 ask 入场,止损止盈对称放在 bid ± SL_SIZE。反向逻辑镜像处理——前低高于昨高、收在 ma[0] 上方则 Trade.Sell。 release() 在 EA 卸载时 IndicatorRelease 掉均线句柄,末尾一串 #undef 把 TF_1、TF_2、VOL、SL_SIZE、MA_PERIOD、MA_PRICE、MA_TYPE 全部取消宏定义,避免与其他文件撞名。外汇与贵金属杠杆高,这类对称止损若 SL_SIZE 设太小,毛刺就可能频繁扫损。 开 MT5 把 MA_PERIOD 从默认改到 34 或 55,回看同一段行情,你会看到穿越 yesterday_low 后被 ma[0] 拦掉的单子明显变多,这是调参能直接验证的现象。

MQL5 / C++
CopyBuffer(ma_handler,class="num">0,class="num">0,class="num">1,ma);
class=class="str">"cmt">//--- Check for a setup
if(PositionsTotal()==class="num">0)  find_setup();
   }
  }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom functions                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void setup(class="type">void)
  {
  ma_handler    = iMA(Symbol(),TF_2,MA_PERIOD,class="num">0,MA_TYPE,MA_PRICE);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check if we have any trading setups                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void find_setup(class="type">void)
  {
  if(iHigh(Symbol(),TF_2,class="num">1) < yesterday_low)
    {
      if(iClose(Symbol(),TF_2,class="num">1) < ma[class="num">0])
        {
         Trade.Buy(VOL,Symbol(),ask,(bid - (SL_SIZE)),(bid + (SL_SIZE)));
        }
    }
  if(iLow(Symbol(),TF_2,class="num">1) > yesterday_high)
    {
      if(iClose(Symbol(),TF_2,class="num">1) > ma[class="num">0])
        {
         Trade.Sell(VOL,Symbol(),bid,(ask + (SL_SIZE)),(ask - (SL_SIZE)));
        }
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Free resources we are no longer class="kw">using up                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void release(class="type">void)
  {
  IndicatorRelease(ma_handler);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Undefine the system constants we created                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#undef TF_1
class="macro">#undef TF_2
class="macro">#undef VOL
class="macro">#undef SL_SIZE
class="macro">#undef MA_PERIOD
class="macro">#undef MA_PRICE
class="macro">#undef MA_TYPE

常见问题

常见原因是止损放在前日高低点外侧太近,被正常波动扫掉。可把止损设在昨日区间外一定缓冲距离,或改用收盘确认再平仓。
倾向在价格触及前日低点下方少许挂买、前日高点上方少许挂卖,配合均线方向过滤,避免逆势接飞刀。
小布可基于近期波动和昨日区间自动标注品种当前更偏均值回归还是突破,并提示对应风控参数,打开品种页即可看。
建议用至少3个月同季节数据做基础窗口,再切分静态时间戳分别测日线与M30,观察不同更新节奏下的稳定性。
用静态时间戳在每日固定时刻重算昨日区间,均线用M30收盘更新,避免盘中重算导致信号跳动和重复开仓。