diff --git a/Data/FibonacciEngine.mqh b/Data/FibonacciEngine.mqh new file mode 100644 index 0000000..8af09b8 --- /dev/null +++ b/Data/FibonacciEngine.mqh @@ -0,0 +1,163 @@ +//+------------------------------------------------------------------+ +//| Data/FibonacciEngine.mqh | +//| Fibonacci Retracement & Extension Analysis | +//| Identifies key S/R levels: 0.236, 0.382, 0.5, 0.618, 0.786 | +//| Uses swing highs/lows for accurate level placement | +//+------------------------------------------------------------------+ +#ifndef __FIBONACCI_ENGINE_MQH__ +#define __FIBONACCI_ENGINE_MQH__ + +#include "../Core/Config.mqh" +#include "../Core/State.mqh" +#include "../Data/PriceEngine.mqh" + +class CFibonacciEngine +{ +private: + ENUM_TIMEFRAMES m_tf; + double m_levels[7]; // 0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0 + double m_levelValues[7]; + bool m_levelsValid; + double m_swingHigh; + double m_swingLow; + datetime m_swingHighTime; + datetime m_swingLowTime; + +public: + CFibonacciEngine() : m_levelsValid(false), m_swingHigh(0), m_swingLow(0) {} + + bool Init(ENUM_TIMEFRAMES tf) + { + m_tf = tf; + m_levels[0] = 0.0; + m_levels[1] = 0.236; + m_levels[2] = 0.382; + m_levels[3] = 0.500; + m_levels[4] = 0.618; + m_levels[5] = 0.786; + m_levels[6] = 1.0; + Print("[FibonacciEngine] Initialized on ", EnumToString(tf)); + return true; + } + + void Calculate() + { + // Find significant swing high and low + FindSwingPoints(); + + if(m_swingHigh <= m_swingLow || m_swingHigh == 0 || m_swingLow == 0) + { + m_levelsValid = false; + return; + } + + double range = m_swingHigh - m_swingLow; + for(int i = 0; i < 7; i++) + { + m_levelValues[i] = m_swingHigh - (range * m_levels[i]); + } + m_levelsValid = true; + } + + // Check if price is near a key fibonacci level (for entries) + bool IsNearFibLevel(double price, double tolerance, int &nearestLevel) + { + if(!m_levelsValid) return false; + + nearestLevel = -1; + double minDist = DBL_MAX; + + // Most important levels for entries: 0.382, 0.5, 0.618, 0.786 + int keyLevels[] = {2, 3, 4, 5}; + + for(int i = 0; i < ArraySize(keyLevels); i++) + { + int idx = keyLevels[i]; + double dist = MathAbs(price - m_levelValues[idx]); + if(dist < minDist) + { + minDist = dist; + nearestLevel = idx; + } + } + + double atr = iATR(_Symbol, m_tf, 14); + if(atr == 0) atr = _Point * 50; + + return (minDist <= tolerance * atr); + } + + // Get the strongest level (0.618 golden ratio) + double GetGoldenRatioLevel() const + { + if(!m_levelsValid) return 0; + return m_levelValues[4]; // 0.618 + } + + // Get 0.786 level (deep retracement - final support/resistance) + double GetDeepLevel() const + { + if(!m_levelsValid) return 0; + return m_levelValues[5]; // 0.786 + } + + // Check if price broke a fib level (trend continuation signal) + bool DidBreakLevel(double prevClose, double currClose, int levelIdx) + { + if(!m_levelsValid || levelIdx < 0 || levelIdx >= 7) return false; + + double level = m_levelValues[levelIdx]; + return ((prevClose < level && currClose > level) || + (prevClose > level && currClose < level)); + } + + // Get fibonacci extension for TP calculation + double GetExtension(double multiplier) + { + if(!m_levelsValid) return 0; + double range = m_swingHigh - m_swingLow; + return m_swingHigh + (range * multiplier); + } + + string GetLevelName(int idx) const + { + if(idx < 0 || idx >= 7) return "Invalid"; + string names[] = {"0.0", "0.236", "0.382", "0.5", "0.618", "0.786", "1.0"}; + return names[idx]; + } + + bool IsValid() const { return m_levelsValid; } + double GetSwingHigh() const { return m_swingHigh; } + double GetSwingLow() const { return m_swingLow; } + +private: + void FindSwingPoints() + { + MqlRates rates[]; + ArraySetAsSeries(rates, true); + int copied = CopyRates(_Symbol, m_tf, 0, 100, rates); + if(copied < 20) { m_levelsValid = false; return; } + + // Find swing high (highest high in last 50 bars) + m_swingHigh = 0; + m_swingHighTime = 0; + int highIdx = iHighest(_Symbol, m_tf, MODE_HIGH, 50, 1); + if(highIdx >= 0) + { + m_swingHigh = iHigh(_Symbol, m_tf, highIdx); + m_swingHighTime = iTime(_Symbol, m_tf, highIdx); + } + + // Find swing low (lowest low in last 50 bars) + m_swingLow = DBL_MAX; + m_swingLowTime = 0; + int lowIdx = iLowest(_Symbol, m_tf, MODE_LOW, 50, 1); + if(lowIdx >= 0) + { + m_swingLow = iLow(_Symbol, m_tf, lowIdx); + m_swingLowTime = iTime(_Symbol, m_tf, lowIdx); + } + } +}; + +#endif // __FIBONACCI_ENGINE_MQH__ diff --git a/Data/LiquidityEngine.mqh b/Data/LiquidityEngine.mqh new file mode 100644 index 0000000..e64e59b --- /dev/null +++ b/Data/LiquidityEngine.mqh @@ -0,0 +1,306 @@ +//+------------------------------------------------------------------+ +//| Data/LiquidityEngine.mqh | +//| Smart Money Concepts: Order Blocks, Liquidity Sweeps, FVG | +//| Identifies institutional levels for high-probability entries | +//+------------------------------------------------------------------+ +#ifndef __LIQUIDITY_ENGINE_MQH__ +#define __LIQUIDITY_ENGINE_MQH__ + +#include "../Core/Config.mqh" +#include "../Core/State.mqh" + +struct OrderBlock +{ + double high; + double low; + double open; + double close; + datetime time; + bool isBullish; // true = bullish OB (buy zone) + bool isValid; + int strength; // 1-3 based on volume and follow-through +}; + +struct LiquidityPool +{ + double level; + datetime time; + bool isBuySide; // true = buy-side liquidity (equal highs) + bool isSwept; // true = liquidity was swept/taken + int touchCount; // how many times price touched this level +}; + +class CLiquidityEngine +{ +private: + ENUM_TIMEFRAMES m_tf; + OrderBlock m_bullishOBs[]; + OrderBlock m_bearishOBs[]; + LiquidityPool m_pools[]; + int m_maxOBs; + int m_lookback; + +public: + CLiquidityEngine() : m_maxOBs(5), m_lookback(50) {} + + bool Init(ENUM_TIMEFRAMES tf) + { + m_tf = tf; + ArrayResize(m_bullishOBs, m_maxOBs); + ArrayResize(m_bearishOBs, m_maxOBs); + ArrayResize(m_pools, 10); + Print("[LiquidityEngine] Initialized on ", EnumToString(tf)); + return true; + } + + void Update() + { + FindOrderBlocks(); + FindLiquidityPools(); + } + + // Check if price is at a valid order block + bool IsAtOrderBlock(double price, bool wantBullish, OrderBlock &outOB) + { + if(wantBullish) + { + for(int i = 0; i < ArraySize(m_bullishOBs); i++) + { + if(!m_bullishOBs[i].isValid) continue; + if(price >= m_bullishOBs[i].low && price <= m_bullishOBs[i].high) + { + outOB = m_bullishOBs[i]; + return true; + } + } + } + else + { + for(int i = 0; i < ArraySize(m_bearishOBs); i++) + { + if(!m_bearishOBs[i].isValid) continue; + if(price >= m_bearishOBs[i].low && price <= m_bearishOBs[i].high) + { + outOB = m_bearishOBs[i]; + return true; + } + } + } + return false; + } + + // Check for liquidity sweep (stop hunt) - reversal signal + bool WasLiquiditySwept(int barsBack, bool &sweptBuySide) + { + MqlRates rates[]; + ArraySetAsSeries(rates, true); + if(CopyRates(_Symbol, m_tf, 0, barsBack + 5, rates) < barsBack + 5) return false; + + // Check for sweep of equal highs/lows + double recentHigh = 0, recentLow = DBL_MAX; + for(int i = 1; i <= barsBack; i++) + { + if(rates[i].high > recentHigh) recentHigh = rates[i].high; + if(rates[i].low < recentLow) recentLow = rates[i].low; + } + + // Buy-side liquidity sweep (swept highs then reversed down) + if(rates[0].high > recentHigh && rates[0].close < rates[1].close) + { + sweptBuySide = true; + return true; + } + + // Sell-side liquidity sweep (swept lows then reversed up) + if(rates[0].low < recentLow && rates[0].close > rates[1].close) + { + sweptBuySide = false; + return true; + } + + return false; + } + + // Check for Fair Value Gap (FVG) - imbalance zone + bool HasFVG(int barsBack, bool &isBullishFVG, double &fvgTop, double &fvgBottom) + { + MqlRates rates[]; + ArraySetAsSeries(rates, true); + if(CopyRates(_Symbol, m_tf, 0, barsBack + 3, rates) < barsBack + 3) return false; + + for(int i = 1; i < barsBack; i++) + { + // Bullish FVG: current low > previous high (gap up) + if(rates[i].low > rates[i+1].high) + { + isBullishFVG = true; + fvgTop = rates[i].low; + fvgBottom = rates[i+1].high; + return true; + } + // Bearish FVG: current high < previous low (gap down) + if(rates[i].high < rates[i+1].low) + { + isBullishFVG = false; + fvgTop = rates[i+1].low; + fvgBottom = rates[i].high; + return true; + } + } + return false; + } + + // Get the nearest untapped liquidity level + double GetNearestLiquidity(double currentPrice, bool above) + { + double nearest = 0; + double minDist = DBL_MAX; + + for(int i = 0; i < ArraySize(m_pools); i++) + { + if(m_pools[i].isSwept) continue; + + if(above && m_pools[i].level > currentPrice) + { + double dist = m_pools[i].level - currentPrice; + if(dist < minDist) { minDist = dist; nearest = m_pools[i].level; } + } + else if(!above && m_pools[i].level < currentPrice) + { + double dist = currentPrice - m_pools[i].level; + if(dist < minDist) { minDist = dist; nearest = m_pools[i].level; } + } + } + return nearest; + } + +private: + void FindOrderBlocks() + { + MqlRates rates[]; + ArraySetAsSeries(rates, true); + int copied = CopyRates(_Symbol, m_tf, 0, m_lookback, rates); + if(copied < 10) return; + + int bullCount = 0, bearCount = 0; + + for(int i = 2; i < copied - 1 && (bullCount < m_maxOBs || bearCount < m_maxOBs); i++) + { + // Bullish Order Block: bearish candle before strong bullish move + if(rates[i].close < rates[i].open && rates[i-1].close > rates[i-1].open * 1.01) + { + // Strong bullish follow-through + if(bullCount < m_maxOBs) + { + m_bullishOBs[bullCount].high = rates[i].high; + m_bullishOBs[bullCount].low = rates[i].low; + m_bullishOBs[bullCount].open = rates[i].open; + m_bullishOBs[bullCount].close = rates[i].close; + m_bullishOBs[bullCount].time = rates[i].time; + m_bullishOBs[bullCount].isBullish = true; + m_bullishOBs[bullCount].isValid = true; + m_bullishOBs[bullCount].strength = CalculateStrength(rates, i); + bullCount++; + } + } + + // Bearish Order Block: bullish candle before strong bearish move + if(rates[i].close > rates[i].open && rates[i-1].close < rates[i-1].open * 0.99) + { + // Strong bearish follow-through + if(bearCount < m_maxOBs) + { + m_bearishOBs[bearCount].high = rates[i].high; + m_bearishOBs[bearCount].low = rates[i].low; + m_bearishOBs[bearCount].open = rates[i].open; + m_bearishOBs[bearCount].close = rates[i].close; + m_bearishOBs[bearCount].time = rates[i].time; + m_bearishOBs[bearCount].isBullish = false; + m_bearishOBs[bearCount].isValid = true; + m_bearishOBs[bearCount].strength = CalculateStrength(rates, i); + bearCount++; + } + } + } + } + + void FindLiquidityPools() + { + MqlRates rates[]; + ArraySetAsSeries(rates, true); + int copied = CopyRates(_Symbol, m_tf, 0, m_lookback, rates); + if(copied < 20) return; + + int poolCount = 0; + + // Find equal highs (buy-side liquidity) + for(int i = 5; i < copied - 5 && poolCount < 10; i++) + { + double currHigh = rates[i].high; + bool isEqualHigh = false; + + for(int j = i + 2; j < i + 10 && j < copied; j++) + { + if(MathAbs(rates[j].high - currHigh) < _Point * 10) + { + isEqualHigh = true; + break; + } + } + + if(isEqualHigh) + { + m_pools[poolCount].level = currHigh; + m_pools[poolCount].time = rates[i].time; + m_pools[poolCount].isBuySide = true; + m_pools[poolCount].isSwept = (rates[0].high > currHigh + _Point * 5); + m_pools[poolCount].touchCount = 2; + poolCount++; + } + } + + // Find equal lows (sell-side liquidity) + for(int i = 5; i < copied - 5 && poolCount < 10; i++) + { + double currLow = rates[i].low; + bool isEqualLow = false; + + for(int j = i + 2; j < i + 10 && j < copied; j++) + { + if(MathAbs(rates[j].low - currLow) < _Point * 10) + { + isEqualLow = true; + break; + } + } + + if(isEqualLow) + { + m_pools[poolCount].level = currLow; + m_pools[poolCount].time = rates[i].time; + m_pools[poolCount].isBuySide = false; + m_pools[poolCount].isSwept = (rates[0].low < currLow - _Point * 5); + m_pools[poolCount].touchCount = 2; + poolCount++; + } + } + } + + int CalculateStrength(MqlRates &rates[], int idx) + { + int strength = 1; + + // Volume check + double avgVol = 0; + for(int i = idx; i < idx + 5 && i < ArraySize(rates); i++) + avgVol += (double)rates[i].tick_volume; + avgVol /= 5.0; + + if(rates[idx].tick_volume > avgVol * 1.5) strength++; + if(rates[idx].tick_volume > avgVol * 2.0) strength++; + + return MathMin(strength, 3); + } +}; + +#endif // __LIQUIDITY_ENGINE_MQH__ diff --git a/Logic/MicroTrigger.mqh b/Logic/MicroTrigger.mqh index 31cc48a..227c4a3 100644 --- a/Logic/MicroTrigger.mqh +++ b/Logic/MicroTrigger.mqh @@ -1,5 +1,7 @@ //+------------------------------------------------------------------+ //| Logic/MicroTrigger.mqh | +//| Enhanced LTF Entry Logic with Fibonacci, Liquidity, Price Action | +//| Higher probability entries using multiple confluences | //+------------------------------------------------------------------+ #ifndef __MICRO_TRIGGER_MQH__ #define __MICRO_TRIGGER_MQH__ @@ -8,6 +10,8 @@ #include "../Core/State.mqh" #include "../Data/PriceEngine.mqh" #include "../Data/Volatility.mqh" +#include "../Data/FibonacciEngine.mqh" +#include "../Data/LiquidityEngine.mqh" #include "../Core/Logger.mqh" extern CLogger g_logger; @@ -18,59 +22,176 @@ class CMicroTrigger private: ENUM_TIMEFRAMES m_ltf; CPriceEngine *m_price; + CFibonacciEngine m_fib; + CLiquidityEngine m_liquidity; public: bool Init(ENUM_TIMEFRAMES ltf, CPriceEngine &price) { - m_ltf = ltf; m_price = GetPointer(price); - Print("[MicroTrigger] LTF entry logic initialized on ", EnumToString(ltf)); + m_ltf = ltf; + m_price = GetPointer(price); + + if(!m_fib.Init(ltf)) + { + Print("[MicroTrigger] FibonacciEngine init failed"); + return false; + } + + if(!m_liquidity.Init(ltf)) + { + Print("[MicroTrigger] LiquidityEngine init failed"); + return false; + } + + Print("[MicroTrigger] LTF entry logic initialized on ", EnumToString(ltf), " (with Fib + Liquidity)"); return true; } + void Release() {} + void GenerateSignal(SignalData &signal, const EAState &state, CPriceEngine &price) { - signal.isValid = false; signal.isBuy = false; signal.pattern = PATTERN_NONE; - signal.rejectionReason = ""; signal.signalTime = TimeCurrent(); signal.atrValue = 0; + signal.isValid = false; + signal.isBuy = false; + signal.pattern = PATTERN_NONE; + signal.rejectionReason = ""; + signal.signalTime = TimeCurrent(); + signal.atrValue = 0; + + // Update fibonacci and liquidity levels + m_fib.Calculate(); + m_liquidity.Update(); + + // Check HTF bias validity if(state.currentBias == BIAS_NEUTRAL && state.currentRegime != REGIME_RANGE) - { signal.rejectionReason = "HTF Bias Neutral + Not Range Mode"; return; } + { + signal.rejectionReason = "HTF Bias Neutral + Not Range Mode"; + return; + } + MqlRates bars[4]; if(!price.GetClosedBar(m_ltf, 1, bars[1]) || !price.GetClosedBar(m_ltf, 2, bars[2])) - { signal.rejectionReason = "Failed to load LTF closed bars"; return; } + { + signal.rejectionReason = "Failed to load LTF closed bars"; + return; + } + + // === CONFLUENCE SCORING SYSTEM === + // Each confluence adds to score. Need minimum score for valid signal. + int confluenceScore = 0; + bool isBuy = false; + ENUM_PATTERN detectedPattern = PATTERN_NONE; + string patternName = ""; + + // 1. Check Price Action Patterns (0-3 points) if(CheckPinBar(bars[1], state)) { - signal.pattern = PATTERN_PIN_BAR; signal.patternName = "Pin Bar"; - signal.isBuy = (bars[1].close > bars[1].open); - if(ValidateDirection(signal, state)) { CalculateLevels(signal, bars[1], state); return; } + detectedPattern = PATTERN_PIN_BAR; + patternName = "Pin Bar"; + isBuy = (bars[1].close > bars[1].open); + confluenceScore += 2; } - if(!price.GetClosedBar(m_ltf, 2, bars[2])) { signal.rejectionReason = "Failed to load bar[2]"; return; } - if(CheckEngulfing(bars[1], bars[2])) + else if(CheckEngulfing(bars[1], bars[2])) { - signal.pattern = PATTERN_ENGULFING; signal.patternName = "Engulfing"; - signal.isBuy = (bars[1].close > bars[1].open); - if(ValidateDirection(signal, state)) { CalculateLevels(signal, bars[1], state); return; } + detectedPattern = PATTERN_ENGULFING; + patternName = "Engulfing"; + isBuy = (bars[1].close > bars[1].open); + confluenceScore += 2; } - if(price.GetClosedBar(m_ltf, 3, bars[3])) + else if(price.GetClosedBar(m_ltf, 3, bars[3]) && CheckInsideBarBreakout(bars[1], bars[2], bars[3])) { - if(CheckInsideBarBreakout(bars[1], bars[2], bars[3])) + detectedPattern = PATTERN_INSIDE_BAR; + patternName = "Inside Bar Breakout"; + isBuy = (bars[1].close > bars[2].high); + confluenceScore += 1; + } + + if(confluenceScore == 0) + { + signal.rejectionReason = "No valid price action pattern"; + return; + } + + // 2. Check Fibonacci Level (0-2 points) + int fibLevel = -1; + if(m_fib.IsNearFibLevel(bars[1].close, 0.5, fibLevel)) + { + confluenceScore += 2; + patternName += " + Fib" + m_fib.GetLevelName(fibLevel); + } + else if(m_fib.IsNearFibLevel(bars[1].close, 1.0, fibLevel)) + { + confluenceScore += 1; + patternName += " + Fib" + m_fib.GetLevelName(fibLevel); + } + + // 3. Check Order Block (0-2 points) + OrderBlock ob; + if(m_liquidity.IsAtOrderBlock(bars[1].close, isBuy, ob)) + { + confluenceScore += ob.strength; + patternName += " + OB"; + } + + // 4. Check Liquidity Sweep (0-3 points) - STRONG signal + bool sweptBuySide; + if(m_liquidity.WasLiquiditySwept(3, sweptBuySide)) + { + // If liquidity was swept and we're trading in opposite direction + if((isBuy && !sweptBuySide) || (!isBuy && sweptBuySide)) { - signal.pattern = PATTERN_INSIDE_BAR; signal.patternName = "Inside Bar Breakout"; - signal.isBuy = (bars[1].close > bars[2].high); - if(ValidateDirection(signal, state)) { CalculateLevels(signal, bars[1], state); return; } + confluenceScore += 3; + patternName += " + Liquidity Sweep"; } } - signal.rejectionReason = "No valid price action pattern"; + + // 5. Check FVG (Fair Value Gap) (0-1 points) + bool isBullishFVG; + double fvgTop, fvgBottom; + if(m_liquidity.HasFVG(10, isBullishFVG, fvgTop, fvgBottom)) + { + if((isBuy && isBullishFVG) || (!isBuy && !isBullishFVG)) + { + confluenceScore += 1; + patternName += " + FVG"; + } + } + + // === VALIDATION === + // Minimum confluence score required + int minScore = (state.currentRegime == REGIME_TREND) ? 4 : 3; + + if(confluenceScore < minScore) + { + signal.rejectionReason = StringFormat("Confluence score %d < minimum %d", confluenceScore, minScore); + return; + } + + // Direction validation + if(!ValidateDirection(isBuy, state)) + { + signal.rejectionReason = isBuy ? "Bullish signal rejected (HTF Bias: BEAR)" : "Bearish signal rejected (HTF Bias: BULL)"; + return; + } + + // Build signal + signal.isValid = true; + signal.isBuy = isBuy; + signal.pattern = detectedPattern; + signal.patternName = patternName + StringFormat(" [Score:%d]", confluenceScore); + + CalculateLevels(signal, bars[1], state); } private: - bool ValidateDirection(SignalData &signal, const EAState &state) + bool ValidateDirection(bool isBuy, const EAState &state) { if(state.currentRegime == REGIME_RANGE) return true; - if(state.currentBias == BIAS_BULL && !signal.isBuy) - { signal.isValid = false; signal.rejectionReason = "Bearish signal rejected (HTF Bias: BULL)"; return false; } - if(state.currentBias == BIAS_BEAR && signal.isBuy) - { signal.isValid = false; signal.rejectionReason = "Bullish signal rejected (HTF Bias: BEAR)"; return false; } - signal.isValid = true; return true; + if(state.currentBias == BIAS_BULL && !isBuy) return false; + if(state.currentBias == BIAS_BEAR && isBuy) return false; + return true; } + bool CheckPinBar(const MqlRates &bar, const EAState &state) { double body = MathAbs(bar.close - bar.open); @@ -78,6 +199,7 @@ private: double lowerWick = MathMin(bar.open, bar.close) - bar.low; double range = bar.high - bar.low; if(range == 0 || body == 0) return false; + bool bullish = (bar.close > bar.open); if(bullish) { @@ -94,6 +216,7 @@ private: return wickOK && closePos && atLevel; } } + bool CheckEngulfing(const MqlRates &curr, const MqlRates &prev) { bool bullish = (curr.close > prev.open && curr.open < prev.close); @@ -101,6 +224,7 @@ private: if(!bullish && !bearish) return false; return (curr.tick_volume >= prev.tick_volume * ENGULF_VOLUME_MULT); } + bool CheckInsideBarBreakout(const MqlRates &breakout, const MqlRates &inside, const MqlRates &mother) { bool isInside = (inside.high < mother.high && inside.low > mother.low); @@ -109,41 +233,61 @@ private: bool bearBreak = (breakout.close < inside.low); return (bullBreak || bearBreak); } + bool IsAtKeyLevel(const MqlRates &bar, const EAState &state, bool isBullish) { double proximity = state.assetProfile.atrMultiplierSL * g_volatility.GetATR() * 0.5; if(MathAbs(bar.close - state.vwapState.vwapValue) <= proximity) return true; if(isBullish && MathAbs(bar.low - state.swingLow) <= proximity) return true; if(!isBullish && MathAbs(bar.high - state.swingHigh) <= proximity) return true; - int maHandle = iMA(_Symbol, m_ltf, 50, 0, MODE_EMA, PRICE_CLOSE); - if(maHandle != INVALID_HANDLE) - { - double maBuf[]; ArraySetAsSeries(maBuf, true); - if(CopyBuffer(maHandle, 0, 1, 1, maBuf) > 0) - { - double ema50 = maBuf[0]; - IndicatorRelease(maHandle); - if(MathAbs(bar.close - ema50) <= proximity) return true; - } - IndicatorRelease(maHandle); - } + + // Check fibonacci levels + int fibIdx; + if(m_fib.IsNearFibLevel(bar.close, 0.3, fibIdx)) return true; + return false; } + void CalculateLevels(SignalData &signal, const MqlRates &bar, const EAState &state) { double atr = g_volatility.GetATR(); if(atr <= 0) atr = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * 10; signal.atrValue = atr; + if(signal.isBuy) signal.entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); else signal.entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double slMult, tp1Mult, tp2Mult; if(state.currentRegime == REGIME_TREND) - { slMult = InpTrendATRMult; tp1Mult = InpTrendATRMult * 2.0; tp2Mult = InpTrendATRMult * 4.0; } + { + slMult = InpTrendATRMult; + tp1Mult = InpTrendATRMult * 2.0; + tp2Mult = InpTrendATRMult * 4.0; + } else - { slMult = InpRangeATRMult; tp1Mult = InpRangeATRMult * 1.5; tp2Mult = InpRangeATRMult * 2.5; } + { + slMult = InpRangeATRMult; + tp1Mult = InpRangeATRMult * 1.5; + tp2Mult = InpRangeATRMult * 2.5; + } + double slDist = atr * slMult; double tp1Dist = atr * tp1Mult; double tp2Dist = atr * tp2Mult; + + // Adjust TP based on fibonacci extensions if available + if(m_fib.IsValid()) + { + double fibExtension = m_fib.GetExtension(1.618); + if(fibExtension > 0) + { + if(signal.isBuy && fibExtension > signal.entryPrice + tp1Dist) + tp2Dist = fibExtension - signal.entryPrice; + else if(!signal.isBuy && fibExtension < signal.entryPrice - tp1Dist) + tp2Dist = signal.entryPrice - fibExtension; + } + } + if(signal.isBuy) { signal.slPrice = signal.entryPrice - slDist; @@ -156,6 +300,7 @@ private: signal.tp1Price = signal.entryPrice - tp1Dist; signal.tp2Price = signal.entryPrice - tp2Dist; } + signal.isValid = true; } }; diff --git a/Risk/PositionSizer.mqh b/Risk/PositionSizer.mqh index 7264c95..dc253ba 100644 --- a/Risk/PositionSizer.mqh +++ b/Risk/PositionSizer.mqh @@ -1,11 +1,16 @@ //+------------------------------------------------------------------+ //| Risk/PositionSizer.mqh | +//| ATR-based Position Sizing with Dynamic Risk Multiplier | +//| Reduces size after losses, increases after wins | //+------------------------------------------------------------------+ #ifndef __POSITION_SIZER_MQH__ #define __POSITION_SIZER_MQH__ #include "../Core/Config.mqh" #include "../Core/State.mqh" +#include "../Risk/Protection.mqh" + +extern CProtection g_protection; class CPositionSizer { @@ -16,21 +21,32 @@ private: public: bool Init(const AssetProfile &profile, double maxRisk) { - m_profile = profile; m_maxRiskPercent = maxRisk; - Print("[PositionSizer] Max risk per trade: ", maxRisk, "%"); + m_profile = profile; + m_maxRiskPercent = maxRisk; + Print("[PositionSizer] Max risk per trade: ", maxRisk, "% (with dynamic multiplier)"); return true; } + void Calculate(TradeParams ¶ms, const SignalData &signal, const EAState &state) { - params.isValid = false; params.rejectReason = ""; + params.isValid = false; + params.rejectReason = ""; + double equity = AccountInfoDouble(ACCOUNT_EQUITY); if(equity <= 0) { params.rejectReason = "Invalid account equity"; return; } - double riskAmount = equity * (m_maxRiskPercent / 100.0); + + // Apply dynamic risk multiplier based on recent performance + double riskMultiplier = g_protection.GetRiskMultiplier(); + double adjustedRiskPercent = m_maxRiskPercent * riskMultiplier; + + double riskAmount = equity * (adjustedRiskPercent / 100.0); double slDistance = MathAbs(signal.entryPrice - signal.slPrice); if(slDistance <= 0) { params.rejectReason = "Invalid SL distance"; return; } + double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); if(tickValue <= 0 || tickSize <= 0) { params.rejectReason = "Invalid tick value/size"; return; } + double slTicks = slDistance / tickSize; double lotSize = riskAmount / (slTicks * tickValue); double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); @@ -56,13 +72,17 @@ public: double finalSlTicks = slDistance / tickSize; double finalRisk = lotSize * finalSlTicks * tickValue; double finalRiskPercent = (finalRisk / equity) * 100.0; - if(finalRiskPercent > m_maxRiskPercent * 1.1) - { params.rejectReason = "Risk exceeds max"; return; } - params.lotSize = lotSize; params.riskAmount = finalRisk; - params.riskPercent = finalRiskPercent; params.slDistance = slDistance; + if(finalRiskPercent > adjustedRiskPercent * 1.1) + { params.rejectReason = "Risk exceeds adjusted max"; return; } + + params.lotSize = lotSize; + params.riskAmount = finalRisk; + params.riskPercent = finalRiskPercent; + params.slDistance = slDistance; params.tp1Distance = MathAbs(signal.tp1Price - signal.entryPrice); params.tp2Distance = MathAbs(signal.tp2Price - signal.entryPrice); - params.marginRequired = marginRequired; params.isValid = true; + params.marginRequired = marginRequired; + params.isValid = true; } }; diff --git a/Risk/Protection.mqh b/Risk/Protection.mqh index 3afe5cd..2162470 100644 --- a/Risk/Protection.mqh +++ b/Risk/Protection.mqh @@ -1,7 +1,7 @@ //+------------------------------------------------------------------+ //| Risk/Protection.mqh | -//| Circuit Breakers: Daily/Weekly Loss, Consecutive Loss, Spread | -//| MODIFIED: Completed UpdateState with live statistics tracking | +//| Enhanced Circuit Breakers with Dynamic Risk Adjustment | +//| Reduces position size after consecutive losses | //+------------------------------------------------------------------+ #ifndef __PROTECTION_MQH__ #define __PROTECTION_MQH__ @@ -25,6 +25,7 @@ private: double m_lastEquity; int m_consecLossCounter; datetime m_lastTradeTime; + double m_currentRiskMultiplier; // Dynamic risk reduction public: bool Init(double dailyLoss, double weeklyLoss, int consecLoss, int maxPos, double maxRisk) @@ -39,6 +40,7 @@ public: m_lastEquity = AccountInfoDouble(ACCOUNT_EQUITY); m_consecLossCounter = 0; m_lastTradeTime = 0; + m_currentRiskMultiplier = 1.0; Print("[Protection] Circuit breakers active. Daily:", dailyLoss, "% Weekly:", weeklyLoss, "% Consec:", consecLoss); return true; } @@ -96,6 +98,12 @@ public: return (spreadPrice <= profile.maxSpreadPoints); } + // Get dynamic risk multiplier based on recent performance + double GetRiskMultiplier() const + { + return m_currentRiskMultiplier; + } + void UpdateState(EAState &state) { double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); @@ -111,7 +119,14 @@ public: m_consecLossCounter++; state.consecutiveLosses = m_consecLossCounter; m_lastTradeTime = TimeCurrent(); - g_logger.LogEvent("PROTECTION", StringFormat("Loss detected. Consecutive: %d/%d", m_consecLossCounter, m_maxConsecLosses)); + + // Dynamic risk reduction after consecutive losses + if(m_consecLossCounter == 1) m_currentRiskMultiplier = 0.75; + else if(m_consecLossCounter == 2) m_currentRiskMultiplier = 0.50; + else if(m_consecLossCounter >= 3) m_currentRiskMultiplier = 0.25; + + g_logger.LogEvent("PROTECTION", StringFormat("Loss detected. Consecutive: %d/%d. Risk multiplier: %.2f", + m_consecLossCounter, m_maxConsecLosses, m_currentRiskMultiplier)); } } else if(equityChange > 0) @@ -120,15 +135,16 @@ public: { m_consecLossCounter = 0; state.consecutiveLosses = 0; - g_logger.LogEvent("PROTECTION", "Profit detected. Consecutive loss counter reset."); + m_currentRiskMultiplier = 1.0; // Reset to full risk + g_logger.LogEvent("PROTECTION", "Profit detected. Risk multiplier reset to 1.0"); } } } m_lastEquity = currentEquity; if(InpDebugMode) { - g_logger.LogEvent("PROTECTION", StringFormat("State | Daily: %.2f | Weekly: %.2f | Consec: %d | Equity: %.2f", - state.dailyPnL, state.weeklyPnL, state.consecutiveLosses, currentEquity)); + g_logger.LogEvent("PROTECTION", StringFormat("State | Daily: %.2f | Weekly: %.2f | Consec: %d | RiskMult: %.2f | Equity: %.2f", + state.dailyPnL, state.weeklyPnL, state.consecutiveLosses, m_currentRiskMultiplier, currentEquity)); } } @@ -143,10 +159,11 @@ public: state.totalTradesToday = 0; state.consecutiveLosses = 0; m_consecLossCounter = 0; + m_currentRiskMultiplier = 1.0; m_lastDailyReset = todayStart; state.equityAtStart = AccountInfoDouble(ACCOUNT_EQUITY); m_lastEquity = state.equityAtStart; - g_logger.LogEvent("PROTECTION", "Daily counters reset"); + g_logger.LogEvent("PROTECTION", "Daily counters reset. Risk multiplier reset to 1.0"); } if(dt.day_of_week == 1 && todayStart > m_lastWeeklyReset) { diff --git a/Universal_MTF_EA.ex5 b/Universal_MTF_EA.ex5 index e309848..5892fb2 100644 Binary files a/Universal_MTF_EA.ex5 and b/Universal_MTF_EA.ex5 differ diff --git a/Universal_MTF_EA.mq5 b/Universal_MTF_EA.mq5 index 2d95b41..64d72d1 100644 --- a/Universal_MTF_EA.mq5 +++ b/Universal_MTF_EA.mq5 @@ -1,20 +1,18 @@ //+------------------------------------------------------------------+ //| Universal_MTF_EA.mq5 | -//| Universal Multi-Timeframe Expert Advisor v2.0 | +//| Universal Multi-Timeframe Expert Advisor v2.1 | +//| Enhanced: Fibonacci + Liquidity + Dynamic Risk + Confluence | //+------------------------------------------------------------------+ #property strict #property copyright "Institutional Quantitative Systems" -#property version "2.000" -#property description "Universal MTF EA v2.0" +#property version "2.100" +#property description "Universal MTF EA v2.1 - Fibonacci + Liquidity + Smart Risk" -//--- Input for magic number +//--- Input for magic number (ONLY in .mq5, not in any .mqh) input group "=== EA IDENTIFICATION ===" input ulong InpMagicNumber = 20250625; input string InpEALabel = "Universal_MTF"; -//+------------------------------------------------------------------+ -//| MODULE INCLUDES | -//+------------------------------------------------------------------+ input group "=== RISK MANAGEMENT ===" input double InpMaxRiskPerTrade = 0.5; input double InpMaxDailyLoss = 2.0; @@ -63,6 +61,9 @@ input string InpLogPath = "Universal_MTF_EA/"; input bool InpDebugMode = false; input int InpDashboardUpdateSec = 5; +//+------------------------------------------------------------------+ +//| MODULE INCLUDES | +//+------------------------------------------------------------------+ #include "Core/Config.mqh" #include "Core/State.mqh" #include "Core/Logger.mqh" @@ -71,6 +72,8 @@ input int InpDashboardUpdateSec = 5; #include "Data/PriceEngine.mqh" #include "Data/VWAP_Engine.mqh" #include "Data/Volatility.mqh" +#include "Data/FibonacciEngine.mqh" +#include "Data/LiquidityEngine.mqh" #include "Execution/OrderManager.mqh" #include "Execution/TradeManager.mqh" #include "Logic/MacroAudit.mqh" @@ -91,6 +94,8 @@ CTelegramNotifier g_notifier; CPriceEngine g_priceEngine; CVWAPEngine g_vwapEngine; CVolatility g_volatility; +CFibonacciEngine g_fibonacci; +CLiquidityEngine g_liquidity; CMacroAudit g_macroAudit; CContextFilter g_contextFilter; CMicroTrigger g_microTrigger; @@ -108,7 +113,7 @@ CTradeManager g_tradeManager; int OnInit() { Print("============================================================"); - Print("[Universal_MTF_EA] Initializing v2.000..."); + Print("[Universal_MTF_EA] Initializing v2.100..."); Print("============================================================"); if(!g_logger.Init(InpLogPath, InpEALabel, InpMagicNumber)) @@ -116,7 +121,7 @@ int OnInit() Print("[CRITICAL] Logger init failed. EA halted."); return INIT_FAILED; } - g_logger.LogEvent("SYSTEM", "EA Initialization started v2.0"); + g_logger.LogEvent("SYSTEM", "EA Initialization started v2.1 (Fib + Liquidity + Smart Risk)"); if(!g_session.Init()) { @@ -149,6 +154,18 @@ int OnInit() return INIT_FAILED; } + if(!g_fibonacci.Init(InpMTF)) + { + g_logger.LogError("OnInit", 0, "FibonacciEngine init failed", 0); + return INIT_FAILED; + } + + if(!g_liquidity.Init(InpLTF)) + { + g_logger.LogError("OnInit", 0, "LiquidityEngine init failed", 0); + return INIT_FAILED; + } + if(!g_macroAudit.Init(InpHTF, g_vwapEngine)) { g_logger.LogError("OnInit", 0, "MacroAudit init failed", 0); @@ -231,17 +248,19 @@ int OnInit() g_priceEngine.RefreshAll(); g_vwapEngine.Calculate(g_state.vwapState); g_volatility.Update(); + g_fibonacci.Calculate(); + g_liquidity.Update(); g_macroAudit.Analyze(g_state); g_contextFilter.Analyze(g_state); - g_logger.LogEvent("SYSTEM", "EA Initialization completed successfully v2.0"); + g_logger.LogEvent("SYSTEM", "EA Initialization completed successfully v2.1"); g_logger.LogEvent("SYSTEM", StringFormat("Symbol: %s | Class: %s | HTF: %s | MTF: %s | LTF: %s", _Symbol, g_state.assetProfile.description, EnumToString(InpHTF), EnumToString(InpMTF), EnumToString(InpLTF))); if(InpAlertOnTrade) { - g_notifier.SendMessage("*Universal MTF EA v2.0 Started*\n\nSymbol: " + _Symbol + + g_notifier.SendMessage("*Universal MTF EA v2.1 Started*\n\nSymbol: " + _Symbol + "\nAsset: " + g_state.assetProfile.description + "\nTime: " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS)); } @@ -269,7 +288,7 @@ void OnDeinit(const int reason) if(InpAlertOnTrade) { - g_notifier.SendMessage("*Universal MTF EA v2.0 Stopped*\n\nSymbol: " + _Symbol + + g_notifier.SendMessage("*Universal MTF EA v2.1 Stopped*\n\nSymbol: " + _Symbol + "\nReason: " + IntegerToString(reason) + "\nDaily PnL: " + StringFormat("%.2f", g_state.dailyPnL) + "\nTime: " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS)); @@ -353,6 +372,8 @@ void OnTimer() g_state.isBarClosedMTF = true; g_state.lastMTFBarTime = iTime(_Symbol, InpMTF, 0); g_volatility.Update(); + g_fibonacci.Calculate(); + g_liquidity.Update(); g_contextFilter.Analyze(g_state); g_regimeEngine.UpdateState(g_state); } @@ -420,8 +441,8 @@ void ProcessSignal(const SignalData &signal) { g_state.openPositions++; g_logger.LogTradeOpen(signal, tradeParams, ticket); - g_logger.LogEvent("EXECUTE", StringFormat("Order Ticket=%llu | %s | Lots: %.2f", - ticket, signal.isBuy ? "BUY" : "SELL", tradeParams.lotSize)); + g_logger.LogEvent("EXECUTE", StringFormat("Order Ticket=%llu | %s | Lots: %.2f | RiskMult: %.2f", + ticket, signal.isBuy ? "BUY" : "SELL", tradeParams.lotSize, g_protection.GetRiskMultiplier())); if(InpAlertOnTrade) g_notifier.SendTradeOpen(signal, tradeParams, ticket); } else