251 lines
9.9 KiB
C#
251 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using FinlyticCore.Dtos.TechnicalAnalysis;
|
|
|
|
namespace FinlyticTechnicals.Patterns.SmartMoney;
|
|
|
|
/// <summary>
|
|
/// Detects Bullish and Bearish Fair Value Gaps (FVG) across 3-candle sequences.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detects Liquidity Sweeps where price takes out multi-period swing highs/lows and immediately rejects back inside the range.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detects Change of Character (CHoCH) structural trend reversals and Break of Structure (BOS) continuations.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detects institutional Order Blocks (last opposing candle before a strong directional displacement).
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|