From 99ae15560ae64cc40afe99f8751f1348f2b60659 Mon Sep 17 00:00:00 2001 From: kingstonebridge2032 Date: Sat, 23 May 2026 20:26:44 +0300 Subject: [PATCH] Update InstitutionalTickHybridNewsFilter.mq5 --- InstitutionalTickHybridNewsFilter.mq5 | 1158 ++++++++++++++++--------- 1 file changed, 731 insertions(+), 427 deletions(-) diff --git a/InstitutionalTickHybridNewsFilter.mq5 b/InstitutionalTickHybridNewsFilter.mq5 index 4f06867..815cd57 100644 --- a/InstitutionalTickHybridNewsFilter.mq5 +++ b/InstitutionalTickHybridNewsFilter.mq5 @@ -1,503 +1,807 @@ //+------------------------------------------------------------------+ -//| InstitutionalTickHybridEA.mq5 | -//| FIXED VERSION + HIGH IMPACT NEWS FILTER | +//| PhantomEdge_TickScalper.mq5 | +//| Ultra-Fast Tick Scalper for Cent Accounts | +//| | +//| PHILOSOPHY: | +//| - Works on TICKS not bars — reacts to every price movement | +//| - Mean reversion on tick RETURNS (not raw prices) | +//| - Micro-VWAP as dynamic fair value anchor | +//| - Ultra-tight risk: equity-based lots, circuit breaker, session | +//| - News-aware: blocks new entries but NEVER closes winning trades | +//| - Aggressive entries, surgical exits | //+------------------------------------------------------------------+ +#property copyright "PhantomEdge" +#property version "1.00" #property strict -#property version "1.20" -#include +#include -CTrade trade; +//+------------------------------------------------------------------+ +//| INPUT PARAMETERS | +//+------------------------------------------------------------------+ -//==================================================== -// INPUTS -//==================================================== -input ulong MagicNumber = 880001; +//--- Core Strategy +input group "=== CORE STRATEGY ===" +input int TickBufferSize = 60; // Tick buffer size (number of ticks) +input double ZScoreEntry = 2.0; // Z-Score threshold to enter (on returns) +input double ZScoreExit = 0.3; // Z-Score threshold to exit (mean reached) +input int VWAPWindowTicks = 120; // Micro-VWAP lookback (ticks) +input bool UseVWAPConfirm = true; // Require VWAP confirmation for entry -input double ForexLot = 0.01; -input double GoldLot = 0.01; +//--- Risk Management +input group "=== RISK MANAGEMENT ===" +input double RiskPercentPerTrade = 1.0; // Risk % of equity per trade +input double MaxDailyDrawdownPct = 5.0; // Max daily drawdown % (circuit breaker) +input double MaxTotalDrawdownPct = 15.0; // Max total drawdown % from peak equity +input int MaxPositionsPerSymbol= 1; // Max positions per symbol (keep it 1!) +input double MaxLotSize = 0.50; // Maximum lot size cap +input double MinLotSize = 0.01; // Minimum lot size -input int EMAFastPeriod = 20; -input int EMATrendPeriod = 200; -input int ATRPeriod = 14; -input double ATRMultiplierSL = 2.0; -input double ATRTrailingMultiplier = 1.5; +//--- Stop Loss & Take Profit +input group "=== SL/TP & TRAILING ===" +input double SL_ATR_Multiplier = 1.5; // SL = ATR × this (tight for scalping) +input double TP_RR_Ratio = 2.0; // TP = SL distance × this (risk:reward) +input double TrailingATR_Mult = 1.0; // Trailing stop = ATR × this +input double BreakevenATR_Mult = 0.8; // Move SL to breakeven at ATR × this profit +input int ATR_Period = 14; // ATR period +input ENUM_TIMEFRAMES ATR_Timeframe = PERIOD_M1; // ATR timeframe -input int TickBufferSize = 30; -input double ZScoreThreshold = 1.3; -input int CooldownSeconds = 10; -input int MaxPositionsPerSymbol = 3; +//--- Spread & Volatility Filters +input group "=== FILTERS ===" +input int MaxSpreadPoints = 25; // Max allowed spread (points) +input double MinATR_Filter = 0.0; // Min ATR to trade (0 = auto-detect) +input bool UseSessionFilter = true; // Filter by trading session +input int SessionStartHour = 7; // Session start (server hour, London open) +input int SessionEndHour = 20; // Session end (server hour, NY close) -input double MaxSpreadPoints = 40; -input double MinATRPointsForex = 15; -input double MinATRPointsGold = 150; +//--- News Filter +input group "=== NEWS FILTER ===" +input bool UseNewsFilter = true; // Enable news filter +input int NewsMinutesBefore = 30; // Minutes before news to stop entries +input int NewsMinutesAfter = 30; // Minutes after news to stop entries +input bool NewsClosePositions = false; // Close positions on news? (false = safer) -input double ProfitTargetPerLot = 20.0; -input bool UseBreakeven = true; -input double BreakevenOffsetPoints = 10; -input bool EnableDebug = true; +//--- Trade Settings +input group "=== TRADE SETTINGS ===" +input int MagicNumber = 777888; // Magic number +input int TradeSlippage = 10; // Max slippage (points) +input string TradeComment = "PhantomEdge"; // Trade comment +input int CooldownSeconds = 5; // Seconds between trades -//==================================================== -// NEWS FILTER -//==================================================== -input bool UseNewsFilter = true; -input int NewsPauseBeforeMin = 60; -input int NewsPauseAfterMin = 60; +//+------------------------------------------------------------------+ +//| GLOBAL VARIABLES | +//+------------------------------------------------------------------+ +CTrade trade; -//==================================================== -// GLOBALS -//==================================================== -double TickBuffer[]; -datetime LastTradeTime = 0; +// Tick data buffers +double TickPrices[]; // Raw tick prices (mid) +double TickReturns[]; // Log returns between ticks +double TickVolumes[]; // Tick volumes for VWAP +int TickCount = 0; // How many ticks we've collected +bool BufferReady = false; // Is buffer fully populated? -int emaFastHandle; -int emaTrendHandle; -int atrHandle; +// VWAP data +double VWAPPrices[]; +double VWAPVolumes[]; +int VWAPCount = 0; -//==================================================== -// INIT -//==================================================== +// State tracking +datetime LastTradeTime = 0; +double DayStartEquity = 0; +double PeakEquity = 0; +bool DailyCircuitBreaker = false; +bool TotalCircuitBreaker = false; +datetime LastDay = 0; +datetime LastTickTime = 0; + +// ATR handle +int ATR_Handle = INVALID_HANDLE; + +//+------------------------------------------------------------------+ +//| Expert initialization | +//+------------------------------------------------------------------+ int OnInit() { - ArrayResize(TickBuffer, TickBufferSize); - - emaFastHandle = iMA(_Symbol,_Period,EMAFastPeriod,0,MODE_EMA,PRICE_CLOSE); - emaTrendHandle = iMA(_Symbol,_Period,EMATrendPeriod,0,MODE_EMA,PRICE_CLOSE); - atrHandle = iATR(_Symbol,_Period,ATRPeriod); - - if(emaFastHandle==INVALID_HANDLE || emaTrendHandle==INVALID_HANDLE || atrHandle==INVALID_HANDLE) + // Setup trade object + trade.SetExpertMagicNumber(MagicNumber); + trade.SetDeviationInPoints(TradeSlippage); + trade.SetTypeFilling(ORDER_FILLING_IOC); + + // Initialize tick buffers + ArrayResize(TickPrices, TickBufferSize); + ArrayResize(TickReturns, TickBufferSize); + ArrayResize(TickVolumes, TickBufferSize); + ArrayInitialize(TickPrices, 0); + ArrayInitialize(TickReturns, 0); + ArrayInitialize(TickVolumes, 0); + + // Initialize VWAP buffers + ArrayResize(VWAPPrices, VWAPWindowTicks); + ArrayResize(VWAPVolumes, VWAPWindowTicks); + ArrayInitialize(VWAPPrices, 0); + ArrayInitialize(VWAPVolumes, 0); + + // Create ATR indicator + ATR_Handle = iATR(_Symbol, ATR_Timeframe, ATR_Period); + if(ATR_Handle == INVALID_HANDLE) { - Print("Indicator init failed"); + Print("ERROR: Failed to create ATR indicator"); return INIT_FAILED; } - - Print("EA WITH NEWS FILTER INITIALIZED"); + + // Initialize equity tracking + DayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); + PeakEquity = DayStartEquity; + LastDay = TimeCurrent(); + + Print("=== PhantomEdge TickScalper Initialized ==="); + Print("Account Balance: ", AccountInfoDouble(ACCOUNT_BALANCE)); + Print("Account Equity: ", AccountInfoDouble(ACCOUNT_EQUITY)); + Print("Symbol: ", _Symbol, " | Digits: ", _Digits); + Print("Tick Buffer: ", TickBufferSize, " | Z-Entry: ", ZScoreEntry); + Print("Risk per trade: ", RiskPercentPerTrade, "% | Max Daily DD: ", MaxDailyDrawdownPct, "%"); + return INIT_SUCCEEDED; } -//==================================================== -// DEINIT -//==================================================== +//+------------------------------------------------------------------+ +//| Expert deinitialization | +//+------------------------------------------------------------------+ void OnDeinit(const int reason) { - IndicatorRelease(emaFastHandle); - IndicatorRelease(emaTrendHandle); - IndicatorRelease(atrHandle); + if(ATR_Handle != INVALID_HANDLE) + IndicatorRelease(ATR_Handle); + + Print("=== PhantomEdge TickScalper Deinitialized ==="); } -//==================================================== -// ON TICK -//==================================================== +//+------------------------------------------------------------------+ +//| Expert tick function — THE HEART OF THE EA | +//+------------------------------------------------------------------+ void OnTick() { - UpdateTickBuffer(); - - //================================================ - // HIGH IMPACT NEWS FILTER - //================================================ - if(UseNewsFilter && IsHighImpactNewsTime()) + //--- Process ALL ticks since last call using CopyTicks + MqlTick ticks[]; + int copied = CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, 0, 200); + + if(copied <= 0) return; + + // Process only new ticks (after our last processed time) + for(int i = 0; i < copied; i++) { - CloseAllPositions(); - Debug("Trading paused due to HIGH IMPACT NEWS"); + if(ticks[i].time_msc <= LastTickTime && LastTickTime != 0) continue; + + LastTickTime = ticks[i].time_msc; + double midPrice = (ticks[i].ask + ticks[i].bid) / 2.0; + double tickVol = (double)ticks[i].volume; + if(tickVol < 1) tickVol = 1; + + ProcessTick(midPrice, tickVol); + } + + //--- Check daily reset + CheckDailyReset(); + + //--- Circuit breaker check + UpdateCircuitBreakers(); + if(DailyCircuitBreaker || TotalCircuitBreaker) + { + ManageOpenPositions(); // Still manage exits return; } + + //--- Manage existing positions (trailing, breakeven, z-score exit) + ManageOpenPositions(); + + //--- Check if buffer is ready + if(!BufferReady) return; + + //--- Check all filters before entry + if(!PassesAllFilters()) return; + + //--- Check cooldown + if(TimeCurrent() - LastTradeTime < CooldownSeconds) return; + + //--- Check max positions + if(CountMyPositions() >= MaxPositionsPerSymbol) return; + + //--- ENTRY LOGIC + EvaluateEntry(); +} - ManagePositions(); +//+------------------------------------------------------------------+ +//| Process a single tick into our buffers | +//+------------------------------------------------------------------+ +void ProcessTick(double price, double volume) +{ + // Shift buffer left (oldest drops off) + for(int i = 0; i < TickBufferSize - 1; i++) + { + TickPrices[i] = TickPrices[i + 1]; + TickReturns[i] = TickReturns[i + 1]; + TickVolumes[i] = TickVolumes[i + 1]; + } + + // Add new tick + TickPrices[TickBufferSize - 1] = price; + TickVolumes[TickBufferSize - 1] = volume; + + // Calculate log return (if we have a previous price) + if(TickPrices[TickBufferSize - 2] > 0) + TickReturns[TickBufferSize - 1] = MathLog(price / TickPrices[TickBufferSize - 2]); + else + TickReturns[TickBufferSize - 1] = 0; + + TickCount++; + + // Mark buffer as ready only after it's fully populated with real data + if(TickCount >= TickBufferSize + 1) + BufferReady = true; + + // VWAP buffer + for(int i = 0; i < VWAPWindowTicks - 1; i++) + { + VWAPPrices[i] = VWAPPrices[i + 1]; + VWAPVolumes[i] = VWAPVolumes[i + 1]; + } + VWAPPrices[VWAPWindowTicks - 1] = price; + VWAPVolumes[VWAPWindowTicks - 1] = volume; + VWAPCount++; +} - if(!CanTrade()) - return; +//+------------------------------------------------------------------+ +//| Calculate Z-Score on RETURNS (not raw prices!) | +//+------------------------------------------------------------------+ +double CalcZScore() +{ + // Calculate mean of returns + double sum = 0; + for(int i = 0; i < TickBufferSize; i++) + sum += TickReturns[i]; + double mean = sum / TickBufferSize; + + // Calculate standard deviation of returns + double sumSqDev = 0; + for(int i = 0; i < TickBufferSize; i++) + { + double dev = TickReturns[i] - mean; + sumSqDev += dev * dev; + } + double stdev = MathSqrt(sumSqDev / (TickBufferSize - 1)); // Sample stdev (N-1) + + if(stdev < 1e-12) return 0; // Avoid division by zero + + // Z-score of the LATEST return + double zScore = (TickReturns[TickBufferSize - 1] - mean) / stdev; + + return zScore; +} - if(CountPositionsBySymbol() >= MaxPositionsPerSymbol) - return; +//+------------------------------------------------------------------+ +//| Calculate Micro-VWAP (Volume-Weighted Average Price) | +//+------------------------------------------------------------------+ +double CalcMicroVWAP() +{ + if(VWAPCount < VWAPWindowTicks) return 0; + + double sumPV = 0; + double sumV = 0; + + for(int i = 0; i < VWAPWindowTicks; i++) + { + if(VWAPPrices[i] <= 0) return 0; + sumPV += VWAPPrices[i] * VWAPVolumes[i]; + sumV += VWAPVolumes[i]; + } + + if(sumV < 1) return 0; + return sumPV / sumV; +} - double z = CalculateZScore(); - double emaFast = GetEMA(emaFastHandle); - double emaTrend = GetEMA(emaTrendHandle); +//+------------------------------------------------------------------+ +//| Calculate cumulative Z-Score (multi-tick momentum) | +//+------------------------------------------------------------------+ +double CalcCumulativeZScore(int lookback) +{ + if(lookback > TickBufferSize) lookback = TickBufferSize; + + // Sum of recent returns (cumulative move) + double cumReturn = 0; + for(int i = TickBufferSize - lookback; i < TickBufferSize; i++) + cumReturn += TickReturns[i]; + + // Mean and stdev of individual returns for scaling + double sum = 0; + for(int i = 0; i < TickBufferSize; i++) + sum += TickReturns[i]; + double mean = sum / TickBufferSize; + + double sumSqDev = 0; + for(int i = 0; i < TickBufferSize; i++) + { + double dev = TickReturns[i] - mean; + sumSqDev += dev * dev; + } + double stdev = MathSqrt(sumSqDev / (TickBufferSize - 1)); + + if(stdev < 1e-12) return 0; + + // Scale cumulative return by expected stdev of sum + double expectedStdev = stdev * MathSqrt((double)lookback); + return (cumReturn - mean * lookback) / expectedStdev; +} + +//+------------------------------------------------------------------+ +//| ENTRY EVALUATION — The money-maker | +//+------------------------------------------------------------------+ +void EvaluateEntry() +{ + double zScore = CalcZScore(); + double cumZ = CalcCumulativeZScore(10); // 10-tick momentum check + double vwap = CalcMicroVWAP(); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double mid = (ask + bid) / 2.0; + + // Get ATR for SL/TP calculation double atr = GetATR(); - - double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); - - bool bullish = bid > emaTrend; - bool bearish = bid < emaTrend; - - bool reversalBuy = (z < -1.0); - bool reversalSell = (z > 1.0); - - if(bullish && reversalBuy) - OpenBuy(atr); - - if(bearish && reversalSell) - OpenSell(atr); -} - -//==================================================== -// NEWS FILTER FUNCTION -//==================================================== -//==================================================== -// NEWS FILTER FUNCTION -//==================================================== -bool IsHighImpactNewsTime() -{ - MqlCalendarValue values[]; - - datetime now = TimeCurrent(); - datetime from = now - 60; - datetime to = now + (NewsPauseBeforeMin * 60); - - int count = CalendarValueHistory(values, from, to); - - if(count <= 0) - return false; - - string symbolCurrency1 = StringSubstr(_Symbol,0,3); - string symbolCurrency2 = StringSubstr(_Symbol,3,3); - - for(int i=0; i 0) + buySignal = buySignal && (mid < vwap); // Price below fair value + + //=== SELL SIGNAL === + // Tick returns show extreme positive deviation (price spiked up) + // AND cumulative momentum confirms the spike + // AND price is above VWAP (overvalued) — mean reversion SELL + bool sellSignal = (zScore >= ZScoreEntry) && (cumZ >= 1.0); + + if(UseVWAPConfirm && vwap > 0) + sellSignal = sellSignal && (mid > vwap); // Price above fair value + + //=== EXECUTE === + if(buySignal) { - MqlCalendarEvent event; - - if(!CalendarEventById(values[i].event_id,event)) - continue; - - // HIGH IMPACT ONLY - if(event.importance != CALENDAR_IMPORTANCE_HIGH) - continue; - - //================================================ - // FIXED CURRENCY ACCESS - //================================================ - MqlCalendarCountry country; - - if(!CalendarCountryById(event.country_id,country)) - continue; - - string currency = country.currency; - - bool relevant = false; - - // GOLD -> USD NEWS ONLY - if(StringFind(_Symbol,"XAU") >= 0 || - StringFind(_Symbol,"GOLD") >= 0) + double sl = NormalizeDouble(ask - atr * SL_ATR_Multiplier, _Digits); + double tp = NormalizeDouble(ask + atr * SL_ATR_Multiplier * TP_RR_Ratio, _Digits); + + // Ensure stops are valid + if(!ValidateStops(ORDER_TYPE_BUY, ask, sl, tp)) return; + + double lots = CalcLotSize(MathAbs(ask - sl) / point); + + if(trade.Buy(lots, _Symbol, ask, sl, tp, TradeComment)) { - if(currency == "USD") - relevant = true; - } - else - { - if(currency == symbolCurrency1 || - currency == symbolCurrency2) - relevant = true; - } - - if(!relevant) - continue; - - datetime eventTime = values[i].time; - - datetime blockStart = - eventTime - (NewsPauseBeforeMin * 60); - - datetime blockEnd = - eventTime + (NewsPauseAfterMin * 60); - - if(now >= blockStart && now <= blockEnd) - { - Debug("High impact news detected"); - return true; + LastTradeTime = TimeCurrent(); + Print(">>> BUY | Z=", DoubleToString(zScore, 2), + " CumZ=", DoubleToString(cumZ, 2), + " VWAP=", DoubleToString(vwap, _Digits), + " Lots=", DoubleToString(lots, 2), + " SL=", DoubleToString(sl, _Digits), + " TP=", DoubleToString(tp, _Digits)); } } - - return false; -} - -//==================================================== -// CLOSE ALL POSITIONS -//==================================================== -void CloseAllPositions() -{ - for(int i=PositionsTotal()-1;i>=0;i--) + else if(sellSignal) { - ulong ticket=PositionGetTicket(i); - - if(ticket==0) - continue; - - if(!PositionSelectByTicket(ticket)) - continue; - - if(PositionGetInteger(POSITION_MAGIC)!=MagicNumber) - continue; - - if(PositionGetString(POSITION_SYMBOL)!=_Symbol) - continue; - - trade.PositionClose(ticket); + double sl = NormalizeDouble(bid + atr * SL_ATR_Multiplier, _Digits); + double tp = NormalizeDouble(bid - atr * SL_ATR_Multiplier * TP_RR_Ratio, _Digits); + + if(!ValidateStops(ORDER_TYPE_SELL, bid, sl, tp)) return; + + double lots = CalcLotSize(MathAbs(sl - bid) / point); + + if(trade.Sell(lots, _Symbol, bid, sl, tp, TradeComment)) + { + LastTradeTime = TimeCurrent(); + Print(">>> SELL | Z=", DoubleToString(zScore, 2), + " CumZ=", DoubleToString(cumZ, 2), + " VWAP=", DoubleToString(vwap, _Digits), + " Lots=", DoubleToString(lots, 2), + " SL=", DoubleToString(sl, _Digits), + " TP=", DoubleToString(tp, _Digits)); + } } } -//==================================================== -// UPDATE TICKS -//==================================================== -void UpdateTickBuffer() +//+------------------------------------------------------------------+ +//| MANAGE OPEN POSITIONS — Trailing, breakeven, Z-score exit | +//+------------------------------------------------------------------+ +void ManageOpenPositions() { - for(int i=TickBufferSize-1;i>0;i--) - TickBuffer[i]=TickBuffer[i-1]; - - TickBuffer[0]=SymbolInfoDouble(_Symbol,SYMBOL_BID); -} - -//==================================================== -// Z SCORE -//==================================================== -double CalculateZScore() -{ - double sum=0; - - for(int i=0;i MaxSpreadPoints) + double atr = GetATR(); + if(atr <= 0) return; + + for(int i = PositionsTotal() - 1; i >= 0; i--) { - Debug("Spread too high"); - return false; + 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); + long posType = PositionGetInteger(POSITION_TYPE); + double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + //--- Z-Score exit: if price has reverted back to mean, close early + if(BufferReady) + { + double zNow = CalcZScore(); + double cumZNow = CalcCumulativeZScore(10); + + if(posType == POSITION_TYPE_BUY && zNow >= ZScoreExit && cumZNow >= 0) + { + double profit = PositionGetDouble(POSITION_PROFIT); + if(profit > 0) + { + trade.PositionClose(ticket); + Print("<<< BUY CLOSED (Z-revert) | Z=", DoubleToString(zNow, 2), + " Profit=", DoubleToString(profit, 2)); + continue; + } + } + else if(posType == POSITION_TYPE_SELL && zNow <= -ZScoreExit && cumZNow <= 0) + { + double profit = PositionGetDouble(POSITION_PROFIT); + if(profit > 0) + { + trade.PositionClose(ticket); + Print("<<< SELL CLOSED (Z-revert) | Z=", DoubleToString(zNow, 2), + " Profit=", DoubleToString(profit, 2)); + continue; + } + } + } + + //--- Breakeven logic + double breakevenDist = atr * BreakevenATR_Mult; + + if(posType == POSITION_TYPE_BUY) + { + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + + // Move to breakeven + if(bid >= openPrice + breakevenDist && currentSL < openPrice) + { + double newSL = NormalizeDouble(openPrice + point * 2, _Digits); // Tiny profit guaranteed + if(newSL > currentSL) + { + trade.PositionModify(ticket, newSL, currentTP); + Print("--- BUY BREAKEVEN | NewSL=", DoubleToString(newSL, _Digits)); + } + } + + // Trailing stop + double trailLevel = bid - atr * TrailingATR_Mult; + trailLevel = NormalizeDouble(trailLevel, _Digits); + + if(trailLevel > currentSL && trailLevel > openPrice) + { + trade.PositionModify(ticket, trailLevel, currentTP); + } + } + else if(posType == POSITION_TYPE_SELL) + { + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + + // Move to breakeven + if(ask <= openPrice - breakevenDist && (currentSL > openPrice || currentSL == 0)) + { + double newSL = NormalizeDouble(openPrice - point * 2, _Digits); + if(currentSL == 0 || newSL < currentSL) + { + trade.PositionModify(ticket, newSL, currentTP); + Print("--- SELL BREAKEVEN | NewSL=", DoubleToString(newSL, _Digits)); + } + } + + // Trailing stop + double trailLevel = ask + atr * TrailingATR_Mult; + trailLevel = NormalizeDouble(trailLevel, _Digits); + + if((trailLevel < currentSL || currentSL == 0) && trailLevel < openPrice) + { + trade.PositionModify(ticket, trailLevel, currentTP); + } + } } +} - double atrPoints = GetATR()/_Point; +//+------------------------------------------------------------------+ +//| DYNAMIC LOT SIZING — Based on equity and risk % | +//+------------------------------------------------------------------+ +double CalcLotSize(double slDistancePoints) +{ + if(slDistancePoints <= 0) return MinLotSize; + + double equity = AccountInfoDouble(ACCOUNT_EQUITY); + double riskAmount = equity * (RiskPercentPerTrade / 100.0); + + double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); + double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); + double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + if(tickValue <= 0 || tickSize <= 0 || point <= 0) return MinLotSize; + + // Convert SL distance to monetary risk per lot + double riskPerLot = (slDistancePoints * point / tickSize) * tickValue; + + if(riskPerLot <= 0) return MinLotSize; + + double lots = riskAmount / riskPerLot; + + // Round to lot step + double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + double lotMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + double lotMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + + lots = MathFloor(lots / lotStep) * lotStep; + lots = MathMax(lots, lotMin); + lots = MathMin(lots, lotMax); + lots = MathMax(lots, MinLotSize); + lots = MathMin(lots, MaxLotSize); + + return NormalizeDouble(lots, 2); +} - string sym=_Symbol; - StringToUpper(sym); - - double minATR = MinATRPointsForex; - - if(StringFind(sym,"XAU")>=0 || StringFind(sym,"GOLD")>=0) - minATR = MinATRPointsGold; - - if(atrPoints < minATR) +//+------------------------------------------------------------------+ +//| CIRCUIT BREAKERS — Protect the account | +//+------------------------------------------------------------------+ +void UpdateCircuitBreakers() +{ + double equity = AccountInfoDouble(ACCOUNT_EQUITY); + + // Track peak equity + if(equity > PeakEquity) + PeakEquity = equity; + + // Daily drawdown check + if(DayStartEquity > 0) { - Debug("ATR too low: " + DoubleToString(atrPoints,1)); - return false; + double dailyDD = ((DayStartEquity - equity) / DayStartEquity) * 100.0; + if(dailyDD >= MaxDailyDrawdownPct) + { + if(!DailyCircuitBreaker) + { + DailyCircuitBreaker = true; + Print("!!! DAILY CIRCUIT BREAKER TRIGGERED !!! DD=", DoubleToString(dailyDD, 2), "%"); + Print("!!! No new trades until tomorrow. Managing exits only."); + } + } } + + // Total drawdown from peak + if(PeakEquity > 0) + { + double totalDD = ((PeakEquity - equity) / PeakEquity) * 100.0; + if(totalDD >= MaxTotalDrawdownPct) + { + if(!TotalCircuitBreaker) + { + TotalCircuitBreaker = true; + Print("!!! TOTAL CIRCUIT BREAKER TRIGGERED !!! DD from peak=", DoubleToString(totalDD, 2), "%"); + Print("!!! EA STOPPED. Manual intervention required."); + } + } + } +} - if(TimeCurrent()-LastTradeTime < CooldownSeconds) +//+------------------------------------------------------------------+ +//| Daily reset | +//+------------------------------------------------------------------+ +void CheckDailyReset() +{ + MqlDateTime now; + TimeToStruct(TimeCurrent(), now); + + MqlDateTime last; + TimeToStruct(LastDay, last); + + if(now.day != last.day || now.mon != last.mon) + { + DayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); + DailyCircuitBreaker = false; + LastDay = TimeCurrent(); + Print("=== NEW DAY | Equity Reset: ", DoubleToString(DayStartEquity, 2), " ==="); + } +} + +//+------------------------------------------------------------------+ +//| FILTER CHECKS | +//+------------------------------------------------------------------+ +bool PassesAllFilters() +{ + // Spread filter + double spreadPoints = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD); + if(spreadPoints > MaxSpreadPoints) return false; - + + // ATR volatility filter + double atr = GetATR(); + double minATR = MinATR_Filter; + + // Auto-detect minimum ATR if set to 0 + if(minATR <= 0) + { + double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + minATR = spreadPoints * point * 2; // At least 2× spread of movement + } + + if(atr < minATR) + return false; + + // Session filter + if(UseSessionFilter) + { + MqlDateTime serverTime; + TimeToStruct(TimeCurrent(), serverTime); + + if(SessionStartHour < SessionEndHour) + { + if(serverTime.hour < SessionStartHour || serverTime.hour >= SessionEndHour) + return false; + } + else // Wraps midnight + { + if(serverTime.hour < SessionStartHour && serverTime.hour >= SessionEndHour) + return false; + } + } + + // News filter + if(UseNewsFilter && IsNewsTime()) + return false; + return true; } -//==================================================== -// OPEN BUY -//==================================================== -void OpenBuy(double atr) +//+------------------------------------------------------------------+ +//| NEWS FILTER — Uses MT5 Economic Calendar | +//+------------------------------------------------------------------+ +bool IsNewsTime() { - double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); - double sl=ask-(atr*ATRMultiplierSL); - - AdjustStopsForBroker(sl,true); - - double lot=GetLotSize(); - - trade.SetExpertMagicNumber(MagicNumber); - - if(trade.Buy(lot,_Symbol,ask,sl,0,"BUY WITH NEWS FILTER")) - LastTradeTime=TimeCurrent(); -} - -//==================================================== -// OPEN SELL -//==================================================== -void OpenSell(double atr) -{ - double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); - double sl=bid+(atr*ATRMultiplierSL); - - AdjustStopsForBroker(sl,false); - - double lot=GetLotSize(); - - trade.SetExpertMagicNumber(MagicNumber); - - if(trade.Sell(lot,_Symbol,bid,sl,0,"SELL WITH NEWS FILTER")) - LastTradeTime=TimeCurrent(); -} - -//==================================================== -// LOT SIZE -//==================================================== -double GetLotSize() -{ - string s=_Symbol; - StringToUpper(s); - - if(StringFind(s,"XAU")>=0 || StringFind(s,"GOLD")>=0) - return GoldLot; - - return ForexLot; -} - -//==================================================== -// POSITION COUNT -//==================================================== -int CountPositionsBySymbol() -{ - int c=0; - - for(int i=PositionsTotal()-1;i>=0;i--) + datetime now = TimeCurrent(); + + // Query window: from (NewsPauseAfterMin ago) to (NewsPauseBeforeMin ahead) + // This fixes the bug in your previous EAs that only looked 60 seconds back! + datetime from = now - (NewsMinutesAfter * 60); + datetime to = now + (NewsMinutesBefore * 60); + + MqlCalendarValue values[]; + int count = CalendarValueHistory(values, from, to); + + if(count <= 0) return false; + + // Get the currencies in our symbol + string baseCurrency = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE); + string quoteCurrency = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT); + + // For gold/indices/crypto, watch USD + string symbolName = _Symbol; + StringToUpper(symbolName); + bool isGoldOrIndex = (StringFind(symbolName, "XAU") >= 0 || + StringFind(symbolName, "GOLD") >= 0 || + StringFind(symbolName, "US30") >= 0 || + StringFind(symbolName, "NAS") >= 0 || + StringFind(symbolName, "SPX") >= 0 || + StringFind(symbolName, "BTC") >= 0 || + StringFind(symbolName, "ETH") >= 0); + + for(int i = 0; i < count; i++) { - ulong t=PositionGetTicket(i); - - if(t==0) + MqlCalendarEvent event; + if(!CalendarEventById(values[i].event_id, event)) continue; - - if(!PositionSelectByTicket(t)) + + // Only HIGH impact + if(event.importance != CALENDAR_IMPORTANCE_HIGH) continue; - - if(PositionGetInteger(POSITION_MAGIC)!=MagicNumber) + + MqlCalendarCountry country; + if(!CalendarCountryById(event.country_id, country)) continue; - - if(PositionGetString(POSITION_SYMBOL)!=_Symbol) - continue; - - c++; - } - - return c; -} - -//==================================================== -// POSITION MANAGEMENT -//==================================================== -void ManagePositions() -{ - for(int i=PositionsTotal()-1;i>=0;i--) - { - ulong t=PositionGetTicket(i); - - if(t==0) - continue; - - if(!PositionSelectByTicket(t)) - continue; - - if(PositionGetInteger(POSITION_MAGIC)!=MagicNumber) - continue; - - if(PositionGetString(POSITION_SYMBOL)!=_Symbol) - continue; - - double vol=PositionGetDouble(POSITION_VOLUME); - double sl=PositionGetDouble(POSITION_SL); - double profit=PositionGetDouble(POSITION_PROFIT); - - double atr=GetATR(); - double target=vol*ProfitTargetPerLot; - - if(profit>=target) + + string eventCurrency = country.currency; + StringToUpper(eventCurrency); + + bool relevant = false; + + if(isGoldOrIndex) { - trade.PositionClose(t); - continue; + relevant = (eventCurrency == "USD"); } - - ENUM_POSITION_TYPE type= - (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); - - if(type==POSITION_TYPE_BUY) + else { - double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); - double newSL=bid-(atr*ATRTrailingMultiplier); - - if(newSL>sl) - trade.PositionModify(t,newSL,0); + string baseUp = baseCurrency; + string quoteUp = quoteCurrency; + StringToUpper(baseUp); + StringToUpper(quoteUp); + relevant = (eventCurrency == baseUp || eventCurrency == quoteUp); } - - if(type==POSITION_TYPE_SELL) + + if(relevant) { - double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); - double newSL=ask+(atr*ATRTrailingMultiplier); - - if(sl==0 || newSL= eventTime - NewsMinutesBefore * 60 && + now <= eventTime + NewsMinutesAfter * 60) + { + Print("NEWS BLOCK: ", event.name, " at ", TimeToString(eventTime)); + return true; + } } } + + return false; } -//==================================================== -// BROKER SAFETY -//==================================================== -void AdjustStopsForBroker(double &sl,bool buy) +//+------------------------------------------------------------------+ +//| VALIDATE STOPS — Broker-safe | +//+------------------------------------------------------------------+ +bool ValidateStops(ENUM_ORDER_TYPE orderType, double price, double sl, double tp) { - double stop= - (double)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point; - - double freeze= - (double)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL)*_Point; - - double min=MathMax(stop,freeze); - - double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); - double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); - - if(buy && (ask-sl)= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + count++; + } + return count; +} +//+------------------------------------------------------------------+