Files
mt5_news_eas/InstitutionalTickHybridNewsFilter.mq5
T

808 lines
29 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| 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
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| 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
//--- 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
//--- 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
//--- 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)
//--- 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)
//--- 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
//+------------------------------------------------------------------+
//| 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?
// VWAP data
double VWAPPrices[];
double VWAPVolumes[];
int VWAPCount = 0;
// 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()
{
// 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("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 |
//+------------------------------------------------------------------+
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 |
//+------------------------------------------------------------------+
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)
for(int i = 0; i < copied; i++)
{
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();
}
//+------------------------------------------------------------------+
//| 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++;
}
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+
//| 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();
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(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);
if(UseVWAPConfirm && vwap > 0)
sellSignal = sellSignal && (mid > vwap); // Price above fair value
//=== EXECUTE ===
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 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));
}
}
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 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));
}
}
}
//+------------------------------------------------------------------+
//| MANAGE OPEN POSITIONS — Trailing, breakeven, Z-score exit |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
double atr = GetATR();
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;
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);
}
}
}
}
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+
//| 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)
{
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.");
}
}
}
}
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+
//| NEWS FILTER — Uses MT5 Economic Calendar |
//+------------------------------------------------------------------+
bool IsNewsTime()
{
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;
}
//+------------------------------------------------------------------+
//| 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;
}
else
{
if(MathAbs(sl - price) < minDist) return false;
if(MathAbs(price - tp) < minDist) return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Get current ATR value |
//+------------------------------------------------------------------+
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 |
//+------------------------------------------------------------------+
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;
count++;
}
return count;
}
//+------------------------------------------------------------------+