# 网格回血马丁格尔EA - 完整MQL4源码
本文提供一个实用的网格回血策略EA,采用马丁格尔阶梯式加仓逻辑。与普通马丁格尔EA不同,本EA内置多重安全机制:净值保护、最大回撤限制、盈利目标平仓和订单过期管理。EA开立首单后,在固定网格间隔处挂单,逐级加仓。
策略逻辑
EA基于可配置的方向偏好(双向或单向)进行交易。当价格逆向运行时,EA在预设网格距离处以递增手数开立新仓位(马丁格尔)。网格持续直到整体仓位达到盈利目标或触发最大回撤限制。EA还包含基于净值的移动止损功能以保护利润。
完整MQL4代码
```mql4
//+------------------------------------------------------------------+
//| GridRecoveryEA.mq4 |
//| 自主编译 版权所有 |
//| |
//+------------------------------------------------------------------+
#property copyright "AI助手"
#property link ""
#property version "1.00"
#property strict
//--- 网格参数
input double InitialLot = 0.01; // 初始手数
input double LotMultiplier = 1.5; // 马丁格尔倍数(1.0=关闭)
input int GridDistance = 30; // 网格间距(点数)
input int MaxGridLevels = 10; // 最大网格层数
input int TradeDirection = 0; // 0=双向,1=只做多,2=只做空
//--- 风险管理
input double BasketProfitTarget = 10.0; // 整体盈利目标(账户货币)
input double MaxDrawdownPercent = 20.0; // 最大回撤百分比(0=关闭)
input int EquityTrailingStart = 5; // 盈利超过此金额启动净值移动止损($)
input int EquityTrailingStep = 2; // 净值移动止损步长($)
//--- 订单管理
input int StopLoss = 0; // 单笔止损点数(0=关闭)
input int TakeProfit = 0; // 单笔止盈点数(0=关闭)
input int OrderExpiryMinutes = 0; // 挂单过期时间(分钟,0=关闭)
input int Slippage = 3; // 允许滑点
//--- 时间过滤
input bool UseTimeFilter = false; // 启用时间过滤
input int StartHour = 8; // 开始交易时间
input int EndHour = 20; // 结束交易时间
input int MagicNumber = 202412; // EA魔术号
//--- 全局变量
double currentEquityPeak = 0;
datetime lastOrderTime = 0;
bool emergencyStop = false;
//+------------------------------------------------------------------+
//| EA初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
if(InitialLot <= 0 || LotMultiplier < 1.0)
{
Print("手数参数无效");
return(INIT_PARAMETERS_INCORRECT);
}
if(MaxGridLevels < 1) MaxGridLevels = 1;
currentEquityPeak = AccountEquity();
emergencyStop = false;
Print("网格回血EA初始化完成,峰值净值: ", currentEquityPeak);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA反初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("网格回血EA已移除,原因代码: ", reason);
}
//+------------------------------------------------------------------+
//| EA报价处理函数 |
//+------------------------------------------------------------------+
void OnTick()
{
static datetime lastCheck = 0;
// 每5秒检查一次,避免过载
if(TimeCurrent() - lastCheck < 5) return;
lastCheck = TimeCurrent();
// 更新峰值净值用于移动保护
double currentEquity = AccountEquity();
if(currentEquity > currentEquityPeak)
currentEquityPeak = currentEquity;
// 紧急停止检查
if(emergencyStop)
{
Comment("紧急停止已激活");
if(CountTotalPositions() > 0)
CloseAllPositions();
return;
}
// 检查每日净值回撤
if(MaxDrawdownPercent > 0)
{
double drawdownPercent = (currentEquityPeak - currentEquity) / currentEquityPeak * 100;
if(drawdownPercent >= MaxDrawdownPercent)
{
Print("达到最大回撤限制: ", drawdownPercent, "%");
emergencyStop = true;
return;
}
}
// 净值移动止损管理
ManageEquityTrailingStop();
// 检查整体盈利目标
double basketProfit = CalculateBasketProfit();
if(basketProfit >= BasketProfitTarget && BasketProfitTarget > 0)
{
Print("达到整体盈利目标: ", basketProfit);
CloseAllPositions();
return;
}
// 时间过滤检查
if(UseTimeFilter)
{
int currentHour = TimeHour(TimeCurrent());
if(currentHour < StartHour || currentHour >= EndHour)
{
Comment("非交易时段");
return;
}
}
// 管理现有网格 - 必要时开启下一层
ManageGrid();
// 初始开仓 - 无持仓时
if(CountTotalPositions() == 0 && CountPendingOrders() == 0)
{
OpenInitialPosition();
}
// 移除过期挂单
if(OrderExpiryMinutes > 0)
RemoveExpiredOrders();
// 显示信息
DisplayInfo(basketProfit);
}
//+------------------------------------------------------------------+
//| 开立初始仓位 |
//+------------------------------------------------------------------+
void OpenInitialPosition()
{
int cmd = -1;
if(TradeDirection == 0)
{
// 简单方向判断:比较前两根K线收盘价
double close1 = iClose(Symbol(), 0, 1);
double close2 = iClose(Symbol(), 0, 2);
cmd = (close1 > close2) ? OP_BUY : OP_SELL;
}
else if(TradeDirection == 1)
cmd = OP_BUY;
else if(TradeDirection == 2)
cmd = OP_SELL;
if(cmd != -1)
OpenOrder(cmd, InitialLot);
}
//+------------------------------------------------------------------+
//| 网格管理 - 价格到达网格距离时开立下一层 |
//+------------------------------------------------------------------+
void ManageGrid()
{
int totalPositions = CountTotalPositions();
if(totalPositions >= MaxGridLevels) return;
if(totalPositions == 0) return;
// 找到最远的仓位(距离当前价格最远)
double farthestDistance = 0;
int farthestType = -1;
double farthestOpenPrice = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() <= OP_SELL)
{
double distance = 0;
if(OrderType() == OP_BUY)
distance = (OrderOpenPrice() - Bid) / Point / 10;
else
distance = (Ask - OrderOpenPrice()) / Point / 10;
if(distance > farthestDistance)
{
farthestDistance = distance;
farthestType = OrderType();
farthestOpenPrice = OrderOpenPrice();
}
}
}
}
// 检查价格是否已移动足够距离以开启下一层
bool shouldOpen = false;
double nextLot = InitialLot * MathPow(LotMultiplier, totalPositions);
if(farthestType == OP_BUY && farthestDistance >= GridDistance)
{
if(Bid <= farthestOpenPrice - GridDistance * Point * 10)
shouldOpen = true;
}
else if(farthestType == OP_SELL && farthestDistance >= GridDistance)
{
if(Ask >= farthestOpenPrice + GridDistance * Point * 10)
shouldOpen = true;
}
if(shouldOpen && nextLot <= MaxLotsAllowed())
{
OpenOrder(farthestType, NormalizeDouble(nextLot, 2));
Print("开启网格第 ", totalPositions + 1, " 层,手数: ", nextLot);
}
}
//+------------------------------------------------------------------+
//| 开仓函数 |
//+------------------------------------------------------------------+
void OpenOrder(int cmd, double lot)
{
double price = (cmd == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;
if(StopLoss > 0)
{
if(cmd == OP_BUY)
sl = price - StopLoss * Point * 10;
else
sl = price + StopLoss * Point * 10;
}
if(TakeProfit > 0)
{
if(cmd == OP_BUY)
tp = price + TakeProfit * Point * 10;
else
tp = price - TakeProfit * Point * 10;
}
int ticket = OrderSend(Symbol(), cmd, lot, price, Slippage, sl, tp, "GridRecovery", MagicNumber, 0, clrNONE);
if(ticket > 0)
{
lastOrderTime = TimeCurrent();
Print("开仓成功: ", (cmd == OP_BUY ? "多单" : "空单"), " 手数: ", lot, " 订单号: ", ticket);
}
else
{
Print("开仓失败,错误码: ", GetLastError());
}
}
//+------------------------------------------------------------------+
//| 平仓所有持仓和删除挂单 |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), Slippage, clrNONE);
else if(OrderType() > OP_SELL)
OrderDelete(OrderTicket(), clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
//| 统计市价持仓数量 |
//+------------------------------------------------------------------+
int CountTotalPositions()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| 统计挂单数量 |
//+------------------------------------------------------------------+
int CountPendingOrders()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() > OP_SELL)
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| 计算整体仓位盈亏 |
//+------------------------------------------------------------------+
double CalculateBasketProfit()
{
double profit = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
profit += OrderProfit() + OrderSwap() + OrderCommission();
}
}
}
return profit;
}
//+------------------------------------------------------------------+
//| 净值移动止损保护 |
//+------------------------------------------------------------------+
void ManageEquityTrailingStop()
{
if(EquityTrailingStart <= 0) return;
static double lastTrailingEquity = 0;
double currentEquity = AccountEquity();
double peakSinceTrailing = currentEquityPeak;
if(peakSinceTrailing >= currentEquityPeak + EquityTrailingStart)
{
double drawdown = peakSinceTrailing - currentEquity;
if(drawdown >= EquityTrailingStep && lastTrailingEquity == 0)
{
lastTrailingEquity = peakSinceTrailing - EquityTrailingStep;
}
if(lastTrailingEquity > 0 && currentEquity <= lastTrailingEquity)
{
Print("净值移动止损触发");
CloseAllPositions();
emergencyStop = true;
}
}
else
{
lastTrailingEquity = 0;
}
}
//+------------------------------------------------------------------+
//| 移除过期挂单 |
//+------------------------------------------------------------------+
void RemoveExpiredOrders()
{
if(OrderExpiryMinutes <= 0) return;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() > OP_SELL)
{
datetime orderTime = OrderOpenTime();
if(TimeCurrent() - orderTime >= OrderExpiryMinutes * 60)
{
OrderDelete(OrderTicket(), clrNONE);
Print("过期挂单已删除: ", OrderTicket());
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| 根据账户余额计算最大允许手数 |
//+------------------------------------------------------------------+
double MaxLotsAllowed()
{
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double balanceBased = AccountBalance() * 0.1 / 100000; // 每10万美金风险10%
double allowed = MathMin(maxLot, balanceBased);
if(lotStep > 0)
allowed = MathFloor(allowed / lotStep) * lotStep;
return MathMax(allowed, 0.01);
}
//+------------------------------------------------------------------+
//| 在图表上显示信息 |
//+------------------------------------------------------------------+
void DisplayInfo(double basketProfit)
{
string info = "";
info += "=== 网格回血EA ===\n";
info += "持仓数: " + (string)CountTotalPositions() + "/" + (string)MaxGridLevels + "\n";
info += "整体盈亏: " + DoubleToStr(basketProfit, 2) + "\n";
info += "当前净值: " + DoubleToStr(AccountEquity(), 2) + "\n";
info += "峰值净值: " + DoubleToStr(currentEquityPeak, 2) + "\n";
info += "回撤: " + DoubleToStr((currentEquityPeak - AccountEquity()) / currentEquityPeak * 100, 2) + "%";
Comment(info);
}
//+------------------------------------------------------------------+
```
参数详解
| 参数 | 说明 | 推荐值 |
|------|------|--------|
| 初始手数 | 起始开仓手数 | 小账户0.01 |
| 马丁格尔倍数 | 加仓倍数(1.0=关闭) | 1.3-1.8 |
| 网格间距 | 网格层级间距(点数) | 25-50 |
| 最大网格层数 | 最多加仓次数 | 5-10 |
| 交易方向 | 0=双向,1=只多,2=只空 | 0 |
| 整体盈利目标 | 达到此盈利全部平仓 | 5-20 |
| 最大回撤百分比 | 触发紧急停止的回撤值 | 15-30 |
| 净值移动启动 | 盈利超过此金额启动移动保护 | 5-10 |
| 净值移动步长 | 移动保护步长 | 2-5 |
| 单笔止损 | 每单止损点数(0=关闭) | 0 |
| 单笔止盈 | 每单止盈点数(0=关闭) | 0 |
| 挂单过期时间 | 自动删除挂单(分钟) | 60-240 |
| 魔术号 | EA标识码 | 任意唯一数字 |
编译与安装
1. 复制代码到MetaEditor(MT4按F4)
2. 点击编译按钮(F7)- 应显示0错误
3. 将EA附加到任意图表(推荐H1周期)
4. 在输入参数选项卡中设置参数
5. 启用自动交易