Summary: 从组合爆炸问题出发,深入讲解MT4遗传算法的选择、交叉、变异机制。提供分层优化策略、未来函数检测方法、iCustom指标调用及OrderSend下单代码,帮助你构建稳健不惧过拟合的EA。




# EA参数优化:从组合爆炸到遗传算法的完全指南

一、组合爆炸:为什么穷举法行不通?



当我们对EA进行参数优化时,组合数量会指数级增长。以典型场景为例:20个开关参数和20个数值参数(各有10种取值)。总组合数计算如下 :

  • 20个开关:2²⁰ = 1,048,576种

  • 20个数值:10²⁰ = 100,000,000,000,000,000,000种

  • 总组合数 = 20²⁰ ≈ 1.048 × 10²⁶


  • 这个数字有多大?
  • 远超地球沙粒总数(约7.5×10¹⁸粒)

  • 远超银河系恒星数量(约10¹¹颗)

  • 每秒测试1种组合,所需时间远超宇宙年龄


  • 这就是为什么MT4/MT5内置了遗传算法(Genetic Algorithm)优化,而非穷举搜索。

    二、遗传算法的核心原理



    遗传算法模拟生物进化过程,在巨大的参数空间中高效搜索最优解 。

    五个核心步骤:



    步骤1:种群初始化
    随机生成数百到数千组参数,每组称为一个“个体”。

    步骤2:适应度评估
    用每组参数运行回测,计算适应度分数。MT4支持以下优化目标 :
  • 余额:最终余额最高

  • 盈利因子:毛利润/毛亏损

  • 预期回报:平均每笔交易盈利

  • 最大回撤:最低值(越小越好)

  • 自定义:EA中OnTester()函数的返回值


  • 步骤3:选择(适者生存)
    适应度高的个体有更大概率被选中繁殖,低的被淘汰。

    步骤4:交叉(繁殖)
    选中的个体交换“基因”(参数值)产生子代。例如,父代A的止损参数与父代B的止盈参数组合。

    步骤5:变异(随机扰动)
    少量个体的某些参数随机改变,引入多样性,防止陷入局部最优。

    上述过程重复数十到数百代。种群平均适应度持续提升,最终收敛到高性能参数组合 。

    三、实战优化策略:分层法



    策略1:固定策略性参数



    优化前,先区分什么是策略逻辑、什么是可调数值

    以网格EA为例:
  • 固定:到底用点数间距还是网格间距(策略选择)

  • 优化:固定策略后,优化具体的间距数值


  • 这能大幅缩减搜索空间 。

    策略2:设置合理的优化范围



    在MT4测试器中配置边界 :

    | 参数 | 起始值 | 步长 | 停止值 |
    |------|--------|------|--------|
    | 止盈 | 20 | 10 | 100 |
    | 止损 | 15 | 5 | 80 |
    | 手数 | 0.01 | 0.01 | 0.10 |

    单次优化参数不超过5-7个。

    策略3:样本外验证(防过拟合核心)



    遗传算法找到的参数可能在历史数据上表现完美,但实盘却很差——这是过拟合。解决方案:

    1. 将数据分为70%样本内(用于优化)和30%样本外(用于验证)
    2. 仅在样本内数据上运行GA优化
    3. 将“冠军参数”在样本外数据上测试
    4. 如果性能显著下降(>30%),说明EA过拟合了

    四、未来函数检测:指标EA化的前提



    在将任何指标转换为EA参与优化前,必须确认该指标不含未来函数(即根据当前K线修改历史信号的重绘行为)。

    检测方法一:源码审查(白盒)


    检查循环中是否有修改历史数组的操作。任何对非0偏移量的赋值都是重绘信号。

    检测方法二:肉眼观察(黑盒)


    将指标加载到1分钟图上。随着新K线形成,观察历史信号是否会消失或移动。如果过去的买卖箭头会变化,则该指标含未来函数。

    > 含未来函数的指标回测表现极佳,但实盘必然亏损。永远不要基于重绘指标优化EA。

    五、通过iCustom()加载自定义指标



    确认指标无未来函数后,使用`iCustom()`将其集成到EA中 :

    ```cpp
    // 函数原型
    double iCustom(
    string symbol, // 品种(NULL=当前)
    int timeframe, // 周期(0=当前图表)
    string name, // 指标文件名(不含.ex4)
    ... , // 指标参数(如有)
    int mode, // 线索引(0起,对应SetIndexBuffer顺序)
    int shift // 偏移(0=当前K线,1=前一根)
    );
    ```

    实际示例:RSI背离指标



    ```cpp
    // 假设自定义背离指标:
    // 线0 = 看涨背离价格位
    // 线1 = 看跌背离价格位
    // EMPTY_VALUE 表示无信号

    double bullishDiv = iCustom(NULL, 0, "Divergence_Indicator", 0, 1);
    double bearishDiv = iCustom(NULL, 0, "Divergence_Indicator", 1, 1);

    if (bullishDiv != EMPTY_VALUE && OrdersTotal() == 0) {
    // 前一根K线出现看涨背离信号
    OrderSend(Symbol(), OP_BUY, lotSize, Ask, 3, 0, 0, "Divergence Buy", magic);
    }
    ```

    关键决策:当前K线 vs 前一根K线



    使用`shift=0`(当前K线)时,信号会随价格波动出现和消失,导致回测中出现幻影信号。为获得稳健的优化结果,建议使用`shift=1`(已完成K线)。

    六、OrderSend()下单函数详解



    `OrderSend()`是EA执行交易的核心函数 :

    ```cpp
    int OrderSend(
    string symbol, // 品种(用Symbol()取当前)
    int cmd, // OP_BUY/OP_SELL或挂单类型
    double volume, // 手数
    double price, // 开仓价(买单用Ask,卖单用Bid)
    int slippage, // 允许滑点(通常2-3点)
    double stoploss, // 止损价(0=无)
    double takeprofit, // 止盈价(0=无)
    string comment, // 订单注释
    int magic, // EA魔术号(唯一标识)
    datetime expiration, // 挂单过期时间
    color arrow_color // 图表箭头颜色
    );
    ```

    市价单下单示例



    ```cpp
    int start() {
    double lotSize = 0.1;
    int slippage = 3;
    int magicNumber = 20240601;

    // 买入订单
    int ticket = OrderSend(
    Symbol(), // 当前品种
    OP_BUY, // 买入
    lotSize, // 0.1手
    Ask, // 当前卖价
    slippage, // 允许3点滑点
    Bid - 50 * Point, // 止损:Bid下方50点
    Bid + 100 * Point, // 止盈:Bid上方100点
    "GA_Optimized_EA", // 注释
    magicNumber, // 唯一ID
    0, // 无过期
    clrGreen // 绿色箭头
    );

    if (ticket < 0) {
    Print("订单失败,错误码:", GetLastError());
    }
    return 0;
    }
    ```

    七、OnTester()自定义优化目标



    如需自定义适应度函数,在EA中实现`OnTester()` :

    ```cpp
    double OnTester() {
    // 获取回测统计值
    double profitFactor = TesterStatistics(STAT_PROFIT_FACTOR);
    double maxDrawdown = TesterStatistics(STAT_EQUITY_DD_PERCENT);
    double sharpeRatio = TesterStatistics(STAT_SHARPE_RATIO);

    // 自定义评分:夏普比率优先,同时惩罚高回撤
    double fitness = sharpeRatio * 100;
    if (maxDrawdown > 20.0) fitness *= 0.5; // 回撤超20%扣50%分
    if (profitFactor < 1.3) fitness *= 0.8; // 盈利因子低于1.3扣20%分

    return fitness;
    }
    ```

    八、必须避免的优化陷阱



    | 陷阱 | 后果 | 预防措施 |
    |------|------|----------|
    | 过拟合 | 回测完美,实盘亏损 | 样本外验证 |
    | 曲线拟合 | 换市场周期就崩 | 多市场环境测试 |
    | 忽略交易成本 | 回测盈利,实盘亏损 | 回测中包含点差+手续费 |
    | 优化参数过多 | 找到虚假相关性 | 单次≤7个参数 |
    | 幸存者偏差 | 只用现存品种回测 | 纳入已退市品种 |

    九、推荐的工作流程



    1. 编写模块化EA代码,所有可调参数用input修饰
    2. 确认无未来函数(如使用自定义指标)
    3. 设置合理的参数范围(不让GA搜索极端值)
    4. 运行GA优化:种群500-1000,代数50-100
    5. 从结果中选出前10组参数
    6. 样本外前向测试(用预留的30%数据)
    7. 选择一致性最佳的参数组,而非回测利润最高的
    8. 蒙特卡洛模拟,检验随机交易顺序下的稳健性

    ---

    参考来源:
    1. Eabang. “MT4MT5优化参数的遗传算法.” 2025年8月.
    2. MetaTrader 4帮助文档. “设置 — 专家优化.”
    3. FxGecko. “MT4编程:如何把指标应用到外汇EA上?” 2023年3月.
    4. MQL4教程. “交易操作编程 — 开仓与挂单.”
    5. MQL4教程. “常用函数 — Comment(), MessageBox(), PlaySound().”
    6. MQL4教程. “程序执行 — EA生命周期.”
    ```