WIP: 6+ weeks of uncommitted EA development and preset tuning

Confluence EA (v1.16 → v1.20):
- Per-EA realized P&L tracking via history deals
- Weekly drawdown protection
- Warmup bars, pivot cache, state persistence
- Point-scaled pivot thresholds, ranging ATR factor
- Market filling mode helper per symbol

Grid EA (v3.1 → v4.1):
- Adaptive filters, adaptive entry, spread filter
- Session filter, breakeven, correlation caps, range drift
- Profit protection (stop-after-profit, cycle reports)
- Edge cleanup v5.0 — close wrong-side positions outside grid
- Master one-shot shutdown, grid state persistence

Presets:
- Fix GetOut=Y shutdown bug on 4 grid presets
- Relax ADXMax 18→40, widen RSI 20/80 across grid presets
- Standardize daily drawdown 3%→5%, add weekly 10%
- Increase grid lots 0.01→0.03
- Normalize confluence ATR thresholds per pair
- Add XAGUSD, EURCHF, EURGBP, AUDNZD presets

Docs & DevOps:
- April 23 audit files (preset mismatch, code review, checklist)
- n8n workflow and validation infrastructure updates
- AI agent analyses in notes/

Known issues carried forward:
- Shared drawdown budget contamination (both EAs)
- Confluence ranging-market threshold inversion
- Older grid presets missing v4.1 safety controls
This commit is contained in:
2026-05-12 09:02:25 -04:00
parent b9b4e2b22b
commit 0894d18db4
72 changed files with 3869 additions and 1416 deletions
Regular → Executable
+453 -105
View File
@@ -5,7 +5,7 @@
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Abbey Road Tech"
#property link "https://www.abbeyroadtech.com"
#property version "1.16"
#property version "1.20"
#property strict
#include <Trade\Trade.mqh>
@@ -39,6 +39,9 @@ input double InpMinATRPercent = 0.5; // Min ATR as % of price (0.5 = 0.
input bool InpUseADXFilter = true; // Use ADX trend strength filter
input int InpADXPeriod = 14; // ADX period
input double InpMinADX = 20.0; // Min ADX to trade (20-25 recommended)
input int InpWarmupBars = 20; // Bars required before filters activate
input int InpPivotThresholdPoints = 100; // Proximity threshold in POINTS (100pt = 10 pips)
input double InpRangingATRFactor = 0.3; // Ranging "near pivot" = ATR * factor
input group "=== EA Settings ==="
input ulong InpMagicNumber = 777777; // Magic number
@@ -46,7 +49,8 @@ input int InpSlippage = 3; // Max slippage
input bool InpDebugMode = true; // Print debug info
input group "=== Equity Protection ==="
input double InpMaxDailyDrawdown = 3.0; // Max daily drawdown % (0=disable)
input double InpMaxDailyDrawdown = 5.0; // Max daily drawdown % per-EA (0=disable)
input double InpMaxWeeklyDrawdown = 10.0; // Max weekly drawdown % per-EA (0=disable)
input group "=== Weekend Protection ==="
input bool InpCloseBeforeWeekend = true; // Close positions Friday before market close
@@ -62,13 +66,211 @@ datetime lastBarTime = 0;
int totalBuyPositions = 0;
int totalSellPositions = 0;
//--- Daily Drawdown Protection
//--- Daily Drawdown Protection (per-EA)
double dailyStartEquity = 0;
datetime lastEquityReset = 0;
double realizedPnLToday = 0;
datetime lastRealizedScan = 0;
//--- Weekly Drawdown Protection (per-EA)
double weeklyStartEquity = 0;
datetime lastWeeklyReset = 0;
double realizedPnLWeek = 0;
datetime lastWeeklyScan = 0;
//--- Weekend Protection
bool weekendCloseExecuted = false;
//--- Ranging Detection
bool isRangingMarket = false;
datetime lastRangingCheck = 0;
int rangingBarsCounter = 0;
//--- Pivot cache (1.7)
double cachedPivotP = 0, cachedPivotR1 = 0, cachedPivotR2 = 0, cachedPivotS1 = 0, cachedPivotS2 = 0;
datetime cachedPivotDay = 0;
//--- Warm-up tracker (1.4)
datetime eaInitTime = 0;
//+------------------------------------------------------------------+
//| Log helper with symbol prefix |
//+------------------------------------------------------------------+
void LogS(string msg)
{
Print("[", _Symbol, ":", InpMagicNumber, "] ", msg);
}
//+------------------------------------------------------------------+
//| Filling mode per symbol (1.3) |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING GetMarketFilling()
{
int modes = (int)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if((modes & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC;
if((modes & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK;
return ORDER_FILLING_RETURN;
}
//+------------------------------------------------------------------+
//| State persistence (2.7) |
//+------------------------------------------------------------------+
string GvKey(string suffix)
{
return "Confluence_" + IntegerToString((int)InpMagicNumber) + "_" + _Symbol + "_" + suffix;
}
void SaveDailyState()
{
GlobalVariableSet(GvKey("dailyStartEquity"), dailyStartEquity);
GlobalVariableSet(GvKey("lastEquityReset"), (double)lastEquityReset);
GlobalVariableSet(GvKey("weeklyStartEquity"), weeklyStartEquity);
GlobalVariableSet(GvKey("lastWeeklyReset"), (double)lastWeeklyReset);
}
void LoadDailyState()
{
if(GlobalVariableCheck(GvKey("dailyStartEquity"))) dailyStartEquity = GlobalVariableGet(GvKey("dailyStartEquity"));
if(GlobalVariableCheck(GvKey("lastEquityReset"))) lastEquityReset = (datetime)GlobalVariableGet(GvKey("lastEquityReset"));
if(GlobalVariableCheck(GvKey("weeklyStartEquity"))) weeklyStartEquity = GlobalVariableGet(GvKey("weeklyStartEquity"));
if(GlobalVariableCheck(GvKey("lastWeeklyReset"))) lastWeeklyReset = (datetime)GlobalVariableGet(GvKey("lastWeeklyReset"));
}
//+------------------------------------------------------------------+
//| Warm-up check (1.4) |
//+------------------------------------------------------------------+
bool IsWarmedUp()
{
if(InpWarmupBars <= 0) return true;
int bars = iBars(_Symbol, _Period);
if(bars < InpWarmupBars) return false;
// Also require init to have happened at least one bar period ago
int secsPerBar = PeriodSeconds(_Period);
if(TimeCurrent() - eaInitTime < secsPerBar) return false;
return true;
}
//+------------------------------------------------------------------+
//| Today-at-broker / Monday-at-broker |
//+------------------------------------------------------------------+
datetime TodayStartBroker()
{
datetime now = TimeCurrent();
return (datetime)((now / 86400) * 86400);
}
// Monday 00:00 broker-time of the current week (Mon=1 ... Sun=0)
datetime WeekStartBroker()
{
datetime today = TodayStartBroker();
MqlDateTime dt;
TimeToStruct(today, dt);
int offset = (dt.day_of_week == 0) ? 6 : (dt.day_of_week - 1);
return (datetime)(today - offset * 86400);
}
//+------------------------------------------------------------------+
//| Sum deals for this EA in a window (history helper) |
//+------------------------------------------------------------------+
double SumRealizedPnLSince(datetime from)
{
datetime to = TimeCurrent() + 60;
if(!HistorySelect(from, to)) return 0;
double total = 0;
int deals = HistoryDealsTotal();
for(int i = 0; i < deals; i++)
{
ulong dealTicket = HistoryDealGetTicket(i);
if(dealTicket == 0) continue;
if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != (long)InpMagicNumber) continue;
if(HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol) continue;
long entry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT) continue;
total += HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
total += HistoryDealGetDouble(dealTicket, DEAL_SWAP);
total += HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
}
return total;
}
//+------------------------------------------------------------------+
//| Realized P&L today for this EA (1.1) |
//+------------------------------------------------------------------+
double GetRealizedPnLToday()
{
if(TimeCurrent() - lastRealizedScan < 60 && realizedPnLToday != 0)
return realizedPnLToday;
realizedPnLToday = SumRealizedPnLSince(TodayStartBroker());
lastRealizedScan = TimeCurrent();
return realizedPnLToday;
}
//+------------------------------------------------------------------+
//| Realized P&L this week for this EA |
//+------------------------------------------------------------------+
double GetRealizedPnLWeek()
{
if(TimeCurrent() - lastWeeklyScan < 120 && realizedPnLWeek != 0)
return realizedPnLWeek;
realizedPnLWeek = SumRealizedPnLSince(WeekStartBroker());
lastWeeklyScan = TimeCurrent();
return realizedPnLWeek;
}
//+------------------------------------------------------------------+
//| Floating P&L for this EA |
//+------------------------------------------------------------------+
double GetFloatingPnL()
{
double total = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != (long)InpMagicNumber) continue;
total += PositionGetDouble(POSITION_PROFIT);
total += PositionGetDouble(POSITION_SWAP);
}
return total;
}
//+------------------------------------------------------------------+
//| Cached ATR (returns 0 if unavailable) |
//+------------------------------------------------------------------+
double GetCurrentATR()
{
if(ATRHandle == INVALID_HANDLE) return 0;
double buf[1];
if(CopyBuffer(ATRHandle, 0, 0, 1, buf) <= 0) return 0;
return buf[0];
}
//+------------------------------------------------------------------+
//| Cached pivots (1.7) |
//+------------------------------------------------------------------+
void GetCachedPivots(double &p, double &r1, double &r2, double &s1, double &s2)
{
datetime today = TodayStartBroker();
if(today != cachedPivotDay || cachedPivotP == 0)
{
double prevHigh = iHigh (_Symbol, PERIOD_D1, 1);
double prevLow = iLow (_Symbol, PERIOD_D1, 1);
double prevClose = iClose(_Symbol, PERIOD_D1, 1);
cachedPivotP = (prevHigh + prevLow + prevClose) / 3.0;
cachedPivotR1 = (cachedPivotP * 2) - prevLow;
cachedPivotR2 = cachedPivotP + (prevHigh - prevLow);
cachedPivotS1 = (cachedPivotP * 2) - prevHigh;
cachedPivotS2 = cachedPivotP - (prevHigh - prevLow);
cachedPivotDay = today;
}
p = cachedPivotP;
r1 = cachedPivotR1;
r2 = cachedPivotR2;
s1 = cachedPivotS1;
s2 = cachedPivotS2;
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
@@ -77,22 +279,25 @@ int OnInit()
// Initialize trade object
Trade.SetExpertMagicNumber(InpMagicNumber);
Trade.SetDeviationInPoints(InpSlippage);
Trade.SetTypeFilling(ORDER_FILLING_IOC);
Trade.SetTypeFilling(GetMarketFilling()); // 1.3 — query symbol capability
eaInitTime = TimeCurrent();
// Initialize trend filter
if(InpUseTrendFilter)
TrendMAHandle = iMA(_Symbol, _Period, InpTrendMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
// Initialize volatility filters
if(InpUseVolatilityFilter)
ATRHandle = iATR(_Symbol, _Period, InpATRPeriod);
// Initialize volatility filters — always init ATR (ranging + pivot threshold need it)
ATRHandle = iATR(_Symbol, _Period, InpATRPeriod);
if(InpUseADXFilter)
ADXHandle = iADX(_Symbol, _Period, InpADXPeriod);
lastBarTime = iTime(_Symbol, _Period, 0);
LoadDailyState(); // 2.7 pre-work — safe: these helpers initialize to 0 if keys missing
Print("=== MultiSignal Confluence EA v1.16 Initialized ===");
Print("=== MultiSignal Confluence EA v1.18 Initialized ===");
Print("Symbol: ", _Symbol);
Print("Magic: ", InpMagicNumber);
Print("Min Confluence: ", InpMinConfluence);
@@ -201,85 +406,176 @@ void CountOpenPositions()
bool CheckTrendFilter(int signalType)
{
if(!InpUseTrendFilter) return true;
if(TrendMAHandle == INVALID_HANDLE) return true;
// 1.4 — fail closed when the filter can't evaluate
if(TrendMAHandle == INVALID_HANDLE)
{
if(InpDebugMode) LogS("Trend filter BLOCKED: MA handle invalid");
return false;
}
double maValue[1];
if(CopyBuffer(TrendMAHandle, 0, 0, 1, maValue) <= 0) return true;
if(CopyBuffer(TrendMAHandle, 0, 0, 1, maValue) <= 0)
{
if(InpDebugMode) LogS("Trend filter BLOCKED: CopyBuffer failed");
return false;
}
double close = iClose(_Symbol, _Period, 0);
if(signalType == 1 && close < maValue[0]) // Buy but below MA
{
if(InpDebugMode) Print("Trend filter BLOCKED BUY (price below MA)");
if(InpDebugMode) LogS("Trend filter BLOCKED BUY (price below MA)");
return false;
}
if(signalType == -1 && close > maValue[0]) // Sell but above MA
{
if(InpDebugMode) Print("Trend filter BLOCKED SELL (price above MA)");
if(InpDebugMode) LogS("Trend filter BLOCKED SELL (price above MA)");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Check if Market is Ranging |
//+------------------------------------------------------------------+
bool CheckRangingMarket()
{
datetime currentBar = iTime(_Symbol, _Period, 0);
// Only check once per bar
if(currentBar == lastRangingCheck)
return isRangingMarket;
lastRangingCheck = currentBar;
// Get pivot levels — 1.7 cached
double p, r1, r2, s1, s2;
GetCachedPivots(p, r1, r2, s1, s2);
double close = iClose(_Symbol, _Period, 0);
double high = iHigh (_Symbol, _Period, 0);
double low = iLow (_Symbol, _Period, 0);
// 1.6 — ATR-scaled "near pivot" instead of arbitrary 0.2%
double atr = GetCurrentATR();
double proximity = (atr > 0) ? (atr * InpRangingATRFactor)
: (SymbolInfoDouble(_Symbol, SYMBOL_POINT) * InpPivotThresholdPoints);
bool nearSupport = (MathAbs(close - s1) <= proximity);
bool nearResistance = (MathAbs(close - r1) <= proximity);
bool inChannel = nearSupport || nearResistance;
// Also check if ADX is low (weak trend = ranging)
double adxValue[1];
bool adxWeak = false;
if(ADXHandle != INVALID_HANDLE && CopyBuffer(ADXHandle, 0, 0, 1, adxValue) > 0)
{
adxWeak = (adxValue[0] < 20.0); // ADX below 20 suggests ranging
}
// Count consecutive bars in channel
if(inChannel)
{
rangingBarsCounter++;
}
else
{
rangingBarsCounter = 0;
}
// If 5+ consecutive bars in channel AND weak ADX = ranging
bool wasRanging = isRangingMarket;
isRangingMarket = (rangingBarsCounter >= 5 && adxWeak);
if(isRangingMarket && !wasRanging && InpDebugMode)
{
Print(_Symbol, " RANGING MARKET DETECTED - ADX: ", DoubleToString(adxValue[0], 1),
" Bars in channel: ", rangingBarsCounter);
}
else if(!isRangingMarket && wasRanging && InpDebugMode)
{
Print(_Symbol, " MARKET NOW TRENDING - resuming normal trading");
}
return isRangingMarket;
}
//+------------------------------------------------------------------+
//| Check Volatility Filter (Anti-Chop) |
//+------------------------------------------------------------------+
bool CheckVolatilityFilter()
{
// Check ATR (volatility) filter
// Check if market is ranging (use cached result)
bool ranging = isRangingMarket;
// Apply stricter thresholds when ranging
double effectiveMinATR = ranging ? InpMinATRPercent * 1.5 : InpMinATRPercent;
double effectiveMinADX = ranging ? InpMinADX + 5.0 : InpMinADX;
if(ranging && InpDebugMode)
{
Print(_Symbol, " RANGING MODE - Using stricter thresholds: ATR>=",
DoubleToString(effectiveMinATR, 2), "% ADX>=", DoubleToString(effectiveMinADX, 1));
}
// Check ATR (volatility) filter — 1.4 fail closed
if(InpUseVolatilityFilter)
{
if(ATRHandle == INVALID_HANDLE)
{
if(InpDebugMode) Print("Volatility filter: ATR handle invalid, allowing trade");
return true;
if(InpDebugMode) LogS("Volatility filter BLOCKED: ATR handle invalid");
return false;
}
double atrValue[1];
if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0)
{
if(InpDebugMode) Print("Volatility filter: Failed to get ATR, allowing trade");
return true;
if(InpDebugMode) LogS("Volatility filter BLOCKED: CopyBuffer failed");
return false;
}
double close = iClose(_Symbol, _Period, 0);
double atrPercent = (atrValue[0] / close) * 100;
if(atrPercent < InpMinATRPercent)
if(close <= 0)
{
if(InpDebugMode) Print("Volatility filter BLOCKED: ATR too low (",
DoubleToString(atrPercent, 2), "% < ",
DoubleToString(InpMinATRPercent, 2), "%) - Narrow bands/choppy market");
if(InpDebugMode) LogS("Volatility filter BLOCKED: invalid close price");
return false;
}
double atrPercent = (atrValue[0] / close) * 100;
if(atrPercent < effectiveMinATR)
{
if(InpDebugMode) LogS("Volatility filter BLOCKED: ATR " +
DoubleToString(atrPercent, 2) + "% < " +
DoubleToString(effectiveMinATR, 2) + "% (narrow/choppy)");
return false;
}
}
// Check ADX (trend strength) filter
// Check ADX (trend strength) filter — 1.4 fail closed
if(InpUseADXFilter)
{
if(ADXHandle == INVALID_HANDLE)
{
if(InpDebugMode) Print("ADX filter: Handle invalid, allowing trade");
return true;
if(InpDebugMode) LogS("ADX filter BLOCKED: handle invalid");
return false;
}
double adxValue[1];
if(CopyBuffer(ADXHandle, 0, 0, 1, adxValue) <= 0)
{
if(InpDebugMode) Print("ADX filter: Failed to get ADX, allowing trade");
return true;
if(InpDebugMode) LogS("ADX filter BLOCKED: CopyBuffer failed");
return false;
}
if(adxValue[0] < InpMinADX)
if(adxValue[0] < effectiveMinADX)
{
if(InpDebugMode) Print("ADX filter BLOCKED: Trend too weak (ADX ",
DoubleToString(adxValue[0], 1), " < ",
DoubleToString(InpMinADX, 1), ")");
if(InpDebugMode) LogS("ADX filter BLOCKED: ADX " +
DoubleToString(adxValue[0], 1) + " < " +
DoubleToString(effectiveMinADX, 1));
return false;
}
}
return true;
}
@@ -313,13 +609,13 @@ void CheckSignals(int &buyCount, int &sellCount, string &sources)
double high = iHigh(_Symbol, _Period, 0);
double low = iLow(_Symbol, _Period, 0);
//--- PIVOT SIGNALS
//--- PIVOT SIGNALS — 1.7 cached
double p, r1, r2, s1, s2;
CalculatePivots(p, r1, r2, s1, s2);
// Dynamic threshold based on symbol point value
GetCachedPivots(p, r1, r2, s1, s2);
// 1.5 — point-scaled, symbol-agnostic threshold
double point = GetSymbolPoint();
double threshold = 0.0010; // 10 pips threshold for strong signals
double threshold = InpPivotThresholdPoints * point;
// Calculate distance to each level
double distToS1 = MathAbs(close - s1);
@@ -442,31 +738,35 @@ void CheckSignals(int &buyCount, int &sellCount, string &sources)
double CalculateLotSize(double slPoints)
{
if(!InpUseRiskPercent) return InpLotSize;
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
// 1.2 — size against lower of balance/equity so drawdown actually scales us down
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double sizingBase = MathMin(balance, equity);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if(tickSize <= 0 || slPoints <= 0) return InpLotSize;
// Calculate risk amount
double riskAmount = accountBalance * InpRiskPercent / 100.0;
double riskAmount = sizingBase * InpRiskPercent / 100.0;
// Calculate lot size: RiskAmount / (SL_Points * TickValue / TickSize)
double lots = riskAmount / (slPoints * tickValue / tickSize);
// Normalize to lot step
lots = MathFloor(lots / lotStep) * lotStep;
// Apply min/max limits
lots = MathMax(minLot, MathMin(maxLot, lots));
Print("Risk Lot Calc: Balance=", accountBalance, " Risk%=", InpRiskPercent,
" SL=", slPoints, " Lots=", lots);
LogS("Risk Lot: base=" + DoubleToString(sizingBase, 2) + " (bal=" + DoubleToString(balance, 2) +
", eq=" + DoubleToString(equity, 2) + ") Risk%=" + DoubleToString(InpRiskPercent, 2) +
" SL=" + IntegerToString((int)slPoints) + " Lots=" + DoubleToString(lots, 2));
return lots;
}
@@ -643,48 +943,81 @@ void OpenSellPosition(double strength, string sources)
}
//+------------------------------------------------------------------+
//| Check Daily Drawdown Protection |
//| Check Drawdown — daily + weekly, per-EA (1.1) |
//+------------------------------------------------------------------+
bool CheckDailyDrawdown()
{
static bool warningPrinted = false;
if(InpMaxDailyDrawdown <= 0)
return true; // Protection disabled
datetime today = TimeCurrent() / 86400 * 86400;
static bool dailyWarned = false;
static bool weeklyWarned = false;
// --- Daily rollover ---
datetime today = TodayStartBroker();
if(today != lastEquityReset)
{
// New day - reset equity tracking
dailyStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
lastEquityReset = today;
warningPrinted = false; // Reset warning for new day
Print("Daily equity reset: $", DoubleToString(dailyStartEquity, 2));
return true;
lastEquityReset = today;
realizedPnLToday = 0;
lastRealizedScan = 0;
dailyWarned = false;
LogS("Daily equity reset: $" + DoubleToString(dailyStartEquity, 2));
SaveDailyState();
}
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
if(dailyStartEquity <= 0)
return true;
double drawdownPercent = (dailyStartEquity - currentEquity) / dailyStartEquity * 100;
bool overLimit = (drawdownPercent >= InpMaxDailyDrawdown);
if(overLimit)
// --- Weekly rollover (Monday 00:00 broker) ---
datetime weekStart = WeekStartBroker();
if(weekStart != lastWeeklyReset)
{
if(!warningPrinted)
weeklyStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
lastWeeklyReset = weekStart;
realizedPnLWeek = 0;
lastWeeklyScan = 0;
weeklyWarned = false;
LogS("Weekly equity reset: $" + DoubleToString(weeklyStartEquity, 2));
SaveDailyState();
}
double floating = GetFloatingPnL();
// --- Daily check ---
if(InpMaxDailyDrawdown > 0 && dailyStartEquity > 0)
{
double dailyPnL = GetRealizedPnLToday() + floating;
double dailyLimit = dailyStartEquity * InpMaxDailyDrawdown / 100.0;
if(dailyPnL <= -dailyLimit)
{
Print("⚠️ DAILY DRAWDOWN LIMIT REACHED: ", DoubleToString(drawdownPercent, 2),
"% (Limit: ", InpMaxDailyDrawdown, "%)");
warningPrinted = true;
if(!dailyWarned)
{
LogS("⚠️ DAILY DD HIT: $" + DoubleToString(dailyPnL, 2) +
" (limit: -$" + DoubleToString(dailyLimit, 2) + ")");
SendNotification("Confluence " + _Symbol + ": DAILY DD reached ($" +
DoubleToString(dailyPnL, 2) + ")");
dailyWarned = true;
}
return false;
}
return false; // Block new trades
dailyWarned = false;
}
else
// --- Weekly check ---
if(InpMaxWeeklyDrawdown > 0 && weeklyStartEquity > 0)
{
warningPrinted = false; // Reset when below limit
double weeklyPnL = GetRealizedPnLWeek() + floating;
double weeklyLimit = weeklyStartEquity * InpMaxWeeklyDrawdown / 100.0;
if(weeklyPnL <= -weeklyLimit)
{
if(!weeklyWarned)
{
LogS("⚠️ WEEKLY DD HIT: $" + DoubleToString(weeklyPnL, 2) +
" (limit: -$" + DoubleToString(weeklyLimit, 2) + ")");
SendNotification("Confluence " + _Symbol + ": WEEKLY DD reached ($" +
DoubleToString(weeklyPnL, 2) + ")");
weeklyWarned = true;
}
return false;
}
weeklyWarned = false;
}
return true;
}
@@ -718,9 +1051,17 @@ void OnTick()
// Only process on new bar
if(!isNewBar)
return;
lastBarTime = currentBarTime;
// 1.4 — warm-up gate: block signals until indicators have enough data
if(!IsWarmedUp())
{
if(InpDebugMode) LogS("Warming up (" + IntegerToString(iBars(_Symbol, _Period)) +
"/" + IntegerToString(InpWarmupBars) + " bars)");
return;
}
// Count positions (only for current symbol)
CountOpenPositions();
@@ -760,15 +1101,22 @@ void OnTick()
if(strength < -1.0) strength = -1.0;
// Check minimum strength
if(MathAbs(strength) < InpMinStrength)
{
if(InpDebugMode) Print(_Symbol, " Strength too low: ", DoubleToString(MathAbs(strength), 2), " < ", InpMinStrength);
return;
}
// Check volatility filter (prevents trading in narrow bands / choppy markets)
if(!CheckVolatilityFilter())
return;
if(MathAbs(strength) < InpMinStrength)
{
if(InpDebugMode) Print(_Symbol, " Strength too low: ", DoubleToString(MathAbs(strength), 2), " < ", InpMinStrength);
return;
}
// Check if market is ranging (new ranging detection)
bool ranging = CheckRangingMarket();
if(ranging)
{
if(InpDebugMode) Print(_Symbol, " RANGING MARKET - stricter filters applied");
}
// Check volatility filter (prevents trading in narrow bands / choppy markets)
if(!CheckVolatilityFilter())
return;
// BUY Signal
if(buyCount >= InpMinConfluence && (!InpRequireAllAgree || sellCount == 0))