# 经典单均线交叉策略EA - 完整MQL4源码
本文提供一个基于经典单均线交叉策略的完整自动交易EA。该EA是MetaTrader 4自带的示例EA之一,已成为算法交易的基础学习工具。策略使用一条移动平均线同时作为入场信号和出场触发条件。
策略逻辑
EA实现了价格穿越均线的策略,优雅而简洁。与布林带或RSI等复杂指标不同,这种方法依赖于价格相对于平滑平均线的纯价格行为。
入场逻辑详解
系统通过`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()开头插入小时检查
验证步骤:
参考来源
本文EA源码为自主编译,基于MetaTrader 4自带的经典移动平均线示例EA。复利仓位计算和回撤控制模块是增强功能,提供了专业的风险管理能力。
*如需更专业的优化版EA策略(含多周期分析、AI市场状态检测、完整回测报告和专业技术支持),请查看我们的付费EA合集。订阅后可每周获取更新和独家交易工具。*