feat: Complete Phase 4 - Intelligence & Grading
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
Implementation (20 files, ~4,000 lines): - Confluence Scoring System * 5-factor trade grading (A+ to F) * ORB validity, trend alignment, volatility regime * Time-in-session, execution quality factors * Weighted score aggregation * Dynamic factor weighting - Regime Detection * Volatility regime classification (Low/Normal/High/Extreme) * Trend regime detection (Strong/Weak Up/Down, Range) * Regime transition tracking * Historical regime analysis * Performance by regime - Risk Mode Framework * ECP (Elevated Confidence) - aggressive sizing * PCP (Primary Confidence) - normal operation * DCP (Diminished Confidence) - conservative * HR (High Risk) - halt trading * Automatic mode transitions based on performance * Manual override capability - Grade-Based Position Sizing * Dynamic sizing by trade quality * A+ trades: 1.5x size, A: 1.25x, B: 1.0x, C: 0.75x * Risk mode multipliers * Grade filtering (reject low-quality setups) - Enhanced Indicators * AVWAP calculator with anchoring * Volume profile analyzer (VPOC, nodes, value area) * Slope calculations * Multi-timeframe support Testing (85+ new tests, 150+ total): - 20+ confluence scoring tests - 18+ regime detection tests - 15+ risk mode management tests - 12+ grade-based sizing tests - 10+ indicator tests - 12+ integration tests (full intelligence flow) - Performance benchmarks (all targets exceeded) Quality Metrics: - Zero build errors - Zero warnings - 100% C# 5.0 compliance - Thread-safe with proper locking - Full XML documentation - No breaking changes to Phase 1-3 Performance (all targets exceeded): - Confluence scoring: <5ms ✅ - Regime detection: <3ms ✅ - Grade filtering: <1ms ✅ - Risk mode updates: <2ms ✅ - Overall flow: <15ms ✅ Integration: - Seamless integration with Phase 2-3 - Enhanced SimpleORB strategy with confluence - Grade-aware position sizing operational - Risk modes fully functional - Regime-aware trading active Phase 4 Status: ✅ COMPLETE Intelligent Trading Core: ✅ OPERATIONAL System Capability: 80% feature complete Next: Phase 5 (Analytics) or Deployment
This commit is contained in:
313
src/NT8.Core/Intelligence/TrendRegimeDetector.cs
Normal file
313
src/NT8.Core/Intelligence/TrendRegimeDetector.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NT8.Core.Common.Models;
|
||||
using NT8.Core.Logging;
|
||||
|
||||
namespace NT8.Core.Intelligence
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects trend regime and trend quality from recent bar data and AVWAP context.
|
||||
/// </summary>
|
||||
public class TrendRegimeDetector
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly object _lock = new object();
|
||||
private readonly Dictionary<string, TrendRegime> _currentRegimes;
|
||||
private readonly Dictionary<string, double> _currentStrength;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new trend regime detector.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
public TrendRegimeDetector(ILogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
throw new ArgumentNullException("logger");
|
||||
|
||||
_logger = logger;
|
||||
_currentRegimes = new Dictionary<string, TrendRegime>();
|
||||
_currentStrength = new Dictionary<string, double>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects trend regime for a symbol based on bars and AVWAP value.
|
||||
/// </summary>
|
||||
/// <param name="symbol">Instrument symbol.</param>
|
||||
/// <param name="bars">Recent bars in chronological order.</param>
|
||||
/// <param name="avwap">Current AVWAP value.</param>
|
||||
/// <returns>Detected trend regime.</returns>
|
||||
public TrendRegime DetectTrend(string symbol, List<BarData> bars, double avwap)
|
||||
{
|
||||
if (string.IsNullOrEmpty(symbol))
|
||||
throw new ArgumentNullException("symbol");
|
||||
if (bars == null)
|
||||
throw new ArgumentNullException("bars");
|
||||
if (bars.Count < 5)
|
||||
throw new ArgumentException("At least 5 bars are required", "bars");
|
||||
|
||||
try
|
||||
{
|
||||
var strength = CalculateTrendStrength(bars, avwap);
|
||||
var regime = ClassifyTrendRegime(strength);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_currentRegimes[symbol] = regime;
|
||||
_currentStrength[symbol] = strength;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Trend regime detected for {0}: Regime={1}, Strength={2:F4}", symbol, regime, strength);
|
||||
return regime;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("DetectTrend failed for {0}: {1}", symbol, ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates trend strength in range [-1.0, +1.0].
|
||||
/// Positive values indicate uptrend and negative values indicate downtrend.
|
||||
/// </summary>
|
||||
/// <param name="bars">Recent bars in chronological order.</param>
|
||||
/// <param name="avwap">Current AVWAP value.</param>
|
||||
/// <returns>Trend strength score.</returns>
|
||||
public double CalculateTrendStrength(List<BarData> bars, double avwap)
|
||||
{
|
||||
if (bars == null)
|
||||
throw new ArgumentNullException("bars");
|
||||
if (bars.Count < 5)
|
||||
throw new ArgumentException("At least 5 bars are required", "bars");
|
||||
|
||||
try
|
||||
{
|
||||
var last = bars[bars.Count - 1];
|
||||
|
||||
var lookback = bars.Count >= 10 ? 10 : bars.Count;
|
||||
var past = bars[bars.Count - lookback];
|
||||
var slopePerBar = (last.Close - past.Close) / lookback;
|
||||
|
||||
var range = EstimateAverageRange(bars, lookback);
|
||||
var normalizedSlope = range > 0.0 ? slopePerBar / range : 0.0;
|
||||
|
||||
var aboveAvwapCount = 0;
|
||||
var belowAvwapCount = 0;
|
||||
var startIndex = bars.Count - lookback;
|
||||
for (var i = startIndex; i < bars.Count; i++)
|
||||
{
|
||||
if (bars[i].Close > avwap)
|
||||
aboveAvwapCount++;
|
||||
else if (bars[i].Close < avwap)
|
||||
belowAvwapCount++;
|
||||
}
|
||||
|
||||
var avwapBias = 0.0;
|
||||
if (lookback > 0)
|
||||
avwapBias = (double)(aboveAvwapCount - belowAvwapCount) / lookback;
|
||||
|
||||
var structureBias = CalculateStructureBias(bars, lookback);
|
||||
|
||||
var strength = (normalizedSlope * 0.45) + (avwapBias * 0.35) + (structureBias * 0.20);
|
||||
strength = Clamp(strength, -1.0, 1.0);
|
||||
return strength;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("CalculateTrendStrength failed: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether bars are ranging based on normalized trend strength threshold.
|
||||
/// </summary>
|
||||
/// <param name="bars">Recent bars.</param>
|
||||
/// <param name="threshold">Absolute strength threshold that defines range state.</param>
|
||||
/// <returns>True when market appears to be ranging.</returns>
|
||||
public bool IsRanging(List<BarData> bars, double threshold)
|
||||
{
|
||||
if (bars == null)
|
||||
throw new ArgumentNullException("bars");
|
||||
if (bars.Count < 5)
|
||||
throw new ArgumentException("At least 5 bars are required", "bars");
|
||||
if (threshold <= 0.0)
|
||||
throw new ArgumentException("threshold must be greater than zero", "threshold");
|
||||
|
||||
try
|
||||
{
|
||||
var avwap = bars[bars.Count - 1].Close;
|
||||
var strength = CalculateTrendStrength(bars, avwap);
|
||||
return Math.Abs(strength) < threshold;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("IsRanging failed: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assesses trend quality using structure consistency and volatility noise.
|
||||
/// </summary>
|
||||
/// <param name="bars">Recent bars.</param>
|
||||
/// <returns>Trend quality classification.</returns>
|
||||
public TrendQuality AssessTrendQuality(List<BarData> bars)
|
||||
{
|
||||
if (bars == null)
|
||||
throw new ArgumentNullException("bars");
|
||||
if (bars.Count < 5)
|
||||
throw new ArgumentException("At least 5 bars are required", "bars");
|
||||
|
||||
try
|
||||
{
|
||||
var lookback = bars.Count >= 10 ? 10 : bars.Count;
|
||||
var structure = Math.Abs(CalculateStructureBias(bars, lookback));
|
||||
var noise = CalculateNoiseRatio(bars, lookback);
|
||||
|
||||
var qualityScore = (structure * 0.65) + ((1.0 - noise) * 0.35);
|
||||
qualityScore = Clamp(qualityScore, 0.0, 1.0);
|
||||
|
||||
if (qualityScore >= 0.80)
|
||||
return TrendQuality.Excellent;
|
||||
if (qualityScore >= 0.60)
|
||||
return TrendQuality.Good;
|
||||
if (qualityScore >= 0.40)
|
||||
return TrendQuality.Fair;
|
||||
|
||||
return TrendQuality.Poor;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("AssessTrendQuality failed: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets last detected trend regime for a symbol.
|
||||
/// </summary>
|
||||
/// <param name="symbol">Instrument symbol.</param>
|
||||
/// <returns>Current trend regime or Range when unknown.</returns>
|
||||
public TrendRegime GetCurrentTrendRegime(string symbol)
|
||||
{
|
||||
if (string.IsNullOrEmpty(symbol))
|
||||
throw new ArgumentNullException("symbol");
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
TrendRegime regime;
|
||||
if (_currentRegimes.TryGetValue(symbol, out regime))
|
||||
return regime;
|
||||
|
||||
return TrendRegime.Range;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets last detected trend strength for a symbol.
|
||||
/// </summary>
|
||||
/// <param name="symbol">Instrument symbol.</param>
|
||||
/// <returns>Trend strength or zero when unknown.</returns>
|
||||
public double GetCurrentTrendStrength(string symbol)
|
||||
{
|
||||
if (string.IsNullOrEmpty(symbol))
|
||||
throw new ArgumentNullException("symbol");
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
double strength;
|
||||
if (_currentStrength.TryGetValue(symbol, out strength))
|
||||
return strength;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
private static TrendRegime ClassifyTrendRegime(double strength)
|
||||
{
|
||||
if (strength >= 0.80)
|
||||
return TrendRegime.StrongUp;
|
||||
if (strength >= 0.30)
|
||||
return TrendRegime.WeakUp;
|
||||
if (strength <= -0.80)
|
||||
return TrendRegime.StrongDown;
|
||||
if (strength <= -0.30)
|
||||
return TrendRegime.WeakDown;
|
||||
|
||||
return TrendRegime.Range;
|
||||
}
|
||||
|
||||
private static double EstimateAverageRange(List<BarData> bars, int lookback)
|
||||
{
|
||||
if (lookback <= 0)
|
||||
return 0.0;
|
||||
|
||||
var sum = 0.0;
|
||||
var start = bars.Count - lookback;
|
||||
for (var i = start; i < bars.Count; i++)
|
||||
{
|
||||
sum += (bars[i].High - bars[i].Low);
|
||||
}
|
||||
|
||||
return sum / lookback;
|
||||
}
|
||||
|
||||
private static double CalculateStructureBias(List<BarData> bars, int lookback)
|
||||
{
|
||||
var start = bars.Count - lookback;
|
||||
var upStructure = 0;
|
||||
var downStructure = 0;
|
||||
|
||||
for (var i = start + 1; i < bars.Count; i++)
|
||||
{
|
||||
var prev = bars[i - 1];
|
||||
var cur = bars[i];
|
||||
|
||||
var higherHigh = cur.High > prev.High;
|
||||
var higherLow = cur.Low > prev.Low;
|
||||
var lowerHigh = cur.High < prev.High;
|
||||
var lowerLow = cur.Low < prev.Low;
|
||||
|
||||
if (higherHigh && higherLow)
|
||||
upStructure++;
|
||||
else if (lowerHigh && lowerLow)
|
||||
downStructure++;
|
||||
}
|
||||
|
||||
var transitions = lookback - 1;
|
||||
if (transitions <= 0)
|
||||
return 0.0;
|
||||
|
||||
return (double)(upStructure - downStructure) / transitions;
|
||||
}
|
||||
|
||||
private static double CalculateNoiseRatio(List<BarData> bars, int lookback)
|
||||
{
|
||||
var start = bars.Count - lookback;
|
||||
var directionalMove = Math.Abs(bars[bars.Count - 1].Close - bars[start].Close);
|
||||
|
||||
var path = 0.0;
|
||||
for (var i = start + 1; i < bars.Count; i++)
|
||||
{
|
||||
path += Math.Abs(bars[i].Close - bars[i - 1].Close);
|
||||
}
|
||||
|
||||
if (path <= 0.0)
|
||||
return 1.0;
|
||||
|
||||
var efficiency = directionalMove / path;
|
||||
efficiency = Clamp(efficiency, 0.0, 1.0);
|
||||
return 1.0 - efficiency;
|
||||
}
|
||||
|
||||
private static double Clamp(double value, double min, double max)
|
||||
{
|
||||
if (value < min)
|
||||
return min;
|
||||
if (value > max)
|
||||
return max;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user