//+------------------------------------------------------------------+ //| 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 #include //--- 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); } //+------------------------------------------------------------------+