using System; using System.Collections.Generic; using System.Linq; using FinlyticCore.Dtos.TechnicalAnalysis; namespace FinlyticTechnicals.Patterns.SmartMoney; /// /// Detects Bullish and Bearish Fair Value Gaps (FVG) across 3-candle sequences. /// public class FairValueGapDetector : IPatternDetector { public PatternType HandledType => PatternType.FairValueGapBullish; public PatternCategory Category => PatternCategory.SmartMoney; public PatternResultDto? Evaluate(TechnicalContext context) { var candles = context.PrimaryCandles; if (candles.Count < 3) return null; var c1 = candles[^3]; var c2 = candles[^2]; // Impulse candle var c3 = candles.Last(); // Bullish FVG: Candle 1 High < Candle 3 Low (Gap between c1.High and c3.Low) if (c3.Low > c1.High && c2.Close > c2.Open) { decimal gapSize = c3.Low - c1.High; decimal midGap = (c3.Low + c1.High) / 2m; if (gapSize >= context.CurrentAtr * 0.25m) { return new PatternResultDto( Id: Guid.NewGuid(), Type: PatternType.FairValueGapBullish, Category: PatternCategory.SmartMoney, Bias: PatternBias.Bullish, Name: "Bullish Fair Value Gap (FVG)", Timeframe: context.Timeframe, DetectedAt: c3.Timestamp, KeyPriceLevel: midGap, UpperBoundary: c3.Low, LowerBoundary: c1.High, InvalidationLevel: c1.High * 0.995m, QualityScore: 88m, Description: $"Bullish FVG imbalance zone [{c1.High:F2} - {c3.Low:F2}] with midpoint at {midGap:F2}." ); } } // Bearish FVG: Candle 1 Low > Candle 3 High (Gap between c3.High and c1.Low) if (c3.High < c1.Low && c2.Close < c2.Open) { decimal gapSize = c1.Low - c3.High; decimal midGap = (c1.Low + c3.High) / 2m; if (gapSize >= context.CurrentAtr * 0.25m) { return new PatternResultDto( Id: Guid.NewGuid(), Type: PatternType.FairValueGapBearish, Category: PatternCategory.SmartMoney, Bias: PatternBias.Bearish, Name: "Bearish Fair Value Gap (FVG)", Timeframe: context.Timeframe, DetectedAt: c3.Timestamp, KeyPriceLevel: midGap, UpperBoundary: c1.Low, LowerBoundary: c3.High, InvalidationLevel: c1.Low * 1.005m, QualityScore: 88m, Description: $"Bearish FVG imbalance zone [{c3.High:F2} - {c1.Low:F2}] with midpoint at {midGap:F2}." ); } } return null; } } /// /// Detects Liquidity Sweeps where price takes out multi-period swing highs/lows and immediately rejects back inside the range. /// public class LiquiditySweepDetector : IPatternDetector { public PatternType HandledType => PatternType.LiquiditySweepLow; public PatternCategory Category => PatternCategory.SmartMoney; public PatternResultDto? Evaluate(TechnicalContext context) { var candles = context.PrimaryCandles; if (candles.Count < 20) return null; var lookback = candles.Take(candles.Count - 1).TakeLast(20).ToList(); var current = candles.Last(); decimal swingLow = lookback.Min(c => c.Low); decimal swingHigh = lookback.Max(c => c.High); // Bullish Liquidity Sweep (Sweep Low): Pierced previous swing low but closed back above it if (current.Low < swingLow && current.Close > swingLow) { return new PatternResultDto( Id: Guid.NewGuid(), Type: PatternType.LiquiditySweepLow, Category: PatternCategory.SmartMoney, Bias: PatternBias.Bullish, Name: "Bullish Liquidity Sweep (Stop Hunt)", Timeframe: context.Timeframe, DetectedAt: current.Timestamp, KeyPriceLevel: swingLow, UpperBoundary: swingHigh, LowerBoundary: current.Low, InvalidationLevel: current.Low * 0.995m, QualityScore: 92m, Description: $"Bullish liquidity sweep below swing low {swingLow:F2} with wick rejection to {current.Low:F2}." ); } // Bearish Liquidity Sweep (Sweep High): Pierced previous swing high but closed back below it if (current.High > swingHigh && current.Close < swingHigh) { return new PatternResultDto( Id: Guid.NewGuid(), Type: PatternType.LiquiditySweepHigh, Category: PatternCategory.SmartMoney, Bias: PatternBias.Bearish, Name: "Bearish Liquidity Sweep (Buy-Side Sweep)", Timeframe: context.Timeframe, DetectedAt: current.Timestamp, KeyPriceLevel: swingHigh, UpperBoundary: current.High, LowerBoundary: swingLow, InvalidationLevel: current.High * 1.005m, QualityScore: 92m, Description: $"Bearish liquidity sweep above swing high {swingHigh:F2} with wick rejection to {current.High:F2}." ); } return null; } } /// /// Detects Change of Character (CHoCH) structural trend reversals and Break of Structure (BOS) continuations. /// public class ChochBosDetector : IPatternDetector { public PatternType HandledType => PatternType.ChangeOfCharacter; public PatternCategory Category => PatternCategory.SmartMoney; public PatternResultDto? Evaluate(TechnicalContext context) { var candles = context.PrimaryCandles; if (candles.Count < 20) return null; var current = candles.Last(); var prevCandles = candles.Take(candles.Count - 1).TakeLast(15).ToList(); decimal priorSwingHigh = prevCandles.Max(c => c.High); decimal priorSwingLow = prevCandles.Min(c => c.Low); // Bullish CHoCH: Clean candle body close above previous major swing high if (current.Close > priorSwingHigh && current.Open < priorSwingHigh) { return new PatternResultDto( Id: Guid.NewGuid(), Type: PatternType.ChangeOfCharacter, Category: PatternCategory.SmartMoney, Bias: PatternBias.Bullish, Name: "Bullish Change of Character (CHoCH)", Timeframe: context.Timeframe, DetectedAt: current.Timestamp, KeyPriceLevel: priorSwingHigh, UpperBoundary: current.Close + context.CurrentAtr * 2m, LowerBoundary: priorSwingLow, InvalidationLevel: priorSwingLow, QualityScore: 90m, Description: $"Bullish structural break closing above swing high {priorSwingHigh:F2}." ); } // Bearish CHoCH: Clean candle body close below previous major swing low if (current.Close < priorSwingLow && current.Open > priorSwingLow) { return new PatternResultDto( Id: Guid.NewGuid(), Type: PatternType.ChangeOfCharacter, Category: PatternCategory.SmartMoney, Bias: PatternBias.Bearish, Name: "Bearish Change of Character (CHoCH)", Timeframe: context.Timeframe, DetectedAt: current.Timestamp, KeyPriceLevel: priorSwingLow, UpperBoundary: priorSwingHigh, LowerBoundary: current.Close - context.CurrentAtr * 2m, InvalidationLevel: priorSwingHigh, QualityScore: 90m, Description: $"Bearish structural break closing below swing low {priorSwingLow:F2}." ); } return null; } } /// /// Detects institutional Order Blocks (last opposing candle before a strong directional displacement). /// public class OrderBlockDetector : IPatternDetector { public PatternType HandledType => PatternType.OrderBlock; public PatternCategory Category => PatternCategory.SmartMoney; public PatternResultDto? Evaluate(TechnicalContext context) { var candles = context.PrimaryCandles; if (candles.Count < 5) return null; var obCandle = candles[^3]; var impulse1 = candles[^2]; var impulse2 = candles.Last(); // Bullish Order Block: Red candle followed by 2 strong green candles that expand price > 1.5 ATR if (obCandle.Close < obCandle.Open && impulse1.Close > impulse1.Open && impulse2.Close > impulse2.Open) { decimal displacement = impulse2.Close - obCandle.Low; if (displacement >= context.CurrentAtr * 1.5m) { return new PatternResultDto( Id: Guid.NewGuid(), Type: PatternType.OrderBlock, Category: PatternCategory.SmartMoney, Bias: PatternBias.Bullish, Name: "Bullish Institutional Order Block", Timeframe: context.Timeframe, DetectedAt: impulse2.Timestamp, KeyPriceLevel: (obCandle.Open + obCandle.Close) / 2m, UpperBoundary: obCandle.High, LowerBoundary: obCandle.Low, InvalidationLevel: obCandle.Low * 0.995m, QualityScore: 86m, Description: $"Bullish order block zone [{obCandle.Low:F2} - {obCandle.High:F2}] with strong displacement." ); } } return null; } }