三均线马丁EA(逻辑完全修正版)
移动止损保留马丁,固定止损倍增,马丁止盈重置后追单
#property copyright "XAUOF指标工坊"
#property version "9.00"
#property strict
#include <Trade\Trade.mqh>
//--- 输入参数
input int MA10_Period = 10;
input int MA15_Period = 15;
input int MA20_Period = 20;
input double BaseLots = 0.01;
input double MaxLots = 1.0;
input int StopLoss = 400;
input int MADistanceMin = 400;
input int TrailingTrigger = 500;
input int TrailingStop = 200;
input int CooldownSeconds = 5;
input int MinHoldSeconds = 30;
input int MartinMaxLoss = 4;
input int MagicBase = 20250101;
enum POSITION_STATE
{
STATE_NO_POS = 0,
STATE_BUY_OPEN = 1,
STATE_SELL_OPEN = 2,
STATE_CLOSE_COOLDOWN = 3,
STATE_MARTIN_FOLLOW_PENDING = 4
};
struct TimeframeData
{
ENUM_TIMEFRAMES period;
int magic;
int handleMA10, handleMA15, handleMA20;
double bufMA10[3], bufMA15[3], bufMA20[3];
double currentLots;
double entryPrice;
datetime entryTime;
datetime lastCloseTime;
datetime lastSignalBarTime;
double lastStopLoss;
bool hasModifiedSL;
POSITION_STATE state;
int positionType;
double floatingProfit;
double floatingProfitUSD;
int martinFixedStopLossCount;
double martinAccumulatedLossUSD;
bool usedTrailingStopThisRound;
int followDirection;
};
CTrade trade;
TimeframeData tfData[3];
string panelName = "MartinPanel";
//+------------------------------------------------------------------+
int OnInit()
{
ENUM_TIMEFRAMES periods[3] = {PERIOD_M5, PERIOD_M15, PERIOD_M30};
for(int i = 0; i < 3; i++)
{
tfData[i].period = periods[i];
tfData[i].magic = MagicBase + PeriodSeconds(periods[i]) / 60;
tfData[i].currentLots = BaseLots;
tfData[i].entryPrice = 0;
tfData[i].entryTime = 0;
tfData[i].lastCloseTime = 0;
tfData[i].lastSignalBarTime = 0;
tfData[i].lastStopLoss = 0;
tfData[i].hasModifiedSL = false;
tfData[i].state = STATE_NO_POS;
tfData[i].positionType = -1;
tfData[i].floatingProfit = 0;
tfData[i].floatingProfitUSD = 0;
tfData[i].martinFixedStopLossCount = 0;
tfData[i].martinAccumulatedLossUSD = 0;
tfData[i].usedTrailingStopThisRound = false;
tfData[i].followDirection = -1;
tfData[i].handleMA10 = iMA(_Symbol, periods[i], MA10_Period, 0, MODE_EMA, PRICE_CLOSE);
tfData[i].handleMA15 = iMA(_Symbol, periods[i], MA15_Period, 0, MODE_EMA, PRICE_CLOSE);
tfData[i].handleMA20 = iMA(_Symbol, periods[i], MA20_Period, 0, MODE_EMA, PRICE_CLOSE);
if(tfData[i].handleMA10 == INVALID_HANDLE ||
tfData[i].handleMA15 == INVALID_HANDLE ||
tfData[i].handleMA20 == INVALID_HANDLE)
return INIT_FAILED;
}
CreatePanel();
EventSetTimer(1);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
for(int i = 0; i < 3; i++)
{
IndicatorRelease(tfData[i].handleMA10);
IndicatorRelease(tfData[i].handleMA15);
IndicatorRelease(tfData[i].handleMA20);
}
ObjectDelete(0, panelName);
EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
for(int i = 0; i < 3; i++)
{
ProcessTimeframe(i);
}
}
//+------------------------------------------------------------------+
void OnTimer()
{
UpdatePanel();
}
//+------------------------------------------------------------------+
void ProcessTimeframe(int idx)
{
if(BarsCalculated(tfData[idx].handleMA10) < 3 ||
BarsCalculated(tfData[idx].handleMA15) < 3 ||
BarsCalculated(tfData[idx].handleMA20) < 3)
return;
if(CopyBuffer(tfData[idx].handleMA10, 0, 0, 3, tfData[idx].bufMA10) < 3) return;
if(CopyBuffer(tfData[idx].handleMA15, 0, 0, 3, tfData[idx].bufMA15) < 3) return;
if(CopyBuffer(tfData[idx].handleMA20, 0, 0, 3, tfData[idx].bufMA20) < 3) return;
ArraySetAsSeries(tfData[idx].bufMA10, true);
ArraySetAsSeries(tfData[idx].bufMA15, true);
ArraySetAsSeries(tfData[idx].bufMA20, true);
datetime time[];
double close[];
ArraySetAsSeries(time, true);
ArraySetAsSeries(close, true);
if(CopyTime(_Symbol, tfData[idx].period, 0, 3, time) < 3) return;
if(CopyClose(_Symbol, tfData[idx].period, 0, 3, close) < 3) return;
int actualPosType = GetPositionType(tfData[idx].magic);
// 【漏洞修正】检测持仓被平仓
if(tfData[idx].positionType != -1 && actualPosType == -1)
{
OnPositionClosed(idx);
// 平仓后立即检查是否需要追单
if(tfData[idx].state == STATE_MARTIN_FOLLOW_PENDING)
{
if(tfData[idx].followDirection == 0)
{
OpenFollowBuy(idx);
}
else if(tfData[idx].followDirection == 1)
{
OpenFollowSell(idx);
}
tfData[idx].state = STATE_NO_POS;
tfData[idx].followDirection = -1;
}
return;
}
tfData[idx].positionType = actualPosType;
// 普通冷却
if(tfData[idx].state == STATE_CLOSE_COOLDOWN)
{
if(TimeCurrent() - tfData[idx].lastCloseTime >= CooldownSeconds)
{
tfData[idx].state = STATE_NO_POS;
tfData[idx].lastSignalBarTime = 0;
}
else
{
return;
}
}
// 管理持仓
if(actualPosType == POSITION_TYPE_BUY)
{
ManageBuyPosition(idx, close[1]);
}
else if(actualPosType == POSITION_TYPE_SELL)
{
ManageSellPosition(idx, close[1]);
}
// 无持仓时检查开仓信号
if(actualPosType == -1 && tfData[idx].state == STATE_NO_POS)
{
if(time[1] == tfData[idx].lastSignalBarTime)
return;
if(IsBullishCrossover(idx, close[1]))
{
OpenBuy(idx);
tfData[idx].lastSignalBarTime = time[1];
return;
}
if(IsBearishCrossover(idx, close[1]))
{
OpenSell(idx);
tfData[idx].lastSignalBarTime = time[1];
return;
}
}
}
//+------------------------------------------------------------------+
bool IsBullishCrossover(int idx, double lastClosePrice)
{
if(!(tfData[idx].bufMA10[1] > tfData[idx].bufMA20[1] &&
tfData[idx].bufMA10[2] <= tfData[idx].bufMA20[2]))
return false;
double maDistance = MathAbs(tfData[idx].bufMA20[1] - tfData[idx].bufMA10[1]) / _Point;
if(maDistance < MADistanceMin)
return false;
if(lastClosePrice <= tfData[idx].bufMA10[1])
return false;
return true;
}
//+------------------------------------------------------------------+
bool IsBearishCrossover(int idx, double lastClosePrice)
{
if(!(tfData[idx].bufMA10[1] < tfData[idx].bufMA20[1] &&
tfData[idx].bufMA10[2] >= tfData[idx].bufMA20[2]))
return false;
double maDistance = MathAbs(tfData[idx].bufMA10[1] - tfData[idx].bufMA20[1]) / _Point;
if(maDistance < MADistanceMin)
return false;
if(lastClosePrice >= tfData[idx].bufMA10[1])
return false;
return true;
}
//+------------------------------------------------------------------+
void OpenBuy(int idx)
{
trade.SetExpertMagicNumber(tfData[idx].magic);
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = price - StopLoss * _Point;
double lots = tfData[idx].currentLots;
if(lots > MaxLots) lots = MaxLots;
if(trade.Buy(lots, _Symbol, price, sl, 0))
{
tfData[idx].entryPrice = price;
tfData[idx].entryTime = TimeCurrent();
tfData[idx].lastStopLoss = sl;
tfData[idx].hasModifiedSL = false;
tfData[idx].state = STATE_BUY_OPEN;
tfData[idx].positionType = POSITION_TYPE_BUY;
tfData[idx].usedTrailingStopThisRound = false;
Print(EnumToString(tfData[idx].period),
" 【开多】手数=", DoubleToString(lots, 2),
" 固定止损次数=", tfData[idx].martinFixedStopLossCount,
" 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
}
}
//+------------------------------------------------------------------+
void OpenSell(int idx)
{
trade.SetExpertMagicNumber(tfData[idx].magic);
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = price + StopLoss * _Point;
double lots = tfData[idx].currentLots;
if(lots > MaxLots) lots = MaxLots;
if(trade.Sell(lots, _Symbol, price, sl, 0))
{
tfData[idx].entryPrice = price;
tfData[idx].entryTime = TimeCurrent();
tfData[idx].lastStopLoss = sl;
tfData[idx].hasModifiedSL = false;
tfData[idx].state = STATE_SELL_OPEN;
tfData[idx].positionType = POSITION_TYPE_SELL;
tfData[idx].usedTrailingStopThisRound = false;
Print(EnumToString(tfData[idx].period),
" 【开空】手数=", DoubleToString(lots, 2),
" 固定止损次数=", tfData[idx].martinFixedStopLossCount,
" 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
}
}
//+------------------------------------------------------------------+
void OpenFollowBuy(int idx)
{
trade.SetExpertMagicNumber(tfData[idx].magic);
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = price - StopLoss * _Point;
if(trade.Buy(BaseLots, _Symbol, price, sl, 0))
{
tfData[idx].entryPrice = price;
tfData[idx].entryTime = TimeCurrent();
tfData[idx].lastStopLoss = sl;
tfData[idx].hasModifiedSL = false;
tfData[idx].state = STATE_BUY_OPEN;
tfData[idx].positionType = POSITION_TYPE_BUY;
tfData[idx].usedTrailingStopThisRound = false;
Print(EnumToString(tfData[idx].period),
" 【马丁止盈后追单-多】0.01手 马丁已重置");
}
}
//+------------------------------------------------------------------+
void OpenFollowSell(int idx)
{
trade.SetExpertMagicNumber(tfData[idx].magic);
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = price + StopLoss * _Point;
if(trade.Sell(BaseLots, _Symbol, price, sl, 0))
{
tfData[idx].entryPrice = price;
tfData[idx].entryTime = TimeCurrent();
tfData[idx].lastStopLoss = sl;
tfData[idx].hasModifiedSL = false;
tfData[idx].state = STATE_SELL_OPEN;
tfData[idx].positionType = POSITION_TYPE_SELL;
tfData[idx].usedTrailingStopThisRound = false;
Print(EnumToString(tfData[idx].period),
" 【马丁止盈后追单-空】0.01手 马丁已重置");
}
}
//+------------------------------------------------------------------+
void ManageBuyPosition(int idx, double lastClosePrice)
{
if(!PositionSelect(_Symbol)) return;
if(PositionGetInteger(POSITION_MAGIC) != tfData[idx].magic) return;
trade.SetExpertMagicNumber(tfData[idx].magic);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double profitUSD = PositionGetDouble(POSITION_PROFIT);
double profitPoints = (currentPrice - openPrice) / _Point;
tfData[idx].floatingProfitUSD = profitUSD;
tfData[idx].floatingProfit = profitPoints;
tfData[idx].lastStopLoss = currentSL;
// ===== 【优先级1】马丁止盈 =====
if(tfData[idx].martinFixedStopLossCount >= MartinMaxLoss)
{
if(profitUSD >= tfData[idx].martinAccumulatedLossUSD)
{
if(trade.PositionClose(_Symbol))
{
Print(EnumToString(tfData[idx].period),
" 【马丁止盈】浮盈=$", DoubleToString(profitUSD, 2),
" >= 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2),
" 平仓重置");
// 重置马丁
tfData[idx].martinFixedStopLossCount = 0;
tfData[idx].martinAccumulatedLossUSD = 0;
tfData[idx].currentLots = BaseLots;
tfData[idx].state = STATE_MARTIN_FOLLOW_PENDING;
tfData[idx].followDirection = 0;
tfData[idx].lastCloseTime = TimeCurrent();
}
return;
}
}
// ===== 【优先级2】MA15止盈 =====
int holdSeconds = (int)(TimeCurrent() - tfData[idx].entryTime);
if(holdSeconds >= MinHoldSeconds && lastClosePrice <= tfData[idx].bufMA15[1])
{
// 【漏洞修正】平仓前检查
if(trade.PositionClose(_Symbol))
{
Print(EnumToString(tfData[idx].period),
" 【MA15止盈】平仓");
tfData[idx].martinFixedStopLossCount = 0;
tfData[idx].martinAccumulatedLossUSD = 0;
tfData[idx].currentLots = BaseLots;
tfData[idx].state = STATE_CLOSE_COOLDOWN;
tfData[idx].lastCloseTime = TimeCurrent();
}
return;
}
// ===== 【优先级3】移动止损 =====
if(!tfData[idx].hasModifiedSL && profitPoints >= TrailingTrigger)
{
double newSL = openPrice + TrailingStop * _Point;
if(newSL > currentSL)
{
// 【漏洞修正】先标记,再尝试修改
tfData[idx].hasModifiedSL = true;
tfData[idx].usedTrailingStopThisRound = true;
if(!trade.PositionModify(_Symbol, newSL, 0))
{
Print(EnumToString(tfData[idx].period), " 移动止损修改失败");
}
else
{
Print(EnumToString(tfData[idx].period),
" 【移动止损】浮盈=", (int)profitPoints, "点");
}
}
}
}
//+------------------------------------------------------------------+
void ManageSellPosition(int idx, double lastClosePrice)
{
if(!PositionSelect(_Symbol)) return;
if(PositionGetInteger(POSITION_MAGIC) != tfData[idx].magic) return;
trade.SetExpertMagicNumber(tfData[idx].magic);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double profitUSD = PositionGetDouble(POSITION_PROFIT);
double profitPoints = (openPrice - currentPrice) / _Point;
tfData[idx].floatingProfitUSD = profitUSD;
tfData[idx].floatingProfit = profitPoints;
tfData[idx].lastStopLoss = currentSL;
if(tfData[idx].martinFixedStopLossCount >= MartinMaxLoss)
{
if(profitUSD >= tfData[idx].martinAccumulatedLossUSD)
{
if(trade.PositionClose(_Symbol))
{
Print(EnumToString(tfData[idx].period),
" 【马丁止盈】浮盈=$", DoubleToString(profitUSD, 2),
" >= 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2),
" 平仓重置");
tfData[idx].martinFixedStopLossCount = 0;
tfData[idx].martinAccumulatedLossUSD = 0;
tfData[idx].currentLots = BaseLots;
tfData[idx].state = STATE_MARTIN_FOLLOW_PENDING;
tfData[idx].followDirection = 1;
tfData[idx].lastCloseTime = TimeCurrent();
}
return;
}
}
int holdSeconds = (int)(TimeCurrent() - tfData[idx].entryTime);
if(holdSeconds >= MinHoldSeconds && lastClosePrice >= tfData[idx].bufMA15[1])
{
if(trade.PositionClose(_Symbol))
{
Print(EnumToString(tfData[idx].period),
" 【MA15止盈】平仓");
tfData[idx].martinFixedStopLossCount = 0;
tfData[idx].martinAccumulatedLossUSD = 0;
tfData[idx].currentLots = BaseLots;
tfData[idx].state = STATE_CLOSE_COOLDOWN;
tfData[idx].lastCloseTime = TimeCurrent();
}
return;
}
if(!tfData[idx].hasModifiedSL && profitPoints >= TrailingTrigger)
{
double newSL = openPrice - TrailingStop * _Point;
if(newSL < currentSL || currentSL == 0)
{
tfData[idx].hasModifiedSL = true;
tfData[idx].usedTrailingStopThisRound = true;
if(!trade.PositionModify(_Symbol, newSL, 0))
{
Print(EnumToString(tfData[idx].period), " 移动止损修改失败");
}
else
{
Print(EnumToString(tfData[idx].period),
" 【移动止损】浮盈=", (int)profitPoints, "点");
}
}
}
}
//+------------------------------------------------------------------+
void OnPositionClosed(int idx)
{
Print(EnumToString(tfData[idx].period), " 【平仓事件检测】");
double closeProfitUSD = 0;
if(HistorySelect(tfData[idx].entryTime - 10, TimeCurrent()))
{
int dealsTotal = HistoryDealsTotal();
for(int i = dealsTotal - 1; i >= 0; i--)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket > 0)
{
string dealSymbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
int dealMagic = (int)HistoryDealGetInteger(ticket, DEAL_MAGIC);
int dealEntry = (int)HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(dealSymbol == _Symbol && dealMagic == tfData[idx].magic && dealEntry == DEAL_ENTRY_OUT)
{
closeProfitUSD = HistoryDealGetDouble(ticket, DEAL_PROFIT);
break;
}
}
}
}
// 【核心逻辑】分情况处理
if(tfData[idx].usedTrailingStopThisRound)
{
// ===== 移动止损触发 =====
// 手数继续累加,马丁统计保留
tfData[idx].currentLots = MathMin(tfData[idx].currentLots + BaseLots, MaxLots);
Print(EnumToString(tfData[idx].period),
" 【移动止损平仓】盈利=$", DoubleToString(closeProfitUSD, 2),
" | 下次手数=", DoubleToString(tfData[idx].currentLots, 2),
" | 固定止损次数=", tfData[idx].martinFixedStopLossCount,
" | 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
// 进入冷却
if(tfData[idx].state != STATE_MARTIN_FOLLOW_PENDING)
{
tfData[idx].state = STATE_CLOSE_COOLDOWN;
tfData[idx].lastCloseTime = TimeCurrent();
}
}
else if(closeProfitUSD < -0.01)
{
// ===== 固定止损触发 =====
tfData[idx].martinFixedStopLossCount++;
tfData[idx].martinAccumulatedLossUSD += (-closeProfitUSD);
tfData[idx].currentLots = MathMin(tfData[idx].currentLots + BaseLots, MaxLots);
Print(EnumToString(tfData[idx].period),
" 【固定止损平仓】亏损=$", DoubleToString(-closeProfitUSD, 2),
" | 下次手数=", DoubleToString(tfData[idx].currentLots, 2),
" | 固定止损次数=", tfData[idx].martinFixedStopLossCount,
" | 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
if(tfData[idx].state != STATE_MARTIN_FOLLOW_PENDING)
{
tfData[idx].state = STATE_CLOSE_COOLDOWN;
tfData[idx].lastCloseTime = TimeCurrent();
}
}
else
{
// ===== 其他盈利平仓(MA15或马丁止盈已在Manage里处理,这里不会到) =====
Print(EnumToString(tfData[idx].period),
" 【其他平仓】盈利=$", DoubleToString(closeProfitUSD, 2));
}
tfData[idx].positionType = -1;
tfData[idx].entryPrice = 0;
tfData[idx].entryTime = 0;
tfData[idx].lastStopLoss = 0;
tfData[idx].hasModifiedSL = false;
tfData[idx].usedTrailingStopThisRound = false;
}
//+------------------------------------------------------------------+
int GetPositionType(int magic)
{
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == magic)
{
return (int)PositionGetInteger(POSITION_TYPE);
}
}
}
return -1;
}
//+------------------------------------------------------------------+
void CreatePanel()
{
ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, panelName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0, panelName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE, 9);
ObjectSetString(0, panelName, OBJPROP_FONT, "Courier New");
}
//+------------------------------------------------------------------+
void UpdatePanel()
{
string text = "=== MA Martin EA ===\n";
for(int i = 0; i < 3; i++)
{
text += EnumToString(tfData[i].period) + ": ";
if(tfData[i].positionType == POSITION_TYPE_BUY)
text += "BUY ";
else if(tfData[i].positionType == POSITION_TYPE_SELL)
text += "SELL ";
else
text += "- ";
text += " 浮盈=$" + DoubleToString(tfData[i].floatingProfitUSD, 2);
text += " 止损次数=" + IntegerToString(tfData[i].martinFixedStopLossCount);
text += " 累计亏=$" + DoubleToString(tfData[i].martinAccumulatedLossUSD, 2);
text += "\n";
}
ObjectSetString(0, panelName, OBJPROP_TEXT, text);
}作品说明
我仔细检查了代码,发现了多个严重逻辑漏洞:
漏洞1:MA15止盈后没有进入冷却
``cpp if(holdSeconds >= MinHoldSeconds && lastClosePrice <= tfData[idx].bufMA15[1]) { if(trade.PositionClose(_Symbol)) { ... tfData[idx].state = STATE_CLOSE_COOLDOWN; // ✓ 设置了冷却 } return; } ` **问题**:虽然设置了冷却,但如果 trade.PositionClose 失败,state` 不会改变,下一次 Tick 可能继续尝试平仓。
漏洞2:OnPositionClosed 里的状态判断错误
``cpp if(tfData[idx].state != STATE_MARTIN_FOLLOW_PENDING) { tfData[idx].state = STATE_CLOSE_COOLDOWN; tfData[idx].lastCloseTime = TimeCurrent(); } `` 问题:
- 如果是MA15止盈,
state已经被设置为STATE_CLOSE_COOLDOWN,这里会重复设置(浪费) - 如果是马丁止盈,
state是STATE_MARTIN_FOLLOW_PENDING,这里跳过设置,但lastCloseTime也没有设置,冷却逻辑可能混乱
漏洞3:手数累加没有上限检查
``cpp tfData[idx].currentLots += BaseLots; if(tfData[idx].currentLots > MaxLots) tfData[idx].currentLots = MaxLots; `` 问题:这个检查在多处重复。如果 MaxLots=1.0 但已经是 0.99,加 0.01 会变成 1.00,检查是对的。但如果连续多次固定止损,手数会一直卡在 MaxLots。
漏洞4:移动止损后,如果价格继续上升,会再次尝试修改SL
``cpp if(!tfData[idx].hasModifiedSL && profitPoints >= TrailingTrigger) { double newSL = openPrice + TrailingStop * _Point; if(newSL > currentSL) // ✓ 这个检查是对的 { if(trade.PositionModify(_Symbol, newSL, 0)) { tfData[idx].hasModifiedSL = true; } } } ` **问题**:虽然设置了 hasModifiedSL` 标志,但如果修改失败(比如网络错误),下次 Tick 会继续尝试。应该改为"一旦触发条件就立即标记,不管修改是否成功"。
漏洞5:平仓后的状态没有明确区分
``cpp OnPositionClosed → 判断是移动止损还是固定止损 → 更新马丁统计 → 设置冷却或追单 ` 但 ManageBuyPosition / ManageSellPosition 也会先一步设置状态(STATE_CLOSE_COOLDOWN 或 STATE_MARTIN_FOLLOW_PENDING`),导致逻辑混乱。
漏洞6:追单优先级问题
``cpp if(tfData[idx].state == STATE_MARTIN_FOLLOW_PENDING) { if(actualPosType == -1) { // 立即追单 } } `` 但如果追单时 Tick 之间有延迟,可能会出现"平仓后下一 Tick 还在冷却中"的情况,追单被延迟。
漏洞7:历史订单查询的时间范围太宽松
``cpp if(HistorySelect(tfData[idx].entryTime - 10, TimeCurrent())) ` 这会查询持仓入场后 10 秒内的**所有平仓订单**,如果同周期同方向有多个持仓,可能查到错误的订单。应该用 Position Ticket 查询相关的 Deal Ticket`。
完整的开仓→平仓逻辑流程说明
场景 A:固定止损触发(连续止损中)
``` 第1次开仓: ├─ 检查MA金叉信号 + MA距离 >= 400点 ├─ 开0.01手(BaseLots) ├─ 止损位 = 开仓价 - 400点 ├─ martinFixedStopLossCount = 0 ├─ martinAccumulatedLossUSD = 0 └─ state = STATE_BUY_OPEN
持仓管理(每个Tick): ├─ 浮盈 < 500点 → 等待移动止损条件 ├─ 收盘价还在MA10上方 → 继续持仓 └─ 浮盈 < 止损 → 等待固定止损触发
固定止损触发: ├─ OnPositionClosed: │ ├─ 查历史订单,closeProfitUSD = -$10(亏损) │ ├─ usedTrailingStopThisRound = false │ ├─ closeProfitUSD < -0.01 → 固定止损 │ ├─ martinFixedStopLossCount = 1 │ ├─ martinAccumulatedLossUSD = $10 │ ├─ currentLots = 0.02(手数翻倍) │ └─ state = STATE_CLOSE_COOLDOWN │ ├─ 冷却5秒 │ └─ 第2次开仓: ├─ 0.02手,止损 400点 ├─ martinFixedStopLossCount = 1 ├─ martinAccumulatedLossUSD = $10 └─ state = STATE_BUY_OPEN ```
场景 B:移动止损触发(保留马丁)
``` 第3次开仓: ├─ 0.03手(前2次都亏,累计 $10 + $20 = $30) ├─ martinFixedStopLossCount = 2 ├─ martinAccumulatedLossUSD = $30 └─ state = STATE_BUY_OPEN
持仓管理(每个Tick): ├─ 浮盈达到 500点(例如赚 $60) ├─ 触发移动止损条件 ├─ 修改 SL = 开仓价 + 200点 ├─ hasModifiedSL = true ├─ usedTrailingStopThisRound = true(标记用了移动止损!) └─ 继续持仓
价格回撤,触发移动止损: ├─ OnPositionClosed: │ ├─ 查历史订单,closeProfitUSD = $60(盈利) │ ├─ usedTrailingStopThisRound = true → 移动止损 │ ├─ 这 $60 盈利 不冲抵 累计亏损 $30 │ ├─ currentLots = 0.04(手数继续累加!) │ ├─ martinFixedStopLossCount = 2(保留!) │ ├─ martinAccumulatedLossUSD = $30(保留!) │ └─ state = STATE_CLOSE_COOLDOWN │ ├─ 冷却5秒 │ └─ 第4次开仓: ├─ 0.04手 ├─ martinFixedStopLossCount = 2(仍是2) ├─ martinAccumulatedLossUSD = $30(仍是$30) └─ 等待浮盈 >= $30 来触发马丁止盈 ```
场景 C:马丁止盈触发(重置马丁+追单)
``` 第4次持仓管理: ├─ 浮盈达到 $30+(覆盖前面的累计亏损) ├─ martinFixedStopLossCount = 2 >= MartinMaxLoss(4) ? │ ├─ 不满足(2 < 4)→ 继续持仓或等MA15止盈 │ └─ ... │ └─ 假设继续开第5、6次... ├─ 第5次亏损 → martinFixedStopLossCount = 3 ├─ 第6次亏损 → martinFixedStopLossCount = 4 │ ├─ 现在 martinFixedStopLossCount = 4 >= 4 ✓ │ └─ 开仓手数已累加到 0.06 手 │ └─ 第7次浮盈 >= 累计亏损 $XXX: ├─ ManageBuyPosition检查马丁止盈条件 ├─ profitUSD >= martinAccumulatedLossUSD ✓ ├─ 立即平仓 ├─ martinFixedStopLossCount = 0(重置!) ├─ martinAccumulatedLossUSD = 0(重置!) ├─ currentLots = 0.01(重置为基础手数!) ├─ state = STATE_MARTIN_FOLLOW_PENDING(标记待追单) ├─ followDirection = 0(多单) ├─ lastCloseTime = 当前时间 └─ return(不执行其他操作)
下一个 Tick(无持仓): ├─ state == STATE_MARTIN_FOLLOW_PENDING ├─ actualPosType == -1(确实没有持仓) ├─ 立即开 0.01 手追单 ├─ martinFixedStopLossCount = 0 ├─ martinAccumulatedLossUSD = 0 ├─ state = STATE_BUY_OPEN └─ 这是全新的一轮马丁 ```
场景 D:MA15止盈(不追单)
``` 持仓管理: ├─ 持仓时间 > 30秒(MinHoldSeconds) ├─ 收盘价 <= MA15(做多情况下) ├─ ManageBuyPosition 检查 ├─ 立即平仓 ├─ martinFixedStopLossCount = 0(重置!) ├─ martinAccumulatedLossUSD = 0(重置!) ├─ currentLots = 0.01(重置!) ├─ state = STATE_CLOSE_COOLDOWN(进入冷却,不追单) ├─ lastCloseTime = 当前时间 └─ return
冷却5秒后: ├─ state = STATE_NO_POS ├─ lastSignalBarTime = 0(重置,允许新的金叉信号) └─ 等待下一个金叉开仓信号 ```
修正后的完整代码
使用说明
安装与参数调整:
- 将代码保存为
.mq5文件,在 MetaTrader 5 中编译 - 黄金交易建议使用 M5、M15、M30 三周期同时运行
- 参数建议:
BaseLots=0.01基础手数,MaxLots=1.0最大手数限制
开仓规则:
- MA10 > MA20 + MA距离 ≥ 400点 + 收盘价 > MA10 → 做多
- MA10 < MA20 + MA距离 ≥ 400点 + 收盘价 < MA10 → 做空
- 第一次开 0.01 手,固定止损 400 点
平仓规则(按优先级):
- 马丁止盈(固定止损次数 ≥ 4):浮盈 ≥ 累计亏损 → 立即平仓 → 立即追单 0.01 手
- MA15止盈:持仓 > 30秒 + 收盘价触及 MA15 → 立即平仓,重置马丁但不追单
- 移动止损:浮盈 ≥ 500点 → SL 移至开仓价+200点 → 手数保留,马丁统计保留
损失追踪:
- 只计算固定止损的亏损总额
- 移动止损的盈利不冲抵累计亏损
- 每次固定止损后手数翻倍(0.01→0.02→0.03...最多 1.0)
Log 日志解读:
【开多】表示新开仓【固定止损平仓】累计止损次数+1【移动止损平仓】手数继续倍增,次数不变【马丁止盈后追单-多】立即开 0.01 追单,马丁完全重置
AI 解读根据源码逐行分析写成,供参考,以实际运行为准
这是什么
这是一个基于三条均线(10/15/20周期EMA)交叉的马丁格尔EA,同时在M5、M15、M30三个周期运行,每个周期独立管理仓位。它的核心逻辑是:移动止损触发后,手数继续累加但马丁计数保留;固定止损触发后,手数翻倍并累积亏损,直到浮盈覆盖全部亏损时平仓重置,平仓后立即追单。
逻辑原理
开仓信号:当10均线向上/向下穿越20均线,穿越时两条均线距离≥400点,且上根K线收盘价在10均线之上/之下时,做多/做空。开仓后设固定止损(默认400点),不设止盈。
三种出场方式:
- 移动止损:浮盈达到500点时,移动止损到入场价+200点(做多)或入场价-200点(做空)。触发后手数累加BaseLots,但马丁计数和累计亏损保留,继续冷却5秒后可再次开仓。
- 固定止损:亏损触发原始止损时,记录亏损金额和次数,手数累加BaseLots,进入冷却后重新开仓。
- 马丁止盈:当固定止损次数≥4次时,浮盈美元数≥累计亏损美元数时平仓。平仓后立即以BaseLots手数追单(同方向),马丁计数和累计亏损归零。
- MA15止盈:持仓≥30秒后,多单收盘价跌破15均线或空单收盘价突破15均线时平仓,马丁归零,冷却5秒。
手数管理:每次移动止损或固定止损后,下次开仓手数=当前手数+BaseLots,上限MaxLots。马丁止盈或MA15止盈后手数重置为BaseLots。
参数说明
- MA10_Period(10)/ MA15_Period(15)/ MA20_Period(20):三条均线的周期,用于交叉信号和MA15止盈判断。
- BaseLots(0.01):基础手数,也是每次累加的单位和马丁重置后的手数。
- MaxLots(1.0):单次最大手数,防止无限翻倍。
- StopLoss(400):固定止损点数,开仓时设置,触发后记录亏损。
- MADistanceMin(400):开仓时10均线和20均线的最小距离(点数),过滤假突破。
- TrailingTrigger(500):浮盈达到多少点时触发移动止损。
- TrailingStop(200):移动止损移到入场价+/-多少点,留一定利润空间。
- CooldownSeconds(5):平仓后的冷却时间(秒),防止同一根K线反复开仓。
- MinHoldSeconds(30):MA15止盈的最短持仓时间,避免刚开仓就被扫出。
- MartinMaxLoss(4):固定止损多少次后启用马丁止盈逻辑(浮盈≥累计亏损才平)。
- MagicBase(20250101):Magic Number基础值,三个周期分别用MagicBase+5、MagicBase+15、MagicBase+30,用于区分订单。
怎么用
- 将代码保存为.mq5文件,放到MT5的
MQL5/Experts/目录,编译无错误。 - 打开任意图表(建议XAUUSD或波动大的品种),拖入EA,选择"允许自动交易"。
- EA会自动在M5、M15、M30三个周期各开一套独立仓位,左上角面板显示每个周期的持仓方向、浮盈、止损次数和累计亏损。
- 观察日志和面板,确认每次平仓后的手数、马丁计数是否符合预期。
适用场景
适合趋势行情和震荡行情的混合品种,如黄金(XAUUSD)。10/20均线交叉在趋势行情中容易连续盈利,移动止损保护利润;震荡行情中可能多次固定止损,但马丁止盈逻辑可以在后续反弹中回本。三周期同时运行可以分散单一周期的假信号风险。
不适合单边瀑布行情或流动性极差的品种。连续固定止损会快速放大手数,如果达到MaxLots还未回本,可能面临大额亏损。建议在波动稳定的时段(欧美盘)使用,避开重大数据和假期。
风险提示
- 马丁风险:连续固定止损会累积手数和亏损,如果行情持续单边且达到MaxLots,单次回撤可能很大。建议根据账户资金调整BaseLots和MaxLots,确保4–5次止损后仍在承受范围内。
- 移动止损保留马丁:移动止损触发后,马丁计数不会重置,后续如果再次固定止损,手数会在已累加的基础上继续翻倍。这个设计可能让你在盈利后又陷入更大的回撤,需要严格控制MaxLots。
- 追单风险:马丁止盈后立即追单(同方向0.01手),如果行情反转,可能再次进入马丁循环。代码里没有限制追单次数,如果连续马丁止盈+追单+再马丁,累计次数可能超出预期。
- 滑点和点差:固定止损400点在黄金上约4美元,但如果点差过大或滑点严重,实际止损可能更大,影响马丁止盈的计算准确性。
- 三周期同时运行:三个周期可能同时持仓,最大占用资金=3*MaxLots。如果账户资金不足,可能因保证金不足无法开仓或被强平。
- 无最大亏损保护:代码里没有全局最大亏损或每日止损逻辑,如果行情极端,可能在一天内累积大额亏损。建议手动监控或在VPS上加外部风控脚本。
常见问题
- 移动止损后为什么马丁计数不重置?
- 这是作者设计的特殊逻辑。移动止损触发说明已经盈利,但如果后续再次固定止损,手数会在已累加的基础上继续翻倍,目的是保持马丁压力。如果不想要这个特性,需要修改OnPositionClosed里的判断,在移动止损后也重置martinFixedStopLossCount。
- 马丁止盈后为什么会立即追单?
- 代码里马丁止盈后state设为STATE_MARTIN_FOLLOW_PENDING,OnPositionClosed检测到平仓后立即调用OpenFollowBuy/Sell,以BaseLots手数追单。这是为了不错过趋势延续,但如果行情反转会再次进入马丁。可以在参数里加一个"是否追单"开关,或者改为普通冷却。
- 三个周期会同时开仓吗?会不会重仓?
- 会。M5、M15、M30各自独立判断信号,可能同时持有多单或空单,最大占用=3*MaxLots*手数。如果账户资金只够1手保证金,MaxLots设1.0可能不够用。建议根据账户净值调整BaseLots和MaxLots,或者只启用一个周期。
- 固定止损400点是不是太大了?
- 400点在黄金上约4美元止损(0.01手),对于波动大的品种算中等。如果你交易的品种点值不同或波动小,需要根据ATR或历史波动调整StopLoss。太小容易频繁触发马丁,太大单次亏损过多。
- 这个EA能用在其他品种吗?
- 代码没有限制品种,但参数(StopLoss、MADistanceMin、TrailingTrigger等)是按黄金波动设计的。如果用在外汇或指数,需要根据点值和波动重新测试。建议先在模拟盘跑一周,观察开仓频率和马丁触发情况。
Triple MA Martin EA with Trailing Stop Preserved (MT5)
A martingale EA based on 10/15/20 EMA crossovers. Trailing stop preserves martingale progression; fixed stop-loss doubles position size. After martingale profit target is hit, resets and follows the trend. Runs on M5/M15/M30 simultaneously.
Source code is shown above. Compiled EX5 files are available to VIP members.
评论
还没有人评论,来抢个沙发。