Summary: 专业的经典单均线交叉策略MT4 EA,包含基于保证金的复利仓位计算和回撤减仓模块。简洁高效的入场/出场逻辑,完整源码可直接编译。




# 经典单均线交叉策略EA - 完整MQL4源码

本文提供一个基于经典单均线交叉策略的完整自动交易EA。该EA是MetaTrader 4自带的示例EA之一,已成为算法交易的基础学习工具。策略使用一条移动平均线同时作为入场信号和出场触发条件。

策略逻辑



EA实现了价格穿越均线的策略,优雅而简洁。与布林带或RSI等复杂指标不同,这种方法依赖于价格相对于平滑平均线的纯价格行为。

入场逻辑详解



  • 做多信号:当上一根K线的开盘价低于均线且收盘价高于均线时(向上穿越)

  • 做空信号:当上一根K线的开盘价高于均线且收盘价低于均线时(向下穿越)


  • 系统通过`Volume[0] > 1`检测仅在新K线形成时检查条件,确保每个信号只被处理一次。

    仓位管理系统



    本EA实现了两个先进的仓位管理机制:

    1. 复利仓位计算:`手数 = 可用保证金 × 风险参数 / 1000` - 根据可用保证金自动调整手数,实现复利增长

    2. 回撤控制模块:当连续亏损发生时,EA使用`DecreaseFactor`参数按比例减小仓位,实现“赢冲输缩”的效果

    完整MQL4代码



    ```mql4
    //+------------------------------------------------------------------+
    //| SingleMA_Trend.mq4 |
    //| 自主编译 |
    //| Based on Classic MA |
    //+------------------------------------------------------------------+
    #property copyright "AI助手"
    #property link ""
    #property version "1.00"
    #property strict

    //--- 魔术码用于订单识别
    #define MAGICMA 20260715

    //--- 输入参数
    input double FixedLots = 0.1; // 固定手数(基准)
    input double MaximumRisk = 0.02; // 最大风险百分比(2%=0.02)
    input double DecreaseFactor = 3.0; // 回撤减仓因子
    input int MAPeriod = 12; // 移动平均线周期
    input int MAShift = 6; // 移动平均线平移
    input int Slippage = 3; // 最大滑点
    input bool UseCloseSignal = true; // 使用均线作为出场信号
    input int MagicNumber = 20260715; // EA魔术号
    input int MaxSpread = 30; // 最大允许点差

    //--- 全局变量
    int pointMultiplier = 10;
    datetime lastSignalTime = 0;

    //+------------------------------------------------------------------+
    //| EA初始化函数 |
    //+------------------------------------------------------------------+
    int OnInit()
    {
    // 检测4位和5位报价平台
    if(Digits == 3 || Digits == 5)
    pointMultiplier = 10;
    else if(Digits == 2 || Digits == 4)
    pointMultiplier = 1;

    if(MAPeriod < 2)
    {
    Print("错误: 均线周期至少为2");
    return(INIT_PARAMETERS_INCORRECT);
    }

    Print("单均线趋势EA初始化成功");
    Print("均线周期: ", MAPeriod, " | 均线平移: ", MAShift);
    Print("风险管理: 最大风险=", MaximumRisk*100, "% | 减仓因子=", DecreaseFactor);

    return(INIT_SUCCEEDED);
    }

    //+------------------------------------------------------------------+
    //| EA反初始化函数 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
    Print("单均线趋势EA已移除. 原因: ", reason);
    }

    //+------------------------------------------------------------------+
    //| 计算优化手数(包含复利和回撤控制) |
    //+------------------------------------------------------------------+
    double CalculateOptimizedLotSize()
    {
    double lot = FixedLots;

    //--- 基于可用保证金的复利仓位计算
    // 公式: 手数 = 可用保证金 × 风险% / 1000
    // 除数1000可产生合理的手数增量
    double marginLot = NormalizeDouble(AccountFreeMargin() * MaximumRisk / 1000.0, 2);

    if(marginLot > lot)
    lot = marginLot;

    //--- 回撤控制模块
    // 连续亏损时减少仓位
    if(DecreaseFactor > 0)
    {
    int totalHistory = OrdersHistoryTotal();
    int consecutiveLosses = 0;

    // 从最近的订单开始统计连续亏损次数
    for(int i = totalHistory - 1; i >= 0; i--)
    {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
    {
    Print("选择历史订单失败");
    break;
    }

    // 跳过非本品种或挂单
    if(OrderSymbol() != Symbol() || OrderType() > OP_SELL)
    continue;

    // 遇到盈利订单停止计数
    if(OrderProfit() > 0)
    break;

    if(OrderProfit() < 0)
    consecutiveLosses++;
    }

    // 根据连续亏损次数减少手数
    if(consecutiveLosses > 1)
    {
    double reduction = lot * consecutiveLosses / DecreaseFactor;
    lot = NormalizeDouble(lot - reduction, 2);
    Print("回撤控制激活: ", consecutiveLosses, " 次连续亏损, 手数减至 ", lot);
    }
    }

    //--- 确保最小手数
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    if(minLot == 0) minLot = 0.01;

    if(lot < minLot)
    lot = minLot;

    //--- 按步长取整
    double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
    if(lotStep > 0)
    lot = MathFloor(lot / lotStep) * lotStep;

    return NormalizeDouble(lot, 2);
    }

    //+------------------------------------------------------------------+
    //| 检查点差条件 |
    //+------------------------------------------------------------------+
    bool IsSpreadOK()
    {
    if(MaxSpread <= 0) return true;

    int currentSpread = (int)((Ask - Bid) / Point / pointMultiplier);
    bool spreadOK = (currentSpread <= MaxSpread);

    if(!spreadOK)
    Print("点差过高: ", currentSpread, " (最大允许: ", MaxSpread, ")");

    return spreadOK;
    }

    //+------------------------------------------------------------------+
    //| 计算移动平均线值 |
    //+------------------------------------------------------------------+
    double GetMA(int shift)
    {
    return iMA(Symbol(), 0, MAPeriod, MAShift, MODE_SMA, PRICE_CLOSE, shift);
    }

    //+------------------------------------------------------------------+
    //| 检查买入信号 - 价格向上穿越均线 |
    //+------------------------------------------------------------------+
    bool IsBuySignal()
    {
    double maValue = GetMA(0);

    // 经典买入条件:上一根K线开盘低于均线且收盘高于均线
    bool condition = (Open[1] < maValue && Close[1] > maValue);

    // 确保在新K线上触发,避免重复信号
    if(condition && Time[0] != lastSignalTime)
    return true;

    return false;
    }

    //+------------------------------------------------------------------+
    //| 检查卖出信号 - 价格向下穿越均线 |
    //+------------------------------------------------------------------+
    bool IsSellSignal()
    {
    double maValue = GetMA(0);

    // 经典卖出条件:上一根K线开盘高于均线且收盘低于均线
    bool condition = (Open[1] > maValue && Close[1] < maValue);

    // 确保在新K线上触发,避免重复信号
    if(condition && Time[0] != lastSignalTime)
    return true;

    return false;
    }

    //+------------------------------------------------------------------+
    //| 检查是否应平掉多单 |
    //+------------------------------------------------------------------+
    bool ShouldCloseBuy()
    {
    if(!UseCloseSignal) return false;

    double maValue = GetMA(0);

    // 价格再次向下穿越均线时平掉多单
    return (Open[1] > maValue && Close[1] < maValue);
    }

    //+------------------------------------------------------------------+
    //| 检查是否应平掉空单 |
    //+------------------------------------------------------------------+
    bool ShouldCloseSell()
    {
    if(!UseCloseSignal) return false;

    double maValue = GetMA(0);

    // 价格再次向上穿越均线时平掉空单
    return (Open[1] < maValue && Close[1] > maValue);
    }

    //+------------------------------------------------------------------+
    //| 开仓函数 |
    //+------------------------------------------------------------------+
    void OpenOrder(int command)
    {
    if(!IsSpreadOK()) return;

    double price = (command == OP_BUY) ? Ask : Bid;
    double lot = CalculateOptimizedLotSize();
    string comment = "Single MA Trend";

    int ticket = OrderSend(Symbol(), command, lot, price, Slippage, 0, 0, comment, MagicNumber, 0, clrNONE);

    if(ticket < 0)
    {
    Print("开仓失败. 错误码: ", GetLastError());
    }
    else
    {
    Print("开仓成功. 订单号: ", ticket);
    Print("方向: ", command == OP_BUY ? "做多" : "做空");
    Print("手数: ", lot);
    Print("入场价: ", price);
    }
    }

    //+------------------------------------------------------------------+
    //| 平掉本EA所有持仓 |
    //+------------------------------------------------------------------+
    void CloseAllPositions()
    {
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    continue;

    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
    {
    double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
    bool closed = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrNONE);

    if(!closed)
    Print("平仓失败 订单", OrderTicket(), ". 错误码: ", GetLastError());
    else
    Print("已平仓 订单: ", OrderTicket());
    }
    }
    }

    //+------------------------------------------------------------------+
    //| 平掉特定类型的持仓 |
    //+------------------------------------------------------------------+
    void ClosePositionsByType(int targetType)
    {
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    continue;

    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == targetType)
    {
    double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
    OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrNONE);
    }
    }
    }

    //+------------------------------------------------------------------+
    //| 统计本EA持仓数量 |
    //+------------------------------------------------------------------+
    int CountPositions()
    {
    int count = 0;
    for(int i = 0; i < OrdersTotal(); i++)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
    count++;
    }
    }
    return count;
    }

    //+------------------------------------------------------------------+
    //| 获取当前持仓类型(-1表示无持仓) |
    //+------------------------------------------------------------------+
    int GetCurrentPositionType()
    {
    for(int i = 0; i < OrdersTotal(); i++)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
    return OrderType();
    }
    }
    return -1;
    }

    //+------------------------------------------------------------------+
    //| EA主函数 - 每个报价变动时执行 |
    //+------------------------------------------------------------------+
    void OnTick()
    {
    //--- 最小K线数量检查
    if(Bars < 100)
    return;

    //--- 仅在新K线开盘时交易(Volume[0]==1表示新K线的第一个报价)
    // 这可以避免在同一K线上产生多个信号
    if(Volume[0] > 1)
    return;

    //--- 更新信号时间戳防止重复
    if(Time[0] != lastSignalTime)
    lastSignalTime = Time[0];

    //--- 检查当前是否有持仓
    int currentPosition = GetCurrentPositionType();
    int positionCount = CountPositions();

    //--- 无持仓:寻找入场信号
    if(positionCount == 0)
    {
    if(IsBuySignal())
    {
    Print("在 ", TimeToString(Time[0]), " 检测到买入信号");
    OpenOrder(OP_BUY);
    }
    else if(IsSellSignal())
    {
    Print("在 ", TimeToString(Time[0]), " 检测到卖出信号");
    OpenOrder(OP_SELL);
    }
    }
    //--- 有持仓:检查出场信号
    else
    {
    if(currentPosition == OP_BUY && ShouldCloseBuy())
    {
    Print("在 ", TimeToString(Time[0]), " 检测到买入平仓信号");
    ClosePositionsByType(OP_BUY);
    }
    else if(currentPosition == OP_SELL && ShouldCloseSell())
    {
    Print("在 ", TimeToString(Time[0]), " 检测到卖出平仓信号");
    ClosePositionsByType(OP_SELL);
    }
    }
    }
    //+------------------------------------------------------------------+
    ```

    参数详解



    | 参数 | 说明 | 推荐值 |
    |------|------|--------|
    | 固定手数 | 基准手数(当风险计算得出更小值时使用此值) | 0.01, 0.05, 0.1 |
    | 最大风险 | 每笔交易占用可用保证金百分比(0.02=2%) | 0.01-0.05(1%-5%) |
    | 减仓因子 | 回撤控制灵敏度,越高则减仓越激进 | 2.0-5.0 |
    | 均线周期 | 移动平均线计算周期 | 12, 20, 50 |
    | 均线平移 | 均线向右平移(可过滤假信号) | 0-6 |
    | 滑点 | 最大滑点点数 | 3-5 |
    | 使用均线出场 | 是否使用均线作为出场信号 | true |
    | 魔术号 | EA唯一标识 | 任意不重复数字 |
    | 最大点差 | 阻止交易的最大点差 | 20-40 |

    核心算法解析



    入场逻辑使用简单而有效的价格-均线穿越方法:

    ```
    如果 (Open[1] < 均线 AND Close[1] > 均线) 则 做多
    如果 (Open[1] > 均线 AND Close[1] < 均线) 则 做空
    ```

    这种方法检查上一根已完成K线的开盘价和收盘价相对于当前均线值的位置,有助于过滤掉K线内部的噪音。

    复利仓位计算公式为:

    ```
    手数 = 可用保证金 × 最大风险 / 1000
    ```

    这产生了一个缩放效应:账户增长时仓位增大,回撤时仓位减小。

    安装步骤



    1. 在MT4中打开MetaEditor(按F4)
    2. 创建新的EA(文件 > 新建 > 智能交易系统)
    3. 将所有默认代码替换为上方完整代码
    4. 按编译按钮(F7)- 确保0个错误
    5. 将EA附加到图表(建议EURUSD,M15或H1)
    6. 在输入参数选项卡中调整参数
    7. 启用自动交易(Alt+T)

    各时间周期推荐设置



    | 时间周期 | 均线周期 | 最大风险 | 使用均线出场 |
    |----------|----------|----------|-------------|
    | M15 | 12-20 | 0.01-0.02 | true |
    | H1 | 20-50 | 0.02-0.03 | true |
    | H4 | 50-100 | 0.02-0.03 | false |

    策略优势与局限



    优势 :
  • 逻辑极其简单,参数极少,易于优化

  • 复利仓位可实现指数级增长潜力

  • 回撤控制模块在亏损期间降低风险

  • 在强趋势市场中表现优异


  • 局限:
  • 在盘整/震荡市场中表现较差(反复穿越风险)

  • 无止损止盈功能(依赖均线反转出场)

  • 一次只能持有一个仓位


  • 编译与修改技巧



    自定义修改:

    1. 添加止损止盈:在OpenOrder()函数中添加SL/TP计算

    2. 改为EMA:在GetMA()函数中将MODE_SMA替换为MODE_EMA

    3. 调整最小手数限制:修改CalculateOptimizedLotSize()中的0.01阈值

    4. 添加时间过滤:在OnTick()开头插入小时检查

    验证步骤:
  • 确保启用`#property strict`编译

  • 在模拟账户中测试后再用于实盘

  • 验证MagicNumber不与其他EA冲突


  • 参考来源



    本文EA源码为自主编译,基于MetaTrader 4自带的经典移动平均线示例EA。复利仓位计算和回撤控制模块是增强功能,提供了专业的风险管理能力。

    *如需更专业的优化版EA策略(含多周期分析、AI市场状态检测、完整回测报告和专业技术支持),请查看我们的付费EA合集。订阅后可每周获取更新和独家交易工具。*