Files
mymt5opp/GoldScalperPro.mq5
2026-06-26 20:50:07 +08:00

581 lines
23 KiB
Plaintext

//+------------------------------------------------------------------+
//| GoldScalperPro.mq5 |
//| |
//| A dedicated XAUUSD (gold) scalping Expert Advisor. |
//| |
//| Strategy (trend-filtered momentum pullback) |
//| ------------------------------------------ |
//| 1. A higher/slower EMA defines the prevailing trend, so the |
//| EA only ever trades WITH the dominant direction. |
//| 2. Inside that trend it waits for a short pullback: price |
//| dips back to the fast EMA and RSI leaves an oversold |
//| (long) / overbought (short) extreme - i.e. it buys dips in |
//| an uptrend and sells rallies in a downtrend. |
//| 3. An ATR filter makes sure there is enough volatility to pay |
//| for the spread, and an ATR-based stop/target adapts the |
//| trade size to current gold volatility. |
//| 4. Position size is derived from a fixed % risk of equity, so |
//| a small account never over-leverages on a single trade. |
//| 5. Hard daily-loss and daily-profit circuit breakers, a max |
//| trades-per-day cap, a spread guard and a trading-session |
//| window keep the scalper out of bad conditions. |
//| |
//| This EA is completely independent of any other strategy and |
//| manages only its own orders (identified by the magic number). |
//| |
//| All times are broker/server time. |
//+------------------------------------------------------------------+
#property copyright "Sam Watts"
#property version "1.00"
#property strict
#property description "Trend-filtered momentum pullback scalper for XAUUSD (gold)."
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
//--- Position sizing mode
enum ENUM_SIZING_MODE
{
SIZE_FIXED_LOT, // Fixed lot size
SIZE_RISK_PERCENT // Risk a % of equity per trade
};
//--- Stop loss / take profit calculation mode
enum ENUM_STOP_MODE
{
STOP_ATR, // ATR multiple (adapts to volatility)
STOP_POINTS // Fixed distance in points
};
//+------------------------------------------------------------------+
//| Inputs |
//+------------------------------------------------------------------+
input group "=== 策略 / 信号 ==="
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M5; // 工作时间框架
input int InpFastEmaPeriod = 21; // 快速EMA (回调价位)
input int InpSlowEmaPeriod = 100; // 慢速EMA (趋势过滤器)
input int InpRsiPeriod = 14; // RSI周期
input double InpRsiBuyLevel = 45.0; // 当RSI回升至该值上方时买入
input double InpRsiSellLevel = 55.0; // 当RSI跌破该值下方时卖出
input double InpPullbackAtrMult = 2.0; // 价格与快速EMA的最大允许距离 (x ATR)
input group "=== 波动性 / 过滤器 ==="
input int InpAtrPeriod = 14; // ATR周期
input int InpMinAtrPoints = 0; // ATR低于此值时跳过 (点数, 0 = 忽略)
input double InpMaxSpreadAtrPct = 25.0; // 最大价差占ATR百分比 (0 = 忽略)
input group "=== 仓位计算 ==="
input ENUM_SIZING_MODE InpSizingMode = SIZE_RISK_PERCENT; // 仓位计算方式
input double InpFixedLots = 0.01; // 固定手数 (固定手数模式)
input double InpRiskPercent = 1.0; // 每笔交易风险百分比 (账户权益)
input group "=== 止损 / 止盈 ==="
input ENUM_STOP_MODE InpStopMode = STOP_ATR; // 止损/止盈计算模式
input double InpAtrSLMult = 1.5; // 止损 = ATR x 此值
input double InpAtrTPMult = 2.0; // 止盈 = ATR x 此值
input int InpStopLossPoints = 200; // 止损 (点数, 固定模式)
input int InpTakeProfitPoints = 300; // 止盈 (点数, 固定模式)
input bool InpUseBreakEven = true; // 移动止损到盈亏平衡点
input int InpBreakEvenPoints = 150; // 触发盈亏平衡的利润点数
input int InpBreakEvenLock = 20; // 盈亏平衡时锁定的点数
input bool InpUseTrailing = true; // 使用移动止损
input int InpTrailStartPoints = 200; // 开始移动止损的利润点数
input int InpTrailStepPoints = 120; // 移动止损距离 (点数)
input group "=== 交易控制 / 风险限制 ==="
input int InpMaxPositions = 1; // 最大持仓数 (本EA)
input int InpMaxTradesPerDay = 6; // 每日最大交易次数 (0 = 不限制)
input double InpDailyLossLimit = 5.0; // 亏损达到此 equity百分比时停止交易 (0 = 关闭)
input double InpDailyProfitTarget = 0.0; // 盈利达到此equity百分比时停止交易 (0 = 关闭)
input int InpMinSecondsBetween = 60; // 最小交易间隔秒数
input group "=== 交易时段 (服务器时间) ==="
input bool InpUseSession = true; // 限制交易时段
input int InpSessionStartHour = 7; // 时段开始小时 (0-23)
input int InpSessionEndHour = 20; // 时段结束小时 (0-23)
input group "=== 常规 ==="
input long InpMagicNumber = 20240530; // 魔术号码
input string InpComment = "GoldScalperPro"; // 订单注释
//+------------------------------------------------------------------+
//| Globals |
//+------------------------------------------------------------------+
CTrade trade;
CPositionInfo posInfo;
int g_fastEmaHandle = INVALID_HANDLE;
int g_slowEmaHandle = INVALID_HANDLE;
int g_rsiHandle = INVALID_HANDLE;
int g_atrHandle = INVALID_HANDLE;
datetime g_lastBarTime = 0; // last processed bar of the working timeframe
datetime g_currentDay = 0; // day (00:00) the daily counters belong to
datetime g_lastTradeTime = 0; // time of the last entry
int g_tradesToday = 0; // entries opened today
double g_dayStartEquity = 0.0; // equity at the start of the trading day
bool g_dayBlocked = false; // daily circuit breaker tripped
//+------------------------------------------------------------------+
//| Expert initialization |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetTypeFillingBySymbol(_Symbol);
trade.SetDeviationInPoints(20);
if(InpFastEmaPeriod <= 0 || InpSlowEmaPeriod <= 0 ||
InpFastEmaPeriod >= InpSlowEmaPeriod)
{
Print("Fast EMA period must be > 0 and smaller than the slow EMA period.");
return(INIT_PARAMETERS_INCORRECT);
}
if(InpRsiPeriod <= 0 || InpAtrPeriod <= 0)
{
Print("RSI and ATR periods must be greater than zero.");
return(INIT_PARAMETERS_INCORRECT);
}
if(InpSizingMode == SIZE_FIXED_LOT && InpFixedLots <= 0.0)
{
Print("Fixed lot size must be greater than zero.");
return(INIT_PARAMETERS_INCORRECT);
}
if(InpSizingMode == SIZE_RISK_PERCENT && InpRiskPercent <= 0.0)
{
Print("Risk percent must be greater than zero.");
return(INIT_PARAMETERS_INCORRECT);
}
g_fastEmaHandle = iMA(_Symbol, InpTimeframe, InpFastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_slowEmaHandle = iMA(_Symbol, InpTimeframe, InpSlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_rsiHandle = iRSI(_Symbol, InpTimeframe, InpRsiPeriod, PRICE_CLOSE);
g_atrHandle = iATR(_Symbol, InpTimeframe, InpAtrPeriod);
if(g_fastEmaHandle == INVALID_HANDLE || g_slowEmaHandle == INVALID_HANDLE ||
g_rsiHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
{
Print("Failed to create one or more indicator handles.");
return(INIT_FAILED);
}
ResetDailyCounters(DayStart(TimeCurrent()));
PrintFormat("GoldScalperPro initialised on %s (%s) | magic %I64d",
_Symbol, EnumToString(InpTimeframe), InpMagicNumber);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_fastEmaHandle != INVALID_HANDLE) IndicatorRelease(g_fastEmaHandle);
if(g_slowEmaHandle != INVALID_HANDLE) IndicatorRelease(g_slowEmaHandle);
if(g_rsiHandle != INVALID_HANDLE) IndicatorRelease(g_rsiHandle);
if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
Comment("");
}
//+------------------------------------------------------------------+
//| Expert tick |
//+------------------------------------------------------------------+
void OnTick()
{
datetime now = TimeCurrent();
//--- New trading day: reset the daily counters / circuit breaker.
datetime today = DayStart(now);
if(today != g_currentDay)
ResetDailyCounters(today);
//--- Manage what is already open on every tick (responsive exits).
ManageOpenPositions();
//--- Trip / hold the daily circuit breaker.
CheckDailyLimits();
//--- Only evaluate fresh signals once per closed bar.
datetime barTime = (datetime)SeriesInfoInteger(_Symbol, InpTimeframe, SERIES_LASTBAR_DATE);
if(barTime == g_lastBarTime)
{
UpdateDashboard();
return;
}
g_lastBarTime = barTime;
EvaluateEntry();
UpdateDashboard();
}
//+------------------------------------------------------------------+
//| Reset the per-day counters and snapshot starting equity |
//+------------------------------------------------------------------+
void ResetDailyCounters(const datetime today)
{
g_currentDay = today;
g_tradesToday = 0;
g_dayBlocked = false;
g_dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
}
//+------------------------------------------------------------------+
//| Daily loss / profit circuit breaker |
//+------------------------------------------------------------------+
void CheckDailyLimits()
{
if(g_dayBlocked)
return;
if(g_dayStartEquity <= 0.0)
return;
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double pct = (equity - g_dayStartEquity) / g_dayStartEquity * 100.0;
if(InpDailyLossLimit > 0.0 && pct <= -InpDailyLossLimit)
{
g_dayBlocked = true;
PrintFormat("Daily loss limit hit (%.2f%%). Trading halted for the day.", pct);
}
else if(InpDailyProfitTarget > 0.0 && pct >= InpDailyProfitTarget)
{
g_dayBlocked = true;
PrintFormat("Daily profit target hit (%.2f%%). Trading halted for the day.", pct);
}
}
//+------------------------------------------------------------------+
//| Evaluate the entry signal on the latest closed bar |
//+------------------------------------------------------------------+
void EvaluateEntry()
{
//--- Respect all the gates before doing any work.
if(g_dayBlocked)
return;
if(InpUseSession && !InSession())
return;
if(InpMaxTradesPerDay > 0 && g_tradesToday >= InpMaxTradesPerDay)
return;
if(CountOpenPositions() >= InpMaxPositions)
return;
if(g_lastTradeTime > 0 && (TimeCurrent() - g_lastTradeTime) < InpMinSecondsBetween)
return;
//--- Pull indicator values for the just-closed bar and the previous one
//--- so we can detect an RSI cross. Arrays are set as time-series, so
//--- index 0 = most recent (shift 1) and index 1 = the bar before it.
double fastEma[], slowEma[], rsi[], atr[];
ArraySetAsSeries(fastEma, true);
ArraySetAsSeries(slowEma, true);
ArraySetAsSeries(rsi, true);
ArraySetAsSeries(atr, true);
if(CopyBuffer(g_fastEmaHandle, 0, 1, 2, fastEma) < 2) return;
if(CopyBuffer(g_slowEmaHandle, 0, 1, 2, slowEma) < 2) return;
if(CopyBuffer(g_rsiHandle, 0, 1, 2, rsi) < 2) return;
if(CopyBuffer(g_atrHandle, 0, 1, 1, atr) < 1) return;
double atrNow = atr[0];
double fastNow = fastEma[0];
double slowNow = slowEma[0];
double rsiNow = rsi[0]; // last closed bar
double rsiPrev = rsi[1]; // the bar before it
if(atrNow <= 0.0)
return;
if(InpMinAtrPoints > 0 && (atrNow / _Point) < InpMinAtrPoints)
return;
if(!SpreadOK(atrNow))
return;
double closePrice = iClose(_Symbol, InpTimeframe, 1);
if(closePrice <= 0.0)
return;
bool trendUp = (fastNow > slowNow) && (closePrice > slowNow);
bool trendDown = (fastNow < slowNow) && (closePrice < slowNow);
//--- How far price has pulled back from the fast EMA, measured in ATR so
//--- it is independent of the symbol's digits / point size.
double distToFast = MathAbs(closePrice - fastNow);
bool nearFast = (distToFast <= InpPullbackAtrMult * atrNow);
//--- Long: uptrend, price near the fast EMA, RSI turning back UP through
//--- the buy level (momentum returning after a dip).
bool buySignal = trendUp && nearFast &&
rsiPrev < InpRsiBuyLevel && rsiNow >= InpRsiBuyLevel;
//--- Short: downtrend, price near the fast EMA, RSI turning back DOWN
//--- through the sell level.
bool sellSignal = trendDown && nearFast &&
rsiPrev > InpRsiSellLevel && rsiNow <= InpRsiSellLevel;
if(buySignal)
OpenTrade(ORDER_TYPE_BUY, atrNow);
else if(sellSignal)
OpenTrade(ORDER_TYPE_SELL, atrNow);
}
//+------------------------------------------------------------------+
//| Open a market order with ATR/points based SL & TP |
//+------------------------------------------------------------------+
void OpenTrade(const ENUM_ORDER_TYPE type, const double atrValue)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double slDist = StopDistance(InpStopMode == STOP_ATR ? InpAtrSLMult : InpStopLossPoints, atrValue);
double tpDist = StopDistance(InpStopMode == STOP_ATR ? InpAtrTPMult : InpTakeProfitPoints, atrValue);
//--- Respect the broker's minimum stop distance.
double minStop = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
if(slDist < minStop) slDist = minStop;
if(tpDist < minStop) tpDist = minStop;
if(slDist <= 0.0)
{
Print("Computed stop distance is zero - aborting entry.");
return;
}
double price = (type == ORDER_TYPE_BUY) ? ask : bid;
double sl, tp;
if(type == ORDER_TYPE_BUY)
{
sl = NormalizeDouble(price - slDist, _Digits);
tp = NormalizeDouble(price + tpDist, _Digits);
}
else
{
sl = NormalizeDouble(price + slDist, _Digits);
tp = NormalizeDouble(price - tpDist, _Digits);
}
double lots = CalcLots(slDist);
if(lots <= 0.0)
{
Print("Computed lot size is zero - aborting entry.");
return;
}
bool ok = (type == ORDER_TYPE_BUY)
? trade.Buy(lots, _Symbol, price, sl, tp, InpComment)
: trade.Sell(lots, _Symbol, price, sl, tp, InpComment);
if(ok)
{
g_tradesToday++;
g_lastTradeTime = TimeCurrent();
PrintFormat("%s %.2f lots @ %.*f SL %.*f TP %.*f (trade %d/%d today)",
(type == ORDER_TYPE_BUY ? "BUY" : "SELL"), lots,
_Digits, price, _Digits, sl, _Digits, tp,
g_tradesToday, InpMaxTradesPerDay);
}
else
PrintFormat("Order failed: %d - %s",
trade.ResultRetcode(), trade.ResultRetcodeDescription());
}
//+------------------------------------------------------------------+
//| Convert an SL/TP setting to a price distance |
//+------------------------------------------------------------------+
double StopDistance(const double value, const double atrValue)
{
if(value <= 0.0)
return(0.0);
if(InpStopMode == STOP_ATR)
return(value * atrValue);
return(value * _Point); // STOP_POINTS
}
//+------------------------------------------------------------------+
//| Position size from fixed lot or % risk of equity |
//+------------------------------------------------------------------+
double CalcLots(const double slDistance)
{
if(InpSizingMode == SIZE_FIXED_LOT)
return(NormalizeLots(InpFixedLots));
//--- Risk-percent sizing: lots = riskMoney / (slDistance valued per lot).
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskMoney = equity * InpRiskPercent / 100.0;
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickValue <= 0.0 || tickSize <= 0.0)
return(NormalizeLots(InpFixedLots));
double lossPerLot = slDistance / tickSize * tickValue;
if(lossPerLot <= 0.0)
return(NormalizeLots(InpFixedLots));
double lots = riskMoney / lossPerLot;
return(NormalizeLots(lots));
}
//+------------------------------------------------------------------+
//| Break-even and trailing-stop management for our positions |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
if(!InpUseBreakEven && !InpUseTrailing)
return;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(!posInfo.SelectByTicket(ticket))
continue;
if(posInfo.Symbol() != _Symbol || posInfo.Magic() != InpMagicNumber)
continue;
long type = posInfo.PositionType();
double openPrice = posInfo.PriceOpen();
double curSL = posInfo.StopLoss();
double curTP = posInfo.TakeProfit();
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double newSL = curSL;
if(type == POSITION_TYPE_BUY)
{
double profitPts = (bid - openPrice) / _Point;
if(InpUseBreakEven && profitPts >= InpBreakEvenPoints)
{
double be = NormalizeDouble(openPrice + InpBreakEvenLock * _Point, _Digits);
if(be > newSL)
newSL = be;
}
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
{
double trail = NormalizeDouble(bid - InpTrailStepPoints * _Point, _Digits);
if(trail > newSL)
newSL = trail;
}
if(newSL > curSL && newSL < bid)
trade.PositionModify(ticket, newSL, curTP);
}
else if(type == POSITION_TYPE_SELL)
{
double profitPts = (openPrice - ask) / _Point;
if(InpUseBreakEven && profitPts >= InpBreakEvenPoints)
{
double be = NormalizeDouble(openPrice - InpBreakEvenLock * _Point, _Digits);
if(curSL == 0.0 || be < newSL)
newSL = be;
}
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
{
double trail = NormalizeDouble(ask + InpTrailStepPoints * _Point, _Digits);
if(curSL == 0.0 || trail < newSL)
newSL = trail;
}
if(newSL != curSL && (curSL == 0.0 || newSL < curSL) && newSL > ask)
trade.PositionModify(ticket, newSL, curTP);
}
}
}
//+------------------------------------------------------------------+
//| Count this EA's open positions on this symbol |
//+------------------------------------------------------------------+
int CountOpenPositions()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(!posInfo.SelectByTicket(ticket))
continue;
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber)
count++;
}
return(count);
}
//+------------------------------------------------------------------+
//| True while the clock is inside the trading session window |
//+------------------------------------------------------------------+
bool InSession()
{
MqlDateTime st;
TimeToStruct(TimeCurrent(), st);
int hour = st.hour;
if(InpSessionStartHour == InpSessionEndHour)
return(true); // 24h
if(InpSessionStartHour < InpSessionEndHour)
return(hour >= InpSessionStartHour && hour < InpSessionEndHour);
//--- window that wraps past midnight
return(hour >= InpSessionStartHour || hour < InpSessionEndHour);
}
//+------------------------------------------------------------------+
//| Spread check (relative to ATR, so it works on any gold symbol) |
//+------------------------------------------------------------------+
bool SpreadOK(const double atrValue)
{
if(InpMaxSpreadAtrPct <= 0.0 || atrValue <= 0.0)
return(true);
double spreadPrice = (double)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point;
return(spreadPrice <= atrValue * InpMaxSpreadAtrPct / 100.0);
}
//+------------------------------------------------------------------+
//| Normalize lots to the symbol's volume constraints |
//+------------------------------------------------------------------+
double NormalizeLots(double lots)
{
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(lotStep > 0.0)
lots = MathFloor(lots / lotStep) * lotStep;
if(lots < minLot) lots = minLot;
if(lots > maxLot) lots = maxLot;
return(lots);
}
//+------------------------------------------------------------------+
//| Midnight (00:00) of the day a timestamp belongs to |
//+------------------------------------------------------------------+
datetime DayStart(const datetime t)
{
return(t - (t % 86400));
}
//+------------------------------------------------------------------+
//| On-chart status read-out |
//+------------------------------------------------------------------+
void UpdateDashboard()
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double dayPct = (g_dayStartEquity > 0.0)
? (equity - g_dayStartEquity) / g_dayStartEquity * 100.0 : 0.0;
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
string state = g_dayBlocked ? "HALTED (daily limit)"
: (InpUseSession && !InSession()) ? "outside session" : "active";
string txt = StringFormat(
"GoldScalperPro [%s %s]\n"
"State: %s\n"
"Open positions: %d / %d\n"
"Trades today: %d / %d\n"
"Day P/L: %.2f%%\n"
"Spread: %d pts",
_Symbol, EnumToString(InpTimeframe),
state, CountOpenPositions(), InpMaxPositions,
g_tradesToday, InpMaxTradesPerDay,
dayPct, (int)spread);
Comment(txt);
}
//+------------------------------------------------------------------+