HT 指标 EA - 增强型趋势跟踪
支持分阶段止损、ATR 动态跟踪、反向信号平仓,适合大趋势行情
MT5 真机运行截图 · 服务器把编译好的 EX5 挂到黄金 H1 图表上截的,你装上去看到的就是这个样子
挂到 MT5 上运行时终端报了错
编译通过了,但真挂到图表上时终端记录了下面的错误。指标一报错就会被终端停掉,图上可能只剩半截线甚至一片空白—— 这就是「装上去和预期不一样」的原因。把报错连同代码丢给 AI 工作台,让它改好重新发布,截图会自动更新。
cannot load custom indicator 'HT' (XAUUSD.n) [4802]
#property copyright "厕所2号"
#property version "3.00"
#include <Trade\Trade.mqh>
//========== 指标设置 ==========
input string Section1 = "========== 指标设置 ==========";
input string IndicatorName = "HT";
input int InpAmplitude = 2;
input int InpAtrPeriod = 100;
input ENUM_TIMEFRAMES SignalTimeframe = PERIOD_M15;
//========== 交易设置 ==========
input string Section2 = "========== 交易设置 ==========";
input double InitialLot = 0.01;
input double MaxLot = 0.3;
//========== 摆动止损设置 ==========
input string Section3 = "========== 摆动止损设置 ==========";
input int SwingLookback = 10;
input int StopBuffer = 100;
//========== 分阶段止损设置(新增)==========
input string Section4 = "========== 分阶段止损设置 ==========";
input bool EnablePhaseTrailing = true; // 启用分阶段跟踪止损
input int Phase1ProfitTrigger = 300; // 第一阶段:盈利达此值(点)后进入第二阶段
input int Phase2ProfitTrigger = 800; // 第二阶段:盈利达此值(点)后进入第三阶段
input int Phase1SLBuffer = 200; // 第一阶段平仓:设置保本止损,缓冲此距离
input int Phase2ATRMultiplier = 2; // 第二阶段跟踪:用 2 倍 ATR 作为止损距离
input int Phase3ATRMultiplier = 1; // 第三阶段跟踪:用 1 倍 ATR 作为止损距离(更激进)
input int MinATRDistance = 100; // ATR 跟踪的最小距离(点)
input bool CloseOnReverseSignal = true; // 反向信号时立即平仓
//========== 风控设置 ==========
input string Section5 = "========== 风控设置 ==========";
input int MagicNumber = 888888;
input string TradeComment = "HT_EA_V3";
//========== 全局变量 ==========
CTrade trade;
int indicatorHandle = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
double upArrow[], downArrow[];
double currentLotBuy = 0.01;
double currentLotSell = 0.01;
datetime lastBarTime = 0;
// 持仓阶段跟踪
struct PositionPhase {
int phase; // 1=初期摆动, 2=中期保本, 3=深度跟踪
double entryPrice;
double highestPrice; // 做多最高点
double lowestPrice; // 做空最低点
int profitAtPhase2; // 进入第二阶段的盈利
};
PositionPhase positionPhaseInfo;
//+------------------------------------------------------------------+
//| EA 初始化
//+------------------------------------------------------------------+
int OnInit()
{
if(SwingLookback < 1)
{
Print("❌ 摆动回溯K线数必须大于0");
return INIT_PARAMETERS_INCORRECT;
}
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(50);
trade.SetTypeFilling(ORDER_FILLING_FOK);
trade.SetAsyncMode(false);
// 加载 HT 指标
indicatorHandle = iCustom(_Symbol, SignalTimeframe, IndicatorName,
InpAmplitude, InpAtrPeriod,
true, true, false, false, false, false);
if(indicatorHandle == INVALID_HANDLE)
{
Print("❌ 无法加载指标: ", IndicatorName);
return INIT_FAILED;
}
// 加载 ATR 指标
atrHandle = iATR(_Symbol, SignalTimeframe, InpAtrPeriod);
if(atrHandle == INVALID_HANDLE)
{
Print("❌ 无法加载 ATR 指标");
return INIT_FAILED;
}
currentLotBuy = InitialLot;
currentLotSell = InitialLot;
Print("✅ EA 初始化成功 (v3.0)");
Print(" 分阶段止损: ", EnablePhaseTrailing ? "启用" : "禁用");
Print(" 第一阶段触发: ", Phase1ProfitTrigger, " 点");
Print(" 第二阶段触发: ", Phase2ProfitTrigger, " 点");
Print(" 反向信号平仓: ", CloseOnReverseSignal ? "是" : "否");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| EA 反初始化
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(indicatorHandle != INVALID_HANDLE)
IndicatorRelease(indicatorHandle);
if(atrHandle != INVALID_HANDLE)
IndicatorRelease(atrHandle);
Print("EA 已停止");
}
//+------------------------------------------------------------------+
//| 每个 Tick 执行
//+------------------------------------------------------------------+
void OnTick()
{
datetime currentBarTime = iTime(_Symbol, SignalTimeframe, 0);
bool isNewBar = (currentBarTime != lastBarTime);
if(isNewBar)
{
lastBarTime = currentBarTime;
CheckSignalsAndTrade();
}
// 每个 Tick 检查分阶段止损和跟踪
if(EnablePhaseTrailing)
CheckPhaseTrailing();
}
//+------------------------------------------------------------------+
//| 检查信号并开单
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
{
int calculated = BarsCalculated(indicatorHandle);
if(calculated < 2)
return;
if(CopyBuffer(indicatorHandle, 6, 0, 3, upArrow) < 3)
return;
if(CopyBuffer(indicatorHandle, 7, 0, 3, downArrow) < 3)
return;
int signalBar = 1;
//====== 做多信号 ======
if(upArrow[signalBar] != EMPTY_VALUE && upArrow[signalBar] > 0)
{
if(HasPosition(POSITION_TYPE_BUY))
return;
double swingLow = GetSwingLow(SwingLookback);
if(swingLow <= 0)
return;
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = swingLow - StopBuffer * _Point;
if(sl >= price)
sl = price - StopBuffer * _Point;
double lotToUse = NormalizeLot(currentLotBuy);
if(trade.Buy(lotToUse, _Symbol, price, sl, 0, TradeComment))
{
Print("🔵 做多开单: ", price, " | 止损: ", sl);
// 初始化持仓阶段信息
positionPhaseInfo.phase = 1;
positionPhaseInfo.entryPrice = price;
positionPhaseInfo.highestPrice = price;
positionPhaseInfo.profitAtPhase2 = 0;
currentLotBuy = InitialLot;
}
}
//====== 做空信号 ======
if(downArrow[signalBar] != EMPTY_VALUE && downArrow[signalBar] > 0)
{
if(HasPosition(POSITION_TYPE_SELL))
return;
double swingHigh = GetSwingHigh(SwingLookback);
if(swingHigh <= 0)
return;
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = swingHigh + StopBuffer * _Point;
if(sl <= price)
sl = price + StopBuffer * _Point;
double lotToUse = NormalizeLot(currentLotSell);
if(trade.Sell(lotToUse, _Symbol, price, sl, 0, TradeComment))
{
Print("🔴 做空开单: ", price, " | 止损: ", sl);
positionPhaseInfo.phase = 1;
positionPhaseInfo.entryPrice = price;
positionPhaseInfo.lowestPrice = price;
positionPhaseInfo.profitAtPhase2 = 0;
currentLotSell = InitialLot;
}
}
//====== 反向信号平仓 ======
if(CloseOnReverseSignal)
{
if(upArrow[signalBar] != EMPTY_VALUE && upArrow[signalBar] > 0 && HasPosition(POSITION_TYPE_SELL))
{
CloseAllPositionByType(POSITION_TYPE_SELL);
Print("⚠️ 反向做多信号,平仓所有做空");
}
if(downArrow[signalBar] != EMPTY_VALUE && downArrow[signalBar] > 0 && HasPosition(POSITION_TYPE_BUY))
{
CloseAllPositionByType(POSITION_TYPE_BUY);
Print("⚠️ 反向做空信号,平仓所有做多");
}
}
}
//+------------------------------------------------------------------+
//| 检查分阶段止损和跟踪(核心优化逻辑)
//+------------------------------------------------------------------+
void CheckPhaseTrailing()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double currentPrice = (posType == POSITION_TYPE_BUY) ? bid : ask;
if(posType == POSITION_TYPE_BUY)
ProcessBuyPhaseTrailing(ticket, openPrice, currentSL, currentTP, currentPrice, bid);
else
ProcessSellPhaseTrailing(ticket, openPrice, currentSL, currentTP, currentPrice, ask);
}
}
//+------------------------------------------------------------------+
//| 处理做多的分阶段止损
//+------------------------------------------------------------------+
void ProcessBuyPhaseTrailing(ulong ticket, double openPrice, double currentSL,
double currentTP, double currentPrice, double bid)
{
double profitPoints = (currentPrice - openPrice) / _Point;
// 更新最高价
if(currentPrice > positionPhaseInfo.highestPrice)
positionPhaseInfo.highestPrice = currentPrice;
double newSL = currentSL;
//===== 第一阶段:只用摆动止损 =====
if(positionPhaseInfo.phase == 1 && profitPoints >= Phase1ProfitTrigger)
{
positionPhaseInfo.phase = 2;
positionPhaseInfo.profitAtPhase2 = (int)profitPoints;
// 激活保本:移至入场价 + Phase1SLBuffer
newSL = openPrice + Phase1SLBuffer * _Point;
Print("📈 [做多] 进入第二阶段 | 票号: ", ticket);
Print(" 触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase1ProfitTrigger, " 点");
Print(" 新止损: ", newSL, " (保本缓冲 ", Phase1SLBuffer, " 点)");
}
//===== 第二阶段:ATR 2x 跟踪 =====
if(positionPhaseInfo.phase == 2)
{
if(profitPoints >= Phase2ProfitTrigger)
{
positionPhaseInfo.phase = 3;
Print("📈 [做多] 进入第三阶段 | 票号: ", ticket);
Print(" 触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase2ProfitTrigger, " 点");
}
else
{
// 用 2 倍 ATR 作为动态止损
double atrValue = GetATRValue();
double atrDistance = MathMax(atrValue * Phase2ATRMultiplier / _Point, MinATRDistance);
newSL = positionPhaseInfo.highestPrice - atrDistance * _Point;
// 只在新止损高于当前止损时修改
if(newSL > currentSL + _Point)
{
if(trade.PositionModify(ticket, newSL, currentTP))
{
Print("📊 [做多] 第二阶段止损更新 | 票号: ", ticket);
Print(" ATR: ", DoubleToString(atrValue, 2), " | 2x距离: ",
DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
}
}
}
}
//===== 第三阶段:ATR 1x 激进跟踪 =====
if(positionPhaseInfo.phase == 3)
{
double atrValue = GetATRValue();
double atrDistance = MathMax(atrValue * Phase3ATRMultiplier / _Point, MinATRDistance);
newSL = positionPhaseInfo.highestPrice - atrDistance * _Point;
if(newSL > currentSL + _Point)
{
if(trade.PositionModify(ticket, newSL, currentTP))
{
Print("📊 [做多] 第三阶段止损更新 | 票号: ", ticket);
Print(" ATR: ", DoubleToString(atrValue, 2), " | 1x距离: ",
DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
}
}
}
}
//+------------------------------------------------------------------+
//| 处理做空的分阶段止损
//+------------------------------------------------------------------+
void ProcessSellPhaseTrailing(ulong ticket, double openPrice, double currentSL,
double currentTP, double currentPrice, double ask)
{
double profitPoints = (openPrice - currentPrice) / _Point;
// 更新最低价
if(currentPrice < positionPhaseInfo.lowestPrice)
positionPhaseInfo.lowestPrice = currentPrice;
double newSL = currentSL;
//===== 第一阶段:只用摆动止损 =====
if(positionPhaseInfo.phase == 1 && profitPoints >= Phase1ProfitTrigger)
{
positionPhaseInfo.phase = 2;
positionPhaseInfo.profitAtPhase2 = (int)profitPoints;
// 激活保本:移至入场价 - Phase1SLBuffer
newSL = openPrice - Phase1SLBuffer * _Point;
Print("📈 [做空] 进入第二阶段 | 票号: ", ticket);
Print(" 触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase1ProfitTrigger, " 点");
Print(" 新止损: ", newSL, " (保本缓冲 ", Phase1SLBuffer, " 点)");
}
//===== 第二阶段:ATR 2x 跟踪 =====
if(positionPhaseInfo.phase == 2)
{
if(profitPoints >= Phase2ProfitTrigger)
{
positionPhaseInfo.phase = 3;
Print("📈 [做空] 进入第三阶段 | 票号: ", ticket);
Print(" 触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase2ProfitTrigger, " 点");
}
else
{
// 用 2 倍 ATR 作为动态止损
double atrValue = GetATRValue();
double atrDistance = MathMax(atrValue * Phase2ATRMultiplier / _Point, MinATRDistance);
newSL = positionPhaseInfo.lowestPrice + atrDistance * _Point;
// 只在新止损低于当前止损时修改
if(newSL < currentSL - _Point)
{
if(trade.PositionModify(ticket, newSL, currentTP))
{
Print("📊 [做空] 第二阶段止损更新 | 票号: ", ticket);
Print(" ATR: ", DoubleToString(atrValue, 2), " | 2x距离: ",
DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
}
}
}
}
//===== 第三阶段:ATR 1x 激进跟踪 =====
if(positionPhaseInfo.phase == 3)
{
double atrValue = GetATRValue();
double atrDistance = MathMax(atrValue * Phase3ATRMultiplier / _Point, MinATRDistance);
newSL = positionPhaseInfo.lowestPrice + atrDistance * _Point;
if(newSL < currentSL - _Point)
{
if(trade.PositionModify(ticket, newSL, currentTP))
{
Print("📊 [做空] 第三阶段止损更新 | 票号: ", ticket);
Print(" ATR: ", DoubleToString(atrValue, 2), " | 1x距离: ",
DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
}
}
}
}
//+------------------------------------------------------------------+
//| 获取 ATR 值
//+------------------------------------------------------------------+
double GetATRValue()
{
double atrBuffer[];
if(BarsCalculated(atrHandle) < 1)
return 100 * _Point; // 默认返回 100 点
if(CopyBuffer(atrHandle, 0, 0, 1, atrBuffer) <= 0)
return 100 * _Point;
return atrBuffer[0];
}
//+------------------------------------------------------------------+
//| 获取摆动低点
//+------------------------------------------------------------------+
double GetSwingLow(int lookback)
{
double lowArray[];
ArraySetAsSeries(lowArray, true);
if(CopyLow(_Symbol, SignalTimeframe, 0, lookback + 1, lowArray) <= 0)
return 0;
double swingLow = lowArray[1];
for(int i = 1; i <= lookback && i < ArraySize(lowArray); i++)
{
if(lowArray[i] < swingLow)
swingLow = lowArray[i];
}
return swingLow;
}
//+------------------------------------------------------------------+
//| 获取摆动高点
//+------------------------------------------------------------------+
double GetSwingHigh(int lookback)
{
double highArray[];
ArraySetAsSeries(highArray, true);
if(CopyHigh(_Symbol, SignalTimeframe, 0, lookback + 1, highArray) <= 0)
return 0;
double swingHigh = highArray[1];
for(int i = 1; i <= lookback && i < ArraySize(highArray); i++)
{
if(highArray[i] > swingHigh)
swingHigh = highArray[i];
}
return swingHigh;
}
//+------------------------------------------------------------------+
//| 检查是否有指定方向持仓
//+------------------------------------------------------------------+
bool HasPosition(ENUM_POSITION_TYPE type)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| 平仓指定方向所有持仓
//+------------------------------------------------------------------+
void CloseAllPositionByType(ENUM_POSITION_TYPE type)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
trade.PositionClose(ticket);
}
}
//+------------------------------------------------------------------+
//| 交易事件回调(检测亏损后翻倍)
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
const MqlTradeResult& result)
{
if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
return;
if(!HistoryDealSelect(trans.deal))
return;
if(HistoryDealGetInteger(trans.deal, DEAL_MAGIC) != MagicNumber)
return;
if(HistoryDealGetString(trans.deal, DEAL_SYMBOL) != _Symbol)
return;
ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT)
return;
double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
double swap = HistoryDealGetDouble(trans.deal, DEAL_SWAP);
double commission = HistoryDealGetDouble(trans.deal, DEAL_COMMISSION);
double totalProfit = profit + swap + commission;
ENUM_DEAL_TYPE dealType = (ENUM_DEAL_TYPE)HistoryDealGetInteger(trans.deal, DEAL_TYPE);
if(totalProfit < 0)
{
if(dealType == DEAL_TYPE_BUY)
{
currentLotBuy = currentLotBuy * 2;
if(currentLotBuy > MaxLot)
currentLotBuy = MaxLot;
Print("📉 做多止损 | 亏损: ", DoubleToString(totalProfit, 2),
" | 下次手数: ", currentLotBuy);
}
else if(dealType == DEAL_TYPE_SELL)
{
currentLotSell = currentLotSell * 2;
if(currentLotSell > MaxLot)
currentLotSell = MaxLot;
Print("📉 做空止损 | 亏损: ", DoubleToString(totalProfit, 2),
" | 下次手数: ", currentLotSell);
}
}
else if(totalProfit > 0)
{
if(dealType == DEAL_TYPE_BUY)
{
currentLotBuy = InitialLot;
Print("📈 做多盈利 | 盈利: ", DoubleToString(totalProfit, 2),
" | 手数重置: ", currentLotBuy);
}
else if(dealType == DEAL_TYPE_SELL)
{
currentLotSell = InitialLot;
Print("📈 做空盈利 | 盈利: ", DoubleToString(totalProfit, 2),
" | 手数重置: ", currentLotSell);
}
}
}
//+------------------------------------------------------------------+
//| 规范化手数
//+------------------------------------------------------------------+
double NormalizeLot(double lot)
{
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(lot < minLot)
lot = minLot;
if(lot > maxLot)
lot = maxLot;
lot = MathFloor(lot / stepLot) * stepLot;
return lot;
}作品说明
我先分析一下这个 EA 的核心逻辑和痛点。
分析与优化方案
现状痛点识别:
- 保本触发太激进 — 当价格触及「摆动高点 - BreakevenTriggerOffset」时就立即激活保本,在大趋势行情中这个条件往往很快满足,导致止损被锁定在接近入场价的位置。
- 移动止损步长固定 — 每盈利固定点数才移动一次,在大行情中跟不上价格变化的速度,容易被回调止损。
- 缺乏趋势强度判断 — 没有区分「盘整回调」和「大趋势继续」,一律用同样的摆动逻辑处理。
优化方案:
- 分阶段止损策略:
- 第一阶段(初期):只用摆动止损,不激活保本,让利润充分积累
- 第二阶段(中期盈利):盈利达到较深的目标(如 500 点)时才激活保本
- 第三阶段(大幅盈利):改用更激进的跟踪止损,每个新高 / 新低主动追踪
- 加入 ATR 动态跟踪 — 用 ATR 衡量波动率,而不是固定点数,大行情时自动跟踪更快
- 反向信号平仓 — HT 指标产生反向信号时强制平仓(可选),防止大行情反转时亏损扩大
- 保留摆动止损作为最后防线 — 不完全依赖移动止损
我给你写个升级版本,加入「深度盈利触发」和「ATR 动态跟踪」:
使用说明
- 分阶段逻辑:盈利 300 点→保本,盈利 800 点→进入 ATR 激进跟踪。参数可根据品种调整。
- Phase1ProfitTrigger / Phase2ProfitTrigger:控制从摆动止损、到保本、再到 ATR 跟踪的两个阶段,比之前一成不变的策略更符合大趋势节奏。
- Phase2ATRMultiplier / Phase3ATRMultiplier:第二、三阶段用 ATR 的倍数动态设置止损距离,大行情波动大时自动放宽,防止被回调止损。
- CloseOnReverseSignal:反向信号时强制平仓,保护大幅利润。
- MinATRDistance:ATR 过小时的最小止损距离下限,避免止损设置过紧。
- 旧参数(SwingLookback、StopBuffer)保留,作为第一阶段的保底止损。
- 关键改进:延迟保本激活 → 让利润充分积累 → ATR 动态跟踪 → 自适应波动 → 反向信号平仓 → 保护大单。
- 建议在回测中调整 Phase1/Phase2 的触发点和 ATR 倍数,找到平衡点。
AI 解读根据源码逐行分析写成,供参考,以实际运行为准
这是什么
这是一个为 MT5 平台开发的自动交易 EA(Expert Advisor),必须配合名为 "HT" 的自定义指标使用。它的核心特点是「分三阶段管理止损」:初期用摆动高低点守住底线,盈利达标后切换到保本模式,大幅盈利时改用 ATR 动态跟踪,让利润跑得更远。同时支持反向信号强制平仓,避免趋势反转时吃回利润。
逻辑原理
开仓信号来自外部 HT 指标的箭头:出现向上箭头(upArrow)时做多,向下箭头(downArrow)时做空。止损设在最近 N 根 K 线的摆动高点或低点外侧,用 StopBuffer 参数留出缓冲距离。
分阶段止损是核心亮点:
- 第一阶段(初期):止损保持在摆动高低点,不轻易移动,给价格足够的波动空间
- 第二阶段(中期盈利):浮盈达到 Phase1ProfitTrigger(默认 300 点)后,止损移至入场价上方 Phase1SLBuffer 距离(保本 + 缓冲),开始用 ATR 的 2 倍作为跟踪距离
- 第三阶段(深度盈利):浮盈达到 Phase2ProfitTrigger(默认 800 点)后,改用 ATR 的 1 倍激进跟踪,止损跟得更紧,锁定大部分利润
每个阶段的止损距离根据当前 ATR 值动态计算,波动大的时候自动放宽,波动小的时候收紧,避免被正常回调扫出场。
反向信号平仓:如果持有做多单时 HT 指标出现做空箭头(或反之),EA 会立即平掉所有持仓,不等止损触发。
资金管理用了马丁格尔策略:亏损后下一单手数翻倍(最高到 MaxLot),盈利后重置为 InitialLot。这是一把双刃剑,能快速回本,但连续亏损会急速放大风险。
参数说明
指标设置
- IndicatorName("HT"):外部指标的文件名,默认是 "HT",必须确保该指标已安装在 Indicators 文件夹
- InpAmplitude(2):传给 HT 指标的振幅参数,具体含义取决于 HT 指标的设计
- InpAtrPeriod(100):ATR 计算周期,用于动态止损距离,周期越长越平滑
- SignalTimeframe(M15):读取 HT 信号的时间周期,默认 15 分钟
交易设置
- InitialLot(0.01):初始手数,建议根据账户余额调整
- MaxLot(0.3):马丁格尔翻倍的上限,防止单笔风险过大
摆动止损设置
- SwingLookback(10):回溯多少根 K 线找摆动高低点,数值越大止损越宽
- StopBuffer(100):止损在摆动点外留的缓冲距离(点),防止被瞬间波动扫到
分阶段止损设置
- EnablePhaseTrailing(true):是否启用分阶段跟踪,关闭后只用摆动止损
- Phase1ProfitTrigger(300):盈利多少点后进入第二阶段(保本),适合波动品种可调大
- Phase2ProfitTrigger(800):盈利多少点后进入第三阶段(激进跟踪),大趋势中才能达到
- Phase1SLBuffer(200):第二阶段保本时,止损在入场价上方留多少点缓冲
- Phase2ATRMultiplier(2):第二阶段用 ATR 的 2 倍作为止损距离,相对保守
- Phase3ATRMultiplier(1):第三阶段用 ATR 的 1 倍,更激进,快速锁定利润
- MinATRDistance(100):ATR 计算出的距离如果太小,强制用这个最小值,防止止损过紧
- CloseOnReverseSignal(true):反向信号时立即平仓,关闭后允许持仓等待止损
风控设置
- MagicNumber(888888):EA 的唯一标识,多个 EA 同时运行时用来区分
- TradeComment("HT_EA_V3"):订单备注
怎么用
- 安装 HT 指标:先确保名为 "HT" 的自定义指标已放在 MT5 的
MQL5/Indicators文件夹,重启 MT5 后在图表上能手动加载它 - 编译 EA:把这个 EA 源码放到
MQL5/Experts文件夹,在 MetaEditor 里编译成 .ex5 文件 - 挂载到图表:打开你想交易的品种(如 XAUUSD)和周期(建议 M15 或以上),把 EA 拖到图表上
- 调整参数:根据品种波动特性调整 Phase1/Phase2 触发点、ATR 倍数、InitialLot 和 MaxLot
- 启用自动交易:点击 MT5 工具栏的 "AutoTrading" 按钮,EA 会在 HT 指标出现箭头时自动开单
- 观察日志:终端窗口的 "Expert" 选项卡会打印每次进入新阶段、修改止损的详细信息
EA 不会在图表上画任何东西,所有信号和止损逻辑都通过订单和日志体现。
适用场景
适合的行情:单边大趋势,价格持续朝一个方向走几百上千点,中间回调幅度不超过 ATR 的 2 倍。典型场景是重大消息后的单边行情、趋势突破后的延续阶段。
适合的品种:波动较大、点差相对小的品种,如 XAUUSD(黄金)、主要货币对(EURUSD、GBPUSD)、原油。点差过大的小品种会被分阶段止损频繁扫出。
适合的周期:M15 及以上,H1 和 H4 更理想。M5 以下噪音太多,摆动止损和 ATR 跟踪都容易误判。
不适合的情况:
- 震荡行情:HT 指标会频繁产生箭头,每次开单都被摆动止损打掉,马丁格尔翻倍会急速亏损
- ATR 极小的时段(如亚洲盘早盘):MinATRDistance 会强制止损收得过紧
- 连续假突破:反向信号平仓会导致来回止损
风险提示
依赖外部指标:EA 完全依赖 HT 指标的箭头信号,如果 HT 指标本身有重绘问题(即箭头出现后又消失),EA 的回测结果会严重失真,实盘可能完全不同。使用前必须先确认 HT 指标不重绘。
马丁格尔翻倍:亏损后手数翻倍是高风险策略,连续 5 次亏损后手数会变成初始的 32 倍。如果 InitialLot = 0.01、MaxLot = 0.3,只需 5 次亏损就触顶。建议只在大概率趋势延续时使用,或者把 MaxLot 设得保守一些。
滑点和点差:分阶段修改止损时,如果在快速行情中滑点较大,实际成交价可能和预期有偏差。Phase1SLBuffer 和 MinATRDistance 建议设得比品种平均点差大 3 倍以上。
反向信号误判:如果 HT 指标在趋势中途产生短暂反向箭头(假信号),CloseOnReverseSignal 会让你过早离场,错过后续大段利润。可以根据 HT 指标的稳定性决定是否关闭这个功能。
ATR 滞后性:ATR 是历史波动率的平均值,在行情突然加速时会滞后,可能导致第三阶段止损跟得太慢。InpAtrPeriod 设得越大越平滑但越滞后,建议在回测中找平衡点。
单一持仓假设:代码中 PositionPhase 结构体是全局变量,隐含假设同一时间只有一个方向的持仓。如果手动或其他 EA 同时开了多单,阶段跟踪会混乱。
常见问题
- 这个 EA 必须配合 HT 指标吗?我能用其他指标吗?
- 必须配合。EA 的开仓信号完全来自 HT 指标的 upArrow 和 downArrow 缓冲区(第 6、7 号缓冲区)。如果你想用其他指标,需要修改源码中 `CopyBuffer(indicatorHandle, 6, ...)` 这部分逻辑,改成你的指标的缓冲区索引和信号判断条件。
- 分阶段止损的 Phase1 和 Phase2 触发点怎么调?
- Phase1ProfitTrigger 决定多快进入保本,默认 300 点适合黄金 M15。如果品种波动小(如 EURUSD)可以改成 30-50 点;Phase2ProfitTrigger 决定多深的利润才激进跟踪,默认 800 点只有大趋势才能达到。建议先在策略测试器里回测历史数据,看多少点后行情通常会延续,把触发点设在「趋势确认」的位置。
- 马丁格尔翻倍策略会不会爆仓?
- 有可能。连续亏损时手数翻倍,5 次亏损后是初始的 32 倍(0.01 → 0.32),如果账户余额不够或达到 MaxLot 上限,再亏一次可能触发强平。这个策略适合趋势延续概率高的场景,震荡行情中非常危险。保守做法是把 MaxLot 设为 InitialLot 的 4-8 倍,或者直接关闭翻倍(把源码里 `currentLotBuy * 2` 改成 `InitialLot`)。
- 适合在 M5 周期用吗?
- 不建议。M5 噪音大,摆动止损会频繁被触发,ATR 值也不稳定。代码里 SignalTimeframe 默认是 M15,这是作者测试过的最小周期。如果一定要用 M5,建议把 SwingLookback 调大到 20 以上、StopBuffer 加到 150-200 点,并在模拟盘测试至少一周。
- 反向信号平仓会不会太敏感?
- 取决于 HT 指标的稳定性。如果 HT 在趋势中途经常闪现反向箭头,CloseOnReverseSignal = true 会让你频繁离场。可以先在图表上手动加载 HT 指标,观察几天它的箭头是否可靠,如果假信号多就把这个参数改成 false,单纯依靠分阶段止损出场。
HT Phase Trailing EA with ATR Dynamic Stop Loss for MT5
An automated EA that trades HT indicator signals with 3-phase stop-loss strategy (swing → breakeven → aggressive ATR trailing). Features reverse-signal closing and martingale money management for trending markets.
Source code is shown above. Compiled EX5 files are available to VIP members.
评论
还没有人评论,来抢个沙发。