From a4ccb9306454f44c1c8c209cd6207aa222b9bb1f Mon Sep 17 00:00:00 2001 From: kingstonebridge2032 Date: Sat, 23 May 2026 20:54:03 +0300 Subject: [PATCH] Update InstitutionalTickHybridNewsFilter.mq5 --- InstitutionalTickHybridNewsFilter.mq5 | 847 +++++++++++++------------- 1 file changed, 416 insertions(+), 431 deletions(-) diff --git a/InstitutionalTickHybridNewsFilter.mq5 b/InstitutionalTickHybridNewsFilter.mq5 index 815cd57..7f68558 100644 --- a/InstitutionalTickHybridNewsFilter.mq5 +++ b/InstitutionalTickHybridNewsFilter.mq5 @@ -1,14 +1,14 @@ //+------------------------------------------------------------------+ -//| 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 | +//| 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" @@ -17,68 +17,68 @@ #include //+------------------------------------------------------------------+ -//| INPUT PARAMETERS | +//| INPUT PARAMETERS | //+------------------------------------------------------------------+ //--- 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 int TickBufferSize = 40; +input double ZScoreEntry = 1.20; +input double ZScoreExit = 0.20; +input int VWAPWindowTicks = 60; +input bool UseVWAPConfirm = true; -//--- Risk Management +//--- 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 double RiskPercentPerTrade = 1.0; +input double MaxDailyDrawdownPct = 5.0; +input double MaxTotalDrawdownPct = 15.0; +input int MaxPositionsPerSymbol= 1; +input double MaxLotSize = 0.50; +input double MinLotSize = 0.01; //--- 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 double SL_ATR_Multiplier = 1.2; +input double TP_RR_Ratio = 1.5; +input double TrailingATR_Mult = 0.8; +input double BreakevenATR_Mult = 0.5; +input int ATR_Period = 14; +input ENUM_TIMEFRAMES ATR_Timeframe = PERIOD_M1; //--- 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 int MaxSpreadPoints = 45; +input double MinATR_Filter = 0.0; +input bool UseSessionFilter = true; +input int SessionStartHour = 0; +input int SessionEndHour = 24; //--- 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 bool UseNewsFilter = false; +input int NewsMinutesBefore = 30; +input int NewsMinutesAfter = 30; +input bool NewsClosePositions = false; //--- 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 +input int MagicNumber = 777888; +input int TradeSlippage = 20; +input string TradeComment = "PhantomEdge"; +input int CooldownSeconds = 1; //+------------------------------------------------------------------+ -//| GLOBAL VARIABLES | +//| GLOBAL VARIABLES | //+------------------------------------------------------------------+ CTrade trade; // 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? +double TickPrices[]; +double TickReturns[]; +double TickVolumes[]; +int TickCount = 0; +bool BufferReady = false; // VWAP data double VWAPPrices[]; @@ -98,418 +98,455 @@ datetime LastTickTime = 0; int ATR_Handle = INVALID_HANDLE; //+------------------------------------------------------------------+ -//| Expert initialization | +//| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { - // Setup trade object trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(TradeSlippage); - trade.SetTypeFilling(ORDER_FILLING_IOC); - - // Initialize tick buffers + + ENUM_SYMBOL_TRADE_EXECUTION exec = + (ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE); + + if(exec == SYMBOL_TRADE_EXECUTION_MARKET) + trade.SetTypeFilling(ORDER_FILLING_FOK); + else + trade.SetTypeFilling(ORDER_FILLING_RETURN); + 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("ERROR: Failed to create ATR indicator"); return INIT_FAILED; } - - // 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; } //+------------------------------------------------------------------+ -//| Expert deinitialization | +//| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(ATR_Handle != INVALID_HANDLE) IndicatorRelease(ATR_Handle); - - Print("=== PhantomEdge TickScalper Deinitialized ==="); } //+------------------------------------------------------------------+ -//| Expert tick function — THE HEART OF THE EA | +//| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { - //--- 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) + + int copied = CopyTicks(_Symbol, ticks, COPY_TICKS_INFO, 0, 200); + + if(copied <= 0) + return; + for(int i = 0; i < copied; i++) { - if(ticks[i].time_msc <= LastTickTime && LastTickTime != 0) continue; - + if(ticks[i].time_msc <= LastTickTime && LastTickTime != 0) + continue; + + if(ticks[i].ask <= 0 || ticks[i].bid <= 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; - + + 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 + ManageOpenPositions(); 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 + + if(!BufferReady) + return; + + if(!PassesAllFilters()) + return; + + if(TimeCurrent() - LastTradeTime < CooldownSeconds) + return; + + if(CountMyPositions() >= MaxPositionsPerSymbol) + return; + EvaluateEntry(); } //+------------------------------------------------------------------+ -//| Process a single tick into our buffers | +//| Process Tick | //+------------------------------------------------------------------+ 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]); + 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++; } //+------------------------------------------------------------------+ -//| Calculate Z-Score on RETURNS (not raw prices!) | +//| Calculate ZScore | //+------------------------------------------------------------------+ 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; + + double stdev = MathSqrt(sumSqDev / (TickBufferSize - 1)); + + if(stdev < 1e-12) + return 0; + + return (TickReturns[TickBufferSize - 1] - mean) / stdev; } //+------------------------------------------------------------------+ -//| Calculate Micro-VWAP (Volume-Weighted Average Price) | +//| Calculate VWAP | //+------------------------------------------------------------------+ double CalcMicroVWAP() { - if(VWAPCount < VWAPWindowTicks) return 0; - + if(VWAPCount < VWAPWindowTicks) + return 0; + double sumPV = 0; double sumV = 0; - + for(int i = 0; i < VWAPWindowTicks; i++) { - if(VWAPPrices[i] <= 0) return 0; + if(VWAPPrices[i] <= 0) + return 0; + sumPV += VWAPPrices[i] * VWAPVolumes[i]; - sumV += VWAPVolumes[i]; + sumV += VWAPVolumes[i]; } - - if(sumV < 1) return 0; + + if(sumV < 1) + return 0; + return sumPV / sumV; } //+------------------------------------------------------------------+ -//| Calculate cumulative Z-Score (multi-tick momentum) | +//| Calculate cumulative ZScore | //+------------------------------------------------------------------+ double CalcCumulativeZScore(int lookback) { - if(lookback > TickBufferSize) lookback = TickBufferSize; - - // Sum of recent returns (cumulative move) + if(lookback > TickBufferSize) + lookback = TickBufferSize; + 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 + + if(stdev < 1e-12) + return 0; + double expectedStdev = stdev * MathSqrt((double)lookback); + return (cumReturn - mean * lookback) / expectedStdev; } //+------------------------------------------------------------------+ -//| ENTRY EVALUATION — The money-maker | +//| Entry Logic | //+------------------------------------------------------------------+ void EvaluateEntry() { double zScore = CalcZScore(); - double cumZ = CalcCumulativeZScore(10); // 10-tick momentum check + double cumZ = CalcCumulativeZScore(10); 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 ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double mid = (ask + bid) / 2.0; + double atr = GetATR(); - if(atr <= 0) return; - + + if(atr <= 0) + return; + double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); - if(point <= 0) return; - - //=== BUY SIGNAL === - // Tick returns show extreme negative deviation (price crashed down relative to recent behavior) - // AND cumulative momentum confirms the dip (not just noise) - // AND price is below VWAP (undervalued) — mean reversion BUY - bool buySignal = (zScore <= -ZScoreEntry) && (cumZ <= -1.0); - + + if(point <= 0) + return; + + bool buySignal = + (zScore <= -ZScoreEntry) && + (cumZ <= -1.0); + if(UseVWAPConfirm && vwap > 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); - + buySignal = buySignal && (mid < vwap); + + bool sellSignal = + (zScore >= ZScoreEntry) && + (cumZ >= 1.0); + if(UseVWAPConfirm && vwap > 0) - sellSignal = sellSignal && (mid > vwap); // Price above fair value - - //=== EXECUTE === + sellSignal = sellSignal && (mid > vwap); + if(buySignal) { - 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 sl = NormalizeDouble( + ask - atr * SL_ATR_Multiplier, + _Digits + ); + + double tp = NormalizeDouble( + ask + atr * SL_ATR_Multiplier * TP_RR_Ratio, + _Digits + ); + + 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)) { 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)); + + Print("BUY OPENED"); } } else if(sellSignal) { - 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 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)); + + Print("SELL OPENED"); } } } //+------------------------------------------------------------------+ -//| MANAGE OPEN POSITIONS — Trailing, breakeven, Z-score exit | +//| Manage Positions | //+------------------------------------------------------------------+ void ManageOpenPositions() { double atr = GetATR(); - if(atr <= 0) return; - + + if(atr <= 0) + return; + 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(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 + + long posType = PositionGetInteger(POSITION_TYPE); + + double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + if(BufferReady) { double zNow = CalcZScore(); double cumZNow = CalcCumulativeZScore(10); - - if(posType == POSITION_TYPE_BUY && zNow >= ZScoreExit && cumZNow >= 0) + + if(posType == POSITION_TYPE_BUY && + zNow >= ZScoreExit && + cumZNow >= 0) { - double profit = PositionGetDouble(POSITION_PROFIT); + 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) + + if(posType == POSITION_TYPE_SELL && + zNow <= -ZScoreExit && + cumZNow <= 0) { - double profit = PositionGetDouble(POSITION_PROFIT); + 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; - + + 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 bid = + SymbolInfoDouble(_Symbol, SYMBOL_BID); + + if(bid >= openPrice + breakevenDist && + currentSL < openPrice) { - double newSL = NormalizeDouble(openPrice + point * 2, _Digits); // Tiny profit guaranteed + double newSL = + NormalizeDouble(openPrice + point * 2, _Digits); + 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) + + double trailLevel = + NormalizeDouble( + bid - atr * TrailingATR_Mult, + _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 ask = + SymbolInfoDouble(_Symbol, SYMBOL_ASK); + + if(ask <= openPrice - breakevenDist && + (currentSL > openPrice || currentSL == 0)) { - double newSL = NormalizeDouble(openPrice - point * 2, _Digits); + 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) + + double trailLevel = + NormalizeDouble( + ask + atr * TrailingATR_Mult, + _Digits + ); + + if((trailLevel < currentSL || currentSL == 0) && + trailLevel < openPrice) { trade.PositionModify(ticket, trailLevel, currentTP); } @@ -518,290 +555,238 @@ void ManageOpenPositions() } //+------------------------------------------------------------------+ -//| DYNAMIC LOT SIZING — Based on equity and risk % | +//| Lot Size | //+------------------------------------------------------------------+ 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; - + 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; + + 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); - + + 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); } //+------------------------------------------------------------------+ -//| CIRCUIT BREAKERS — Protect the account | +//| Circuit Breakers | //+------------------------------------------------------------------+ void UpdateCircuitBreakers() { - double equity = AccountInfoDouble(ACCOUNT_EQUITY); - - // Track peak equity + double equity = + AccountInfoDouble(ACCOUNT_EQUITY); + if(equity > PeakEquity) PeakEquity = equity; - - // Daily drawdown check + if(DayStartEquity > 0) { - double dailyDD = ((DayStartEquity - equity) / DayStartEquity) * 100.0; + 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."); - } - } + DailyCircuitBreaker = true; } - - // Total drawdown from peak + if(PeakEquity > 0) { - double totalDD = ((PeakEquity - equity) / PeakEquity) * 100.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."); - } - } + TotalCircuitBreaker = true; } } //+------------------------------------------------------------------+ -//| Daily reset | +//| 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); + DayStartEquity = + AccountInfoDouble(ACCOUNT_EQUITY); + DailyCircuitBreaker = false; + LastDay = TimeCurrent(); - Print("=== NEW DAY | Equity Reset: ", DoubleToString(DayStartEquity, 2), " ==="); } } //+------------------------------------------------------------------+ -//| FILTER CHECKS | +//| Filters | //+------------------------------------------------------------------+ bool PassesAllFilters() { - // Spread filter - double spreadPoints = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD); + 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 + double point = + SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + minATR = spreadPoints * point * 2; } - + 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) + if(serverTime.hour < SessionStartHour || + serverTime.hour >= SessionEndHour) return false; } } - - // News filter - if(UseNewsFilter && IsNewsTime()) - return false; - + return true; } //+------------------------------------------------------------------+ -//| NEWS FILTER — Uses MT5 Economic Calendar | +//| Validate Stops | //+------------------------------------------------------------------+ -bool IsNewsTime() +bool ValidateStops( + ENUM_ORDER_TYPE orderType, + double price, + double sl, + double tp +) { - 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++) - { - MqlCalendarEvent event; - if(!CalendarEventById(values[i].event_id, event)) - continue; - - // Only HIGH impact - if(event.importance != CALENDAR_IMPORTANCE_HIGH) - continue; - - MqlCalendarCountry country; - if(!CalendarCountryById(event.country_id, country)) - continue; - - string eventCurrency = country.currency; - StringToUpper(eventCurrency); - - bool relevant = false; - - if(isGoldOrIndex) - { - relevant = (eventCurrency == "USD"); - } - else - { - string baseUp = baseCurrency; - string quoteUp = quoteCurrency; - StringToUpper(baseUp); - StringToUpper(quoteUp); - relevant = (eventCurrency == baseUp || eventCurrency == quoteUp); - } - - if(relevant) - { - // Check if we're within the blocking window - datetime eventTime = values[i].time; - - if(now >= eventTime - NewsMinutesBefore * 60 && - now <= eventTime + NewsMinutesAfter * 60) - { - Print("NEWS BLOCK: ", event.name, " at ", TimeToString(eventTime)); - return true; - } - } - } - - return false; -} + long stopsLevel = + SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); + + long freezeLevel = + SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); + + double point = + SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + double minDist = + MathMax((double)stopsLevel, (double)freezeLevel) * point; + + double spreadDist = + SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * point * 3; -//+------------------------------------------------------------------+ -//| VALIDATE STOPS — Broker-safe | -//+------------------------------------------------------------------+ -bool ValidateStops(ENUM_ORDER_TYPE orderType, double price, double sl, double tp) -{ - long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); - long freezeLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); - double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); - - // Minimum distance from price - double minDist = MathMax((double)stopsLevel, (double)freezeLevel) * point; - double spreadDist = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * point * 3; // 3× spread safety minDist = MathMax(minDist, spreadDist); - + if(orderType == ORDER_TYPE_BUY) { - if(MathAbs(price - sl) < minDist) return false; - if(MathAbs(tp - price) < minDist) return false; + if(MathAbs(price - sl) < minDist) + return false; + + if(MathAbs(tp - price) < minDist) + return false; } else { - if(MathAbs(sl - price) < minDist) return false; - if(MathAbs(price - tp) < minDist) return false; + if(MathAbs(sl - price) < minDist) + return false; + + if(MathAbs(price - tp) < minDist) + return false; } - + return true; } //+------------------------------------------------------------------+ -//| Get current ATR value | +//| ATR | //+------------------------------------------------------------------+ double GetATR() { double atrBuffer[]; + ArraySetAsSeries(atrBuffer, true); - + if(CopyBuffer(ATR_Handle, 0, 0, 1, atrBuffer) <= 0) return 0; - + return atrBuffer[0]; } //+------------------------------------------------------------------+ -//| Count our open positions on this symbol | +//| Count Positions | //+------------------------------------------------------------------+ int CountMyPositions() { int count = 0; + 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(ticket == 0) + continue; + + if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) + continue; + + if(PositionGetString(POSITION_SYMBOL) != _Symbol) + continue; + count++; } + return count; } //+------------------------------------------------------------------+