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:
Regular → Executable
+453
-105
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user