黄金的结构密码:一份基于突破回踩逻辑的MQL4 EA
引言
如果你在图表上观察黄金(XAUUSD)的时间足够长,就会发现一件很有趣的事:黄金特别"认"位置。它喜欢测试前期高点,先假突破一下,然后拉回来把突破前的那个水平踩成新的支撑,接着才真正启动一波趋势。这不是我个人的臆想,而是黄金这个市场特有的运行方式。
世界黄金协会(WGC)在《黄金:最有效的商品投资——2026年版》报告中明确指出,黄金的供需动态与其他商品存在根本性差异。与石油或工业金属不同,历史上开采出的黄金大部分至今仍然存在——要么是金条、金饰,要么是央行储备或ETF持仓。这意味着黄金的"可用供给"对价格水平的反应远比年度新开采量要敏感得多。当黄金突破某个关键结构水平时,大量此前"休眠"的供给或需求会突然涌入市场。
这正是我今天要分享的这个EA的核心逻辑来源。它不追突破,而是等市场先亮出底牌——突破一个关键结构位——然后在必然出现的"回踩"过程中寻找入场时机。
策略逻辑:突破→确认→回踩→入场
核心理念
这个EA基于一个简单但非常有效的前提:黄金有意义的趋势行情往往始于一次突破,但最好的入场点通常出现在对突破水平的回踩确认过程中。
为什么这个逻辑对黄金特别有效?
这是一个结构性的原因。世界黄金协会(WGC)指出,在市场压力时期,黄金的投资需求会激增。这会催生剧烈的单边行情。但这些行情很少是直线运行的。因为黄金的大部分持仓存在于场外交易(OTC)和ETF中,价格运动会在关键水平上触发长期持有者的反应——他们可能选择获利了结,也可能选择加仓。这种市场行为特征,就形成了黄金上常见的"突破-回踩"模式。这个EA就是专门为捕捉这种模式而设计的。
风控体系
EA包含波动率保护机制。当点差或ATR超过用户设定的阈值时,EA不会开立新单。同时,我也加入了基于风险百分比的头寸计算功能,根据账户余额和止损距离自动计算手数。
独家视角:黄金的"钟摆"特性
我不主张把黄金当作普通的趋势跟踪品种来对待。根据我对这个市场的长期观察,以及国际清算银行(BIS)最近的警告,黄金越来越像一个"散户驱动型"资产。BIS报告显示,2025年散户投资者对黄金ETF的需求在六个月内翻了三倍,资金流入超过700亿美元。这是一把双刃剑:它提供了强劲的动量,但也造成了夸张的波动和深度的回撤。
这对EA设计来说是一个关键洞察。如果我设计一个EA去激进地追突破,在黄金这个品种上,它会被散户驱动的急涨急跌来回打脸。BIS将2025年黄金的行为描述为"爆炸性"的,年度涨幅达60%——这是1979年以来的最大涨幅——主要由投机资金驱动。
我由此得出的判断很直接:当前黄金的市场结构被一个"趋势跟踪的散户群体"所主导。 这个群体擅长制造趋势,但极不擅长组织有序的回调。因此,一个等待回踩(而非追突破)的EA,实际上是在利用这个群体可预测的行为模式。当恐慌或狂热尘埃落定之后,价格往往会回到"事发现场"——那个被突破的结构水平。
源码:黄金突破回踩EA (MQL4)
``
cpp
//+------------------------------------------------------------------+
//| XAU_BreakRetest_EA.mq4 |
//| Copyright 2023, FXEAR.com |
//| https://www.fxear.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, FXEAR.com"
#property link "https://www.fxear.com"
#property version "1.00"
#property strict
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
// --- 区间检测 ---
input int ZoneLookback = 100; // 扫描K线数量以检测高低点 (范围: 50-200, 默认: 100)
input int ZoneRadius = 25; // 区间容差(点数) (范围: 10-50, 默认: 25)
input bool UseSwingMethod = true; // true=摆动高低点, false=枢轴点
// --- 入场逻辑 ---
input int RetestPips = 20; // 回踩距离区间边界的最大距离(点数) (范围: 10-50, 默认: 20)
input int BreakPips = 15; // 突破确认的最小距离(点数) (范围: 10-30, 默认: 15)
input bool ConfirmWithClose = true; // 要求K线收盘突破区间确认突破
// --- 趋势过滤器 ---
input bool UseTrendFilter = true; // 启用EMA趋势过滤
input int EMAPeriod = 50; // EMA周期 (默认: 50)
// --- 头寸规模与风控 ---
input double LotSize = 0.01; // 固定手数(UseAutoLot为false时生效)
input bool UseAutoLot = true; // 启用基于风险的头寸计算
input double RiskPercent = 1.5; // 每笔风险占账户百分比 (范围: 0.5-3.0)
input int SLDistance = 150; // 止损距离(点数) (范围: 80-300)
input int TakeProfit = 300; // 止盈距离(点数) (范围: 150-500)
// --- 过滤器 ---
input int MaxSpread = 30; // 最大允许点差(点数)
input int MinATR = 10; // 最小ATR值(波动率过滤)
input int MaxATR = 80; // 最大ATR值(波动率过滤)
input int ATRPeriod = 14; // ATR周期
// --- 时段过滤器 ---
input bool UseSessionFilter = true; // 启用交易时段过滤
input int StartHour = 7; // GMT起始小时
input int EndHour = 18; // GMT结束小时
// --- 通用设置 ---
input int MagicNumber = 202310; // EA魔术号
input int Slippage = 30; // 滑点(点数)
input int MaxOrders = 1; // 最大同时持仓数量
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
double pipSize;
int atrHandle, emaHandle;
double zoneHigh, zoneLow;
bool zoneDefined = false;
datetime lastBarTime;
//+------------------------------------------------------------------+
//| 初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
// 计算点值
pipSize = Point 10;
if(Digits == 3 || Digits == 5) pipSize = Point 10;
else if(Digits == 4) pipSize = Point;
else if(Digits == 2) pipSize = Point 10;
// 创建指标句柄
atrHandle = iATR(Symbol(), PERIOD_H1, ATRPeriod);
emaHandle = iMA(Symbol(), PERIOD_H1, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(atrHandle == INVALID_HANDLE || emaHandle == INVALID_HANDLE)
{
Print("指标句柄创建失败,错误码:", GetLastError());
return(INIT_FAILED);
}
Print("EA初始化成功,品种:", Symbol());
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 反初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IndicatorRelease(atrHandle);
IndicatorRelease(emaHandle);
Print("EA已卸载,原因代码:", reason);
}
//+------------------------------------------------------------------+
//| Tick主函数 |
//+------------------------------------------------------------------+
void OnTick()
{
// 仅在新K线开始时执行
if(Time[0] == lastBarTime) return;
lastBarTime = Time[0];
// --- 1. 过滤器检查 ---
if(!PassesFilters()) return;
// --- 2. 检测区间 ---
DetectZone();
// --- 3. 检查现有持仓 ---
int orderCount = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
{
orderCount++;
// 管理现有持仓(追踪止损)
ManageTrailingStop();
}
}
}
if(orderCount >= MaxOrders) return;
// --- 4. 检查突破 ---
double currentClose = Close[1];
bool bullishBreak = (currentClose > zoneHigh + BreakPips pipSize);
bool bearishBreak = (currentClose < zoneLow - BreakPips pipSize);
if(!bullishBreak && !bearishBreak) return;
// --- 5. 检查趋势方向 ---
int trendDirection = 0;
if(UseTrendFilter)
trendDirection = GetTrendDirection();
// --- 6. 检查回踩并入场 ---
double currentPrice = (bullishBreak) ? Bid : Ask;
if(bullishBreak && (trendDirection >= 0 || !UseTrendFilter))
{
double retestLevelLow = zoneHigh - RetestPips pipSize;
double retestLevelHigh = zoneHigh + RetestPips pipSize;
if(currentPrice >= retestLevelLow && currentPrice <= retestLevelHigh)
OpenBuyOrder();
}
if(bearishBreak && (trendDirection <= 0 || !UseTrendFilter))
{
double retestLevelLow = zoneLow - RetestPips pipSize;
double retestLevelHigh = zoneLow + RetestPips pipSize;
if(currentPrice >= retestLevelLow && currentPrice <= retestLevelHigh)
OpenSellOrder();
}
}
//+------------------------------------------------------------------+
//| 检查所有过滤器 |
//+------------------------------------------------------------------+
bool PassesFilters()
{
// --- 时段过滤 ---
if(UseSessionFilter && !IsInTradingSession())
return false;
// --- 点差过滤 ---
int currentSpread = (int)((Ask - Bid) / pipSize);
if(currentSpread > MaxSpread)
{
Print("点差过高:", currentSpread);
return false;
}
// --- 波动率过滤 ---
double atrVal[1];
if(CopyBuffer(atrHandle, 0, 1, 1, atrVal) < 1) return false;
double currentATR = atrVal[0] / pipSize;
if(currentATR < MinATR || currentATR > MaxATR)
{
Print("ATR超出范围:", currentATR);
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| 检测支撑/阻力区间 |
//+------------------------------------------------------------------+
void DetectZone()
{
int barsToCheck = ZoneLookback;
if(barsToCheck > iBars(Symbol(), PERIOD_H1)) barsToCheck = iBars(Symbol(), PERIOD_H1);
double high = 0, low = 999999;
for(int i = 2; i < barsToCheck; i++)
{
if(High[i] > high) high = High[i];
if(Low[i] < low) low = Low[i];
}
zoneHigh = high;
zoneLow = low;
zoneDefined = true;
}
//+------------------------------------------------------------------+
//| 检查当前时间是否在交易时段内 |
//+------------------------------------------------------------------+
bool IsInTradingSession()
{
datetime now = TimeCurrent();
MqlDateTime today;
TimeToStruct(now, today);
int currentHour = today.hour;
int currentMin = today.min;
if(currentHour > StartHour || (currentHour == StartHour && currentMin >= 0))
{
if(currentHour < EndHour || (currentHour == EndHour && currentMin <= 0))
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| 通过EMA判断趋势方向 |
//+------------------------------------------------------------------+
int GetTrendDirection()
{
double emaVal[1];
if(CopyBuffer(emaHandle, 0, 1, 1, emaVal) < 1) return 0;
double currentClose = Close[1];
if(currentClose > emaVal[0]) return 1; // 多头趋势
if(currentClose < emaVal[0]) return -1; // 空头趋势
return 0;
}
//+------------------------------------------------------------------+
//| 开多单 |
//+------------------------------------------------------------------+
void OpenBuyOrder()
{
double ask = Ask;
double sl = ask - (SLDistance pipSize);
double tp = ask + (TakeProfit pipSize);
double lot = CalculateLot();
int ticket = OrderSend(Symbol(), OP_BUY, lot, ask, Slippage, sl, tp, "Gold BreakRetest Buy", MagicNumber, 0, clrGreen);
if(ticket < 0)
Print("多单开仓失败,错误码:", GetLastError());
else
Print("多单开仓成功,订单号:", ticket);
}
//+------------------------------------------------------------------+
//| 开空单 |
//+------------------------------------------------------------------+
void OpenSellOrder()
{
double bid = Bid;
double sl = bid + (SLDistance pipSize);
double tp = bid - (TakeProfit pipSize);
double lot = CalculateLot();
int ticket = OrderSend(Symbol(), OP_SELL, lot, bid, Slippage, sl, tp, "Gold BreakRetest Sell", MagicNumber, 0, clrRed);
if(ticket < 0)
Print("空单开仓失败,错误码:", GetLastError());
else
Print("空单开仓成功,订单号:", ticket);
}
//+------------------------------------------------------------------+
//| 根据风险计算手数 |
//+------------------------------------------------------------------+
double CalculateLot()
{
if(!UseAutoLot)
return LotSize;
double riskAmount = AccountBalance() (RiskPercent / 100.0);
double stopLossDistance = SLDistance pipSize;
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double calculatedLot = riskAmount / (stopLossDistance MarketInfo(Symbol(), MODE_TICKVALUE));
calculatedLot = MathFloor(calculatedLot / lotStep) lotStep;
if(calculatedLot < minLot) calculatedLot = minLot;
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
if(calculatedLot > maxLot) calculatedLot = maxLot;
return NormalizeDouble(calculatedLot, 2);
}
//+------------------------------------------------------------------+
//| 管理现有持仓的追踪止损 |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
// 简单追踪止损:止盈距离的40%
int trailDistance = (int)(TakeProfit 0.4);
if(trailDistance < 30) trailDistance = 30;
if(OrderType() == OP_BUY)
{
double newStop = Bid - (trailDistance pipSize);
if(newStop > OrderStopLoss() + (10 pipSize))
{
if(OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrNONE))
Print("追踪止损更新至:", newStop);
}
}
else if(OrderType() == OP_SELL)
{
double newStop = Ask + (trailDistance pipSize);
if(newStop < OrderStopLoss() - (10 pipSize))
{
if(OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrNONE))
Print("追踪止损更新至:", newStop);
}
}
}
//+------------------------------------------------------------------+
``参考来源
本文首发于FXEAR.com,原创内容,未经授权禁止转载。
免责声明: 外汇及商品交易具有高风险。本文提供的EA源码仅供教育和研究参考使用,过往表现不代表未来结果。用户需对自身交易决策承担全部责任,并应在模拟账户上充分测试后再应用于实盘账户。