using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots;
using Managing.Domain.Indicators;
using Managing.Domain.Risk;
using Managing.Domain.Synth.Models;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;

namespace Managing.Application.Synth;

/// <summary>
/// Service for Synth prediction business logic and probability calculations
/// Uses MongoDB persistence for caching to prevent RAM saturation and improve performance
/// </summary>
public class SynthPredictionService : ISynthPredictionService
{
    private readonly ISynthApiClient _synthApiClient;
    private readonly ISynthRepository _synthRepository;
    private readonly ILogger<SynthPredictionService> _logger;

    private readonly List<Ticker> _availableTickers = new List<Ticker>
    {
        Ticker.BTC,
        Ticker.ETH,
    };

    /// <summary>
    /// Builds Synth configuration based on timeframe and bot configuration
    /// Different timeframes require different time horizons and update frequencies
    /// Note: Synth API only supports 5 minutes (300s) and 24 hours (86400s) time increments
    /// </summary>
    /// <param name="timeframe">Trading timeframe</param>
    /// <param name="botConfig">Bot configuration with Synth settings</param>
    /// <returns>Optimized SynthConfiguration for the timeframe</returns>
    public static SynthConfiguration BuildConfigurationForTimeframe(Timeframe timeframe, TradingBotConfig botConfig)
    {
        if (!botConfig.UseSynthApi)
        {
            return SynthConfigurationHelper.CreateDisabledConfig();
        }

        var config = new SynthConfiguration
        {
            IsEnabled = true,
            TopMinersCount = 10,
            UseForPositionSizing = botConfig.UseForPositionSizing,
            UseForSignalFiltering = botConfig.UseForSignalFiltering,
            UseForDynamicStopLoss = botConfig.UseForDynamicStopLoss,
            MaxLiquidationProbability = 0.10m // 10% default
        };

        // Adjust configuration based on timeframe
        // Note: API only supports 5 minutes (300s) and 24 hours (86400s) increments
        switch (timeframe)
        {
            case Timeframe.OneMinute:
            case Timeframe.FiveMinutes:
            case Timeframe.FifteenMinutes:
            case Timeframe.ThirtyMinutes:
                // Short timeframes - use 5 minute increments with shorter horizons
                config.TimeIncrement = 300; // 5 minutes (only supported short increment)
                config.DefaultTimeLength = 14400; // 4 hour horizon
                config.PredictionCacheDurationMinutes = 2; // Update every 2 minutes
                break;

            case Timeframe.OneHour:
                // Medium timeframes - use 5 minute increments with medium horizons
                config.TimeIncrement = 300; // 5 minutes (only supported short increment)
                config.DefaultTimeLength = 43200; // 12 hour horizon
                config.PredictionCacheDurationMinutes = 5; // Update every 5 minutes
                break;

            case Timeframe.FourHour:
            case Timeframe.OneDay:
                // Longer timeframes - use 24 hour increments with longer horizons
                config.TimeIncrement = 86400; // 24 hours (only supported long increment)
                config.DefaultTimeLength = 172800; // 48 hour horizon
                config.PredictionCacheDurationMinutes = 15; // Update every 15 minutes
                break;

            default:
                // Default to 5 minute increments for unknown timeframes
                config.TimeIncrement = 300; // 5 minutes
                config.DefaultTimeLength = 43200; // 12 hour horizon
                config.PredictionCacheDurationMinutes = 5; // Update every 5 minutes
                break;
        }

        return config;
    }

    public SynthPredictionService(
        ISynthApiClient synthApiClient,
        ISynthRepository synthRepository,
        ILogger<SynthPredictionService> logger)
    {
        _synthApiClient = synthApiClient ?? throw new ArgumentNullException(nameof(synthApiClient));
        _synthRepository = synthRepository ?? throw new ArgumentNullException(nameof(synthRepository));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    /// <summary>
    /// Calculates the probability of price reaching a target within a specified time horizon
    /// </summary>
    public async Task<decimal> GetProbabilityOfTargetPriceAsync(
        string asset,
        decimal currentPrice,
        decimal targetPrice,
        int timeHorizonSeconds,
        bool isLongPosition,
        SynthConfiguration config)
    {
        if (!config.IsEnabled)
        {
            _logger.LogDebug("Synth API is disabled, returning 0 probability");
            return 0m;
        }

        try
        {
            var predictions =
                await GetCachedPredictionsAsync(asset, timeHorizonSeconds, config, false, DateTime.UtcNow);
            if (!predictions.Any())
            {
                _logger.LogWarning($"🚫 **Synth Probability** - No predictions available for {asset}");
                return 0m;
            }

            var probability = CalculateProbabilityFromPaths(predictions, currentPrice, targetPrice, timeHorizonSeconds,
                isLongPosition);

            _logger.LogInformation($"🎯 **Synth Probability** - {asset}\n" +
                                   $"📊 Target: ${targetPrice:F2} | Current: ${currentPrice:F2}\n" +
                                   $"⏱️ Horizon: {timeHorizonSeconds / 3600.0:F1}h | Position: {(isLongPosition ? "LONG" : "SHORT")}\n" +
                                   $"🎲 Probability: {probability:P2}");

            return probability;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error calculating probability for {asset}");
            return 0m;
        }
    }

    /// <summary>
    /// Gets probabilities for multiple price thresholds at once
    /// </summary>
    public async Task<Dictionary<string, decimal>> GetMultipleThresholdProbabilitiesAsync(
        string asset,
        decimal currentPrice,
        Dictionary<string, decimal> priceThresholds,
        int timeHorizonSeconds,
        bool isLongPosition,
        SynthConfiguration config,
        bool isBacktest,
        DateTime signalDate)
    {
        var results = new Dictionary<string, decimal>();

        if (!config.IsEnabled || !priceThresholds.Any())
        {
            return results;
        }

        try
        {
            var predictions =
                await GetCachedPredictionsAsync(asset, timeHorizonSeconds, config, isBacktest, signalDate);
            if (!predictions.Any())
            {
                _logger.LogWarning($"🚫 **Synth Multi-Threshold** - No predictions available for {asset}");
                return results;
            }

            foreach (var threshold in priceThresholds)
            {
                var probability = CalculateProbabilityFromPaths(predictions, currentPrice, threshold.Value,
                    timeHorizonSeconds, isLongPosition);
                results[threshold.Key] = probability;
            }

            _logger.LogInformation($"🎯 **Synth Multi-Threshold** - {asset}\n" +
                                   $"📊 Calculated {results.Count} threshold probabilities\n" +
                                   $"⏱️ Horizon: {timeHorizonSeconds / 3600.0:F1}h | Position: {(isLongPosition ? "LONG" : "SHORT")} {(isBacktest ? "(HISTORICAL)" : "(LIVE)")}");

            return results;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error calculating multiple threshold probabilities for {asset}");
            return results;
        }
    }

    /// <summary>
    /// Clears cached predictions from MongoDB
    /// </summary>
    public async Task ClearCacheAsync()
    {
        await _synthRepository.CleanupOldDataAsync(0); // Remove all data
        _logger.LogInformation("🧹 **Synth Cache** - Cleared all cached individual predictions from MongoDB");
    }

    /// <summary>
    /// Legacy method for backward compatibility
    /// </summary>
    public void ClearCache()
    {
        // For backward compatibility - schedule async cleanup
        _ = Task.Run(async () => await ClearCacheAsync());
    }

    /// <summary>
    /// Validates a trading signal using Synth predictions to check for adverse price movements
    /// Returns comprehensive validation result with confidence, probabilities, and risk analysis
    /// </summary>
    /// <param name="signal">The trading signal containing ticker, direction, candle data, and other context</param>
    /// <param name="currentPrice">Current market price (required)</param>
    /// <param name="botConfig">Trading bot configuration</param>
    /// <param name="isBacktest">Whether this is a backtest scenario</param>
    /// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
    /// <returns>Comprehensive signal validation result including confidence, probabilities, ratio, and blocking status</returns>
    public async Task<SignalValidationResult> ValidateSignalAsync(LightSignal signal, decimal currentPrice,
        TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null)
    {
        var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);

        if (!config.IsEnabled || !config.UseForSignalFiltering || !_availableTickers.Contains(signal.Ticker))
        {
            return SignalValidationResult.CreateDisabledResult(signal.Confidence);
        }

        try
        {
            var asset = GetAssetSymbolFromTicker(signal.Ticker);
            var timeHorizon = 86400; // 24 hours for signal validation (API supported increment)

            // Calculate dynamic price thresholds based on money management settings
            var priceThresholds = new Dictionary<string, decimal>();

            // Get SL and TP percentages from money management (they are stored as decimals, e.g., 0.05 for 5%)
            var stopLossPercentage = botConfig.MoneyManagement.StopLoss;
            var takeProfitPercentage = botConfig.MoneyManagement.TakeProfit;

            if (signal.Direction == TradeDirection.Long)
            {
                // For LONG positions, check downward movement risks
                priceThresholds[$"SL Drop ({stopLossPercentage:P1})"] = currentPrice * (1 - stopLossPercentage);
                priceThresholds[$"TP Rise ({takeProfitPercentage:P1})"] = currentPrice * (1 + takeProfitPercentage);
            }
            else
            {
                // For SHORT positions, check upward movement risks  
                priceThresholds[$"SL Rise ({stopLossPercentage:P1})"] = currentPrice * (1 + stopLossPercentage);
                priceThresholds[$"TP Drop ({takeProfitPercentage:P1})"] = currentPrice * (1 - takeProfitPercentage);
            }

            var probabilities = await GetMultipleThresholdProbabilitiesAsync(
                asset, currentPrice, priceThresholds, timeHorizon, signal.Direction == TradeDirection.Long, config,
                isBacktest, signal.Date);

            // Debug logging to understand LONG vs SHORT differences
            _logger.LogInformation($"🔍 **Debug Signal Direction Analysis** - {signal.Direction} {signal.Ticker}\n" +
                                   $"💰 Current Price: ${currentPrice:F2}\n" +
                                   $"📊 Price Targets Requested:\n" +
                                   $"   {string.Join("\n   ", priceThresholds.Select(kvp => $"{kvp.Key}: ${kvp.Value:F2}"))}\n" +
                                   $"📈 API Probabilities Returned:\n" +
                                   $"   {string.Join("\n   ", probabilities.Select(kvp => $"{kvp.Key}: {kvp.Value:P2}"))}\n" +
                                   $"🎯 Position Type Used for API: {(signal.Direction == TradeDirection.Long ? "LONG" : "SHORT")}");

            // Use RiskManagement configuration if available, otherwise fall back to custom thresholds or defaults
            var riskConfig = botConfig.RiskManagement ?? new RiskManagement();

            var adverseProbabilityThreshold =
                customThresholds?.GetValueOrDefault("AdverseProbabilityThreshold",
                    riskConfig.AdverseProbabilityThreshold) ??
                riskConfig.AdverseProbabilityThreshold;

            var favorableProbabilityThreshold =
                customThresholds?.GetValueOrDefault("FavorableProbabilityThreshold",
                    riskConfig.FavorableProbabilityThreshold) ??
                riskConfig.FavorableProbabilityThreshold;

            // Get Stop Loss and Take Profit probabilities
            string slKey = signal.Direction == TradeDirection.Long
                ? $"SL Drop ({stopLossPercentage:P1})"
                : $"SL Rise ({stopLossPercentage:P1})";

            string tpKey = signal.Direction == TradeDirection.Long
                ? $"TP Rise ({takeProfitPercentage:P1})"
                : $"TP Drop ({takeProfitPercentage:P1})";

            var slProbability = probabilities.GetValueOrDefault(slKey, 0m);
            var tpProbability = probabilities.GetValueOrDefault(tpKey, 0m);

            // Calculate TP/SL ratio
            var tpSlRatio = slProbability > 0
                ? tpProbability / slProbability
                : (tpProbability > 0 ? decimal.MaxValue : 0m);

            // Calculate monetary gains and losses for Expected Utility Theory
            decimal takeProfitGain = 0m;
            decimal stopLossLoss = 0m;

            if (signal.Direction == TradeDirection.Long)
            {
                // For LONG positions
                var tpPrice = currentPrice * (1 + takeProfitPercentage);
                var slPrice = currentPrice * (1 - stopLossPercentage);
                takeProfitGain = tpPrice - currentPrice;
                stopLossLoss = currentPrice - slPrice;
            }
            else
            {
                // For SHORT positions  
                var tpPrice = currentPrice * (1 - takeProfitPercentage);
                var slPrice = currentPrice * (1 + stopLossPercentage);
                takeProfitGain = currentPrice - tpPrice;
                stopLossLoss = slPrice - currentPrice;
            }

            var result = new SignalValidationResult
            {
                Confidence = Confidence.Medium, // Temporary - will be calculated using full object
                StopLossProbability = slProbability,
                TakeProfitProbability = tpProbability,
                TpSlRatio = tpSlRatio,
                IsBlocked = false, // Temporary - will be determined after full analysis
                AdverseProbabilityThreshold = adverseProbabilityThreshold,
                TimeHorizonSeconds = timeHorizon,
                UsedCustomThresholds = customThresholds != null,
                TakeProfitGain = takeProfitGain,
                StopLossLoss = stopLossLoss,
                ValidationContext =
                    $"{signal.Direction} {signal.Ticker} - SL: {slProbability:P2}, TP: {tpProbability:P2}, Ratio: {tpSlRatio:F2}x, EMV: TBD"
            };

            // Calculate Expected Utility Theory metrics using RiskManagement configuration
            result.CalculateExpectedMetrics(
                botConfig.BotTradingBalance, // Use actual bot trading balance
                riskConfig); // Pass complete risk configuration

            // Now calculate confidence using the full SignalValidationResult object
            var finalConfidence = CalculateSignalConfidenceFromResult(result, riskConfig);
            result.Confidence = finalConfidence;
            result.IsBlocked = finalConfidence == Confidence.None;

            // Update validation context with comprehensive analysis
            result.ValidationContext =
                $"{signal.Direction} {signal.Ticker} - SL: {slProbability:P2}, TP: {tpProbability:P2}, " +
                $"Ratio: {tpSlRatio:F2}x, EMV: ${result.ExpectedMonetaryValue:F2}, " +
                $"EU: {result.ExpectedUtility:F4}, Kelly: {result.KellyFraction:P2}, " +
                $"Confidence: {finalConfidence}, Risk: {result.GetUtilityRiskAssessment()}";

            _logger.LogInformation(
                $"🎯 **Synth Signal Analysis** - {signal.Direction} {signal.Ticker} {(isBacktest ? "(BACKTEST)" : "(LIVE)")}\n" +
                $"📊 Signal: `{signal.Identifier}`\n" +
                $"📊 SL Risk: {slProbability:P2} | TP Probability: {tpProbability:P2}\n" +
                $"🎲 Ratio: {tpSlRatio:F2}x (TP/SL) | Win/Loss: {result.WinLossRatio:F2}:1\n" +
                $"💰 Expected Monetary Value: ${result.ExpectedMonetaryValue:F2}\n" +
                $"🧮 Expected Utility: {result.ExpectedUtility:F4} | Utility/Risk Ratio: {result.UtilityRiskRatio:F4}\n" +
                $"📈 Gains/Losses: TP +${takeProfitGain:F2} | SL -${stopLossLoss:F2}\n" +
                $"🎯 Kelly Criterion: {result.KellyFraction:P2} (Capped: {result.KellyCappedFraction:P2})\n" +
                $"📊 Kelly Assessment: {result.KellyAssessment}\n" +
                $"🎯 Confidence: {finalConfidence} | Blocked: {result.IsBlocked} | Kelly Favorable: {result.IsKellyFavorable(riskConfig)}\n" +
                $"📋 Thresholds: Adverse {adverseProbabilityThreshold:P2} | Favorable {favorableProbabilityThreshold:P2} | Custom: {(customThresholds != null ? "Yes" : "No")}\n" +
                $"🔍 Risk Assessment: {result.GetUtilityRiskAssessment()} | Risk Tolerance: {riskConfig.RiskTolerance}\n" +
                $"💰 Price: ${currentPrice:F2} | Signal Date: {signal.Date:HH:mm:ss}\n" +
                $"⏱️ Time horizon: {riskConfig.SignalValidationTimeHorizonHours}h");

            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                $"Error in Synth signal validation for signal {signal.Identifier}, returning fallback confidence");
            return SignalValidationResult.CreateErrorResult(signal.Confidence, ex.Message);
        }
    }

    /// <summary>
    /// Calculates signal confidence based on the full SignalValidationResult object
    /// Uses Expected Utility Theory, Kelly Criterion, and probability analysis for comprehensive assessment
    /// </summary>
    /// <param name="result">The SignalValidationResult object with calculated metrics</param>
    /// <param name="riskConfig">The RiskManagement configuration</param>
    /// <returns>Calculated confidence level</returns>
    private Confidence CalculateSignalConfidenceFromResult(SignalValidationResult result, RiskManagement riskConfig)
    {
        // Enhanced blocking logic - allow conditional pass-through for signals with positive elements
        var isOverAdverseThreshold = result.StopLossProbability > result.AdverseProbabilityThreshold;

        if (isOverAdverseThreshold)
        {
            // Check if signal has redeeming qualities that warrant LOW confidence instead of blocking
            var hasRedeemingQualities = CheckForRedeemingQualities(result, riskConfig);

            if (!hasRedeemingQualities)
            {
                // Complete block for signals with no redeeming qualities
                return Confidence.None;
            }

            // Signal exceeded adverse threshold but has positive elements - force to LOW confidence maximum
            var constrainedScore = CalculateConstrainedScore(result, riskConfig);

            // For over-threshold signals, cap at LOW confidence and add penalty
            var (_, _, configLowThreshold) = GetConfigurationSpecificThresholds(riskConfig);
            var penalizedScore =
                Math.Min(constrainedScore * 0.75m, configLowThreshold + 0.05m); // Small buffer above LOW threshold

            _logger.LogDebug(
                $"🚨 **Conditional Pass-Through** - Signal exceeded {result.AdverseProbabilityThreshold:P1} threshold " +
                $"(SL: {result.StopLossProbability:P2}) but has redeeming qualities. " +
                $"Constrained score: {constrainedScore:F4} → Penalized: {penalizedScore:F4} → LOW confidence");

            return Confidence.Low; // Force to LOW confidence for risky but potentially profitable signals
        }

        // Standard confidence calculation for signals within adverse threshold
        var configScore = CalculateConfigurationAwareScore(result, riskConfig);
        var probabilityScore = CalculateProbabilityScore(result);
        var thresholdAlignmentScore = CalculateThresholdAlignmentScore(result, riskConfig);

        // Heavily weight configuration-aware factors to create differentiation
        var compositeScore = (configScore * 0.50m) + // 50% - Configuration-specific scoring
                             (thresholdAlignmentScore * 0.30m) + // 30% - How well signal aligns with config thresholds
                             (probabilityScore * 0.20m); // 20% - Raw probability assessment

        // More nuanced confidence thresholds based on risk tolerance
        var (highThreshold, mediumThreshold, lowThreshold) = GetConfigurationSpecificThresholds(riskConfig);

        // Determine confidence level using config-specific thresholds
        var confidence = compositeScore switch
        {
            var score when score >= highThreshold => Confidence.High,
            var score when score >= mediumThreshold => Confidence.Medium,
            var score when score >= lowThreshold => Confidence.Low,
            _ => Confidence.None
        };

        // Debug logging to understand scoring breakdown
        _logger.LogDebug($"🧮 **Config-Aware Confidence** [{riskConfig.RiskTolerance}] - " +
                         $"Composite: {compositeScore:F4} = " +
                         $"Config({configScore:F4})*0.5 + " +
                         $"Threshold({thresholdAlignmentScore:F4})*0.3 + " +
                         $"Prob({probabilityScore:F4})*0.2 " +
                         $"| Thresholds: H≥{highThreshold:F2}, M≥{mediumThreshold:F2}, L≥{lowThreshold:F2} " +
                         $"→ {confidence}");

        return confidence;
    }

    /// <summary>
    /// Checks if a signal that exceeded the adverse probability threshold has redeeming qualities
    /// that warrant LOW confidence instead of complete blocking
    /// </summary>
    private bool CheckForRedeemingQualities(SignalValidationResult result, RiskManagement riskConfig)
    {
        var redeemingFactors = 0;
        var totalFactors = 0;

        // Factor 1: Kelly Criterion shows SIGNIFICANT edge (stricter criteria)
        totalFactors++;
        if (result.KellyFraction > riskConfig.KellyMinimumThreshold * 2m &&
            result.KellyFraction > 0.05m) // Need 2x minimum threshold AND >5%
        {
            redeemingFactors++;
        }

        // Factor 2: Positive AND meaningful Expected Monetary Value (stricter)
        totalFactors++;
        if (result.ExpectedMonetaryValue > 100m) // Need at least $100 expected profit
        {
            redeemingFactors++;
        }

        // Factor 3: EXCELLENT TP/SL Ratio (stricter criteria)
        totalFactors++;
        if (result.TpSlRatio >= 2.0m) // Need at least 2:1 ratio (was 1.5:1)
        {
            redeemingFactors++;
        }

        // Factor 4: Significant positive Expected Utility (stricter)
        totalFactors++;
        if (result.ExpectedUtility > 1.0m) // Need meaningful utility, not just positive
        {
            redeemingFactors++;
        }

        // Factor 5: TP probability SIGNIFICANTLY higher than SL probability (stricter)
        totalFactors++;
        if (result.TakeProfitProbability > result.StopLossProbability * 1.5m) // Need 150% higher (was 120%)
        {
            redeemingFactors++;
        }

        // Factor 6: Only SLIGHTLY over the threshold (stricter tolerance)
        totalFactors++;
        var overThresholdRatio = result.StopLossProbability / result.AdverseProbabilityThreshold;
        if (overThresholdRatio <= 1.25m) // Not more than 25% over the threshold (was 50%)
        {
            redeemingFactors++;
        }

        // Factor 7: NEW - Kelly assessment shows strong edge
        totalFactors++;
        if (result.KellyAssessment != null &&
            (result.KellyAssessment.Contains("Strong") ||
             result.KellyAssessment.Contains("Exceptional") ||
             result.KellyAssessment.Contains("Extraordinary")))
        {
            redeemingFactors++;
        }

        // Factor 8: NEW - Win/Loss ratio is favorable
        totalFactors++;
        if (result.WinLossRatio >= 2.0m) // Need at least 2:1 win/loss ratio
        {
            redeemingFactors++;
        }

        // Require at least 75% of factors to be positive for conditional pass-through (was 50%)
        var redeemingPercentage = (decimal)redeemingFactors / totalFactors;

        _logger.LogDebug(
            $"🔍 **Redeeming Qualities Check** - {redeemingFactors}/{totalFactors} factors positive ({redeemingPercentage:P0}). " +
            $"Kelly: {result.KellyFraction:P2} (>2x{riskConfig.KellyMinimumThreshold:P2}), " +
            $"EMV: ${result.ExpectedMonetaryValue:F2} (>$100), " +
            $"TP/SL: {result.TpSlRatio:F2}x (>2.0), " +
            $"EU: {result.ExpectedUtility:F4} (>1.0), " +
            $"TP vs SL: {result.TakeProfitProbability:P2} vs {result.StopLossProbability:P2} (>150%), " +
            $"Threshold Overage: {overThresholdRatio:F2}x (<1.25), " +
            $"Kelly Assessment: {result.KellyAssessment}, " +
            $"Win/Loss: {result.WinLossRatio:F2}x");

        return redeemingPercentage >= 0.75m; // Need at least 75% positive factors (was 50%)
    }

    /// <summary>
    /// Calculates a constrained score for signals that exceeded adverse threshold but have redeeming qualities
    /// </summary>
    private decimal CalculateConstrainedScore(SignalValidationResult result, RiskManagement riskConfig)
    {
        // Focus on the positive aspects while acknowledging the high risk
        var kellyScore = Math.Min(MapToScore(result.KellyFraction, 0.05m, 5.0m), 0.8m); // Cap Kelly contribution
        var tpSlScore = Math.Min(MapToScore(result.TpSlRatio, 1.5m, 3.0m), 0.7m); // Cap TP/SL contribution
        var emvScore = result.ExpectedMonetaryValue > 0 ? 0.6m : 0.2m; // Binary scoring for EMV
        var utilityScore = result.ExpectedUtility > 0 ? 0.5m : 0.2m; // Binary scoring for utility

        // Risk penalty for being over threshold
        var overThresholdRatio = result.StopLossProbability / result.AdverseProbabilityThreshold;
        var riskPenalty = MapToInverseScore(overThresholdRatio, 1.2m, 3.0m); // Penalty starts at 120% of threshold

        // Weighted combination with risk penalty
        var constrainedScore = (kellyScore * 0.25m) +
                               (tpSlScore * 0.25m) +
                               (emvScore * 0.20m) +
                               (utilityScore * 0.15m) +
                               (riskPenalty * 0.15m);

        return Math.Min(constrainedScore, 0.60m); // Cap to ensure it stays in LOW confidence range
    }

    /// <summary>
    /// Calculates configuration-specific thresholds for confidence levels based on risk tolerance
    /// </summary>
    private (decimal high, decimal medium, decimal low) GetConfigurationSpecificThresholds(RiskManagement riskConfig)
    {
        return riskConfig.RiskTolerance switch
        {
            // Conservative: More gaps between thresholds to create MEDIUM zones
            RiskToleranceLevel.Conservative => (0.80m, 0.60m, 0.40m), // Stricter with wider MEDIUM zone
            RiskToleranceLevel.Moderate => (0.75m, 0.55m, 0.35m), // Wider MEDIUM zone (55%-75%)  
            RiskToleranceLevel.Aggressive => (0.70m, 0.45m, 0.25m), // Lower HIGH threshold, wider MEDIUM zone
            _ => (0.75m, 0.55m, 0.35m) // Default to moderate
        };
    }

    /// <summary>
    /// Calculates a score based on how well the signal aligns with the configuration's specific thresholds and limits
    /// This creates differentiation between Conservative, Moderate, and Aggressive settings
    /// </summary>
    private decimal CalculateConfigurationAwareScore(SignalValidationResult result, RiskManagement riskConfig)
    {
        decimal score = 0m;

        // 1. Adverse Probability Pressure - how close are we to the blocking threshold?
        var adversePressure = result.StopLossProbability / result.AdverseProbabilityThreshold;
        var adverseScore = riskConfig.RiskTolerance switch
        {
            RiskToleranceLevel.Conservative => MapToInverseScore(adversePressure, 0.4m,
                6.0m), // More sensitive - penalize at 40% of threshold
            RiskToleranceLevel.Moderate => MapToInverseScore(adversePressure, 0.6m,
                4.0m), // Moderate sensitivity at 60% of threshold  
            RiskToleranceLevel.Aggressive => MapToInverseScore(adversePressure, 0.8m,
                3.0m), // Less sensitive - only penalize at 80% of threshold
            _ => MapToInverseScore(adversePressure, 0.6m, 4.0m)
        };

        // 2. Kelly Alignment - does the Kelly fraction match the configuration's expectations?
        // Make this more nuanced with smoother transitions to create MEDIUM scores
        var kellyAlignmentScore = riskConfig.RiskTolerance switch
        {
            RiskToleranceLevel.Conservative =>
                // Conservative: Smoother curve for Kelly fractions, more MEDIUM scores
                result.KellyCappedFraction <= 0.03m
                    ? MapToScore(result.KellyCappedFraction, 0.02m, 15.0m)
                    : result.KellyCappedFraction <= 0.08m
                        ? MapToScore(result.KellyCappedFraction, 0.05m, 10.0m)
                        : // Smoother penalty 
                        result.KellyCappedFraction <= 0.15m
                            ? 0.3m
                            : 0.1m, // Gradual penalty for >15%

            RiskToleranceLevel.Moderate =>
                // Moderate: Create broader MEDIUM scoring range
                result.KellyCappedFraction <= 0.01m
                    ? 0.2m
                    : // Too small
                    result.KellyCappedFraction <= 0.15m
                        ? MapToScore(result.KellyCappedFraction, 0.06m, 8.0m)
                        : // Broader range
                        0.3m, // Less penalty for larger Kelly

            RiskToleranceLevel.Aggressive =>
                // Aggressive: More forgiving for higher Kelly, but still create MEDIUM range
                result.KellyCappedFraction <= 0.02m
                    ? 0.3m
                    : // Less penalty for small Kelly
                    MapToScore(result.KellyCappedFraction, 0.10m, 6.0m), // Reward higher Kelly with smoother curve

            _ => MapToScore(result.KellyCappedFraction, 0.06m, 8.0m) // Default moderate
        };

        // 3. Risk Aversion Alignment - make this more sensitive to create MEDIUM scores
        var riskAversionScore = 0.4m; // Start lower to allow more MEDIUM scores
        if (result.ExpectedUtility != 0m)
        {
            var expectedUtilityNormalized =
                Math.Max(-1.0m, Math.Min(1.0m, result.ExpectedUtility * 1000m)); // Scale for visibility

            riskAversionScore = riskConfig.RiskTolerance switch
            {
                RiskToleranceLevel.Conservative =>
                    // Conservative: More lenient utility requirements, broader MEDIUM range
                    MapToScore(expectedUtilityNormalized, -0.2m, 3.0m), // Lower bar, smoother curve

                RiskToleranceLevel.Moderate =>
                    // Moderate: Balanced but broader MEDIUM range
                    MapToScore(expectedUtilityNormalized, 0.0m, 5.0m), // Centered at 0, broader curve

                RiskToleranceLevel.Aggressive =>
                    // Aggressive: Still demand higher utility but with smoother transitions
                    MapToScore(expectedUtilityNormalized, 0.2m, 8.0m), // Lower high bar, smoother curve

                _ => MapToScore(expectedUtilityNormalized, 0.0m, 5.0m)
            };
        }

        // 4. Configuration Consistency Bonus - reduce the bonus to avoid pushing scores too high
        var consistencyBonus = 0m;

        // Conservative consistency: More moderate bonuses
        if (riskConfig.RiskTolerance == RiskToleranceLevel.Conservative)
        {
            if (result.StopLossProbability < 0.12m && result.KellyCappedFraction < 0.06m && result.TpSlRatio > 1.5m)
                consistencyBonus = 0.08m; // Smaller bonus
            else if (result.StopLossProbability < 0.15m && result.KellyCappedFraction < 0.10m &&
                     result.TpSlRatio > 1.2m)
                consistencyBonus = 0.04m; // Partial bonus
        }
        // Aggressive consistency: More moderate bonuses
        else if (riskConfig.RiskTolerance == RiskToleranceLevel.Aggressive)
        {
            if (result.KellyCappedFraction > 0.08m && result.TpSlRatio > 2.0m && result.ExpectedMonetaryValue > 0)
                consistencyBonus = 0.08m; // Smaller bonus
            else if (result.KellyCappedFraction > 0.05m && result.TpSlRatio > 1.5m && result.ExpectedMonetaryValue > 0)
                consistencyBonus = 0.04m; // Partial bonus
        }
        // Moderate consistency: Easier to achieve partial bonuses
        else if (riskConfig.RiskTolerance == RiskToleranceLevel.Moderate)
        {
            if (result.StopLossProbability < 0.16m && result.KellyCappedFraction > 0.03m && result.TpSlRatio > 1.5m)
                consistencyBonus = 0.06m; // Moderate bonus
            else if (result.StopLossProbability < 0.18m && result.KellyCappedFraction > 0.02m &&
                     result.TpSlRatio > 1.3m)
                consistencyBonus = 0.03m; // Small bonus
        }

        // Weighted combination with more balanced weights to avoid extremes
        score = (adverseScore * 0.30m) + // 30% - Adverse probability pressure
                (kellyAlignmentScore * 0.25m) + // 25% - Kelly fraction alignment  
                (riskAversionScore * 0.25m) + // 25% - Risk aversion alignment
                (consistencyBonus) + // Variable - Configuration consistency bonus
                0.20m; // 20% - Base score to lift all signals into scoreable range

        return Math.Min(1.0m, score);
    }

    /// <summary>
    /// Calculates how well the signal aligns with the configuration's specific thresholds and limits
    /// </summary>
    private decimal CalculateThresholdAlignmentScore(SignalValidationResult result, RiskManagement riskConfig)
    {
        decimal score = 0m;

        // 1. Kelly Threshold Alignment - is Kelly above minimum but below maximum?
        var kellyMinScore = result.KellyFraction >= riskConfig.KellyMinimumThreshold
            ? 1.0m
            : MapToScore(result.KellyFraction / riskConfig.KellyMinimumThreshold, 1.0m, 10.0m);

        var kellyMaxScore = result.KellyCappedFraction <= riskConfig.KellyMaximumCap
            ? 1.0m
            : MapToInverseScore(result.KellyCappedFraction / riskConfig.KellyMaximumCap, 1.0m, 8.0m);

        // 2. Favorable Probability Threshold - does TP probability meet expectations?
        var favorableThresholdScore = result.TakeProfitProbability >= riskConfig.FavorableProbabilityThreshold
            ? 1.0m
            : MapToScore(result.TakeProfitProbability / riskConfig.FavorableProbabilityThreshold, 1.0m, 8.0m);

        // 3. Multiplier Alignment - is the signal worthy of the Kelly multiplier being used?
        var multiplierScore = 0.5m; // Default neutral
        if (riskConfig.KellyFractionalMultiplier < 1.0m) // Conservative fractional Kelly
        {
            // For fractional Kelly, prefer signals with lower variance/risk
            multiplierScore = MapToInverseScore(result.StopLossProbability, 0.15m, 8.0m);
        }
        else if (riskConfig.KellyFractionalMultiplier >= 1.0m) // Full or over-Kelly
        {
            // For full Kelly, demand higher quality signals
            multiplierScore = MapToScore(result.TpSlRatio, 1.8m, 5.0m);
        }

        // Weighted combination
        score = (kellyMinScore * 0.25m) + // 25% - Meet minimum Kelly threshold
                (kellyMaxScore * 0.25m) + // 25% - Stay within maximum Kelly cap
                (favorableThresholdScore * 0.30m) + // 30% - Meet favorable probability threshold
                (multiplierScore * 0.20m); // 20% - Align with Kelly multiplier philosophy

        return Math.Min(1.0m, score);
    }

    /// <summary>
    /// Calculates probability-based score (0.0 to 1.0) using continuous functions
    /// Enhanced to be the primary confidence factor - focuses on signal quality through TP/SL analysis
    /// </summary>
    private decimal CalculateProbabilityScore(SignalValidationResult result)
    {
        // TP/SL ratio scoring (continuous) - Primary signal quality indicator
        // Midpoint at 1.5 (realistic good ratio), steepness 3.0 for better sensitivity
        var ratioScore = MapToScore(result.TpSlRatio, 1.5m, 3.0m);

        // Stop Loss probability scoring (inverse - lower is better) - Critical risk factor
        // Use percentage of threshold for more nuanced scoring
        var slRiskThreshold = result.AdverseProbabilityThreshold;
        var slScore = MapToInverseScore(result.StopLossProbability, slRiskThreshold * 0.75m, 8.0m);

        // Take Profit probability scoring (direct - higher is better) - Success likelihood
        // Midpoint at 60% TP probability, steepness 4.0 for good sensitivity around realistic ranges
        var tpScore = MapToScore(result.TakeProfitProbability, 0.60m, 4.0m);

        // Win/Loss ratio scoring - Overall signal attractiveness
        // Midpoint at 1.5 (favorable win/loss), steepness 3.0 for realistic sensitivity
        var winLossScore = MapToScore(result.WinLossRatio, 1.5m, 3.0m);

        // Probability dominance check - TP should significantly exceed SL for high confidence
        var probabilityDominance = result.StopLossProbability > 0
            ? Math.Min(1.0m,
                result.TakeProfitProbability / result.StopLossProbability / 2.0m) // Divide by 2 for scaling
            : (result.TakeProfitProbability > 0.5m ? 1.0m : 0.0m);

        // Weighted combination emphasizing the most important probability factors
        var combinedScore = (tpScore * 0.30m) + // 30% - TP probability (success likelihood)
                            (slScore * 0.30m) + // 30% - SL risk management (risk control)
                            (ratioScore * 0.25m) + // 25% - TP/SL ratio (reward/risk)
                            (winLossScore * 0.10m) + // 10% - Win/Loss ratio (historical context)
                            (probabilityDominance * 0.05m); // 5% - Probability dominance bonus

        return Math.Min(1.0m, combinedScore);
    }

    /// <summary>
    /// Helper function to map a value to a score between 0 and 1 using a sigmoid-like curve
    /// </summary>
    /// <param name="value">The input metric (e.g., TpSlRatio, ExpectedMonetaryValue)</param>
    /// <param name="midpoint">The value at which the score should be approximately 0.5</param>
    /// <param name="steepness">Controls how quickly the score changes around the midpoint. Higher = steeper</param>
    /// <returns>Score between 0.0 and 1.0</returns>
    private decimal MapToScore(decimal value, decimal midpoint, decimal steepness)
    {
        // Using Math.Exp for the exponential part
        // Clamp the exponent to prevent overflow/underflow
        double exponent = (double)(-steepness * (value - midpoint));
        exponent = Math.Max(-20.0, Math.Min(20.0, exponent)); // Prevent Math.Exp overflow
        return 1m / (1m + (decimal)Math.Exp(exponent));
    }

    /// <summary>
    /// Helper function for metrics where lower values are better (e.g., risk probabilities)
    /// </summary>
    /// <param name="value">The input metric</param>
    /// <param name="midpoint">The value at which the score should be approximately 0.5</param>
    /// <param name="steepness">Controls how quickly the score changes around the midpoint</param>
    /// <returns>Inverse score between 0.0 and 1.0</returns>
    private decimal MapToInverseScore(decimal value, decimal midpoint, decimal steepness)
    {
        // This is for metrics where lower values are better
        double exponent = (double)(steepness * (value - midpoint)); // Note the positive steepness
        exponent = Math.Max(-20.0, Math.Min(20.0, exponent));
        return 1m / (1m + (decimal)Math.Exp(exponent));
    }

    /// <summary>
    /// Performs risk assessment before opening a position
    /// </summary>
    public async Task<bool> AssessPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
        TradingBotConfig botConfig, bool isBacktest)
    {
        var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);

        if (!config.IsEnabled || !config.UseForPositionSizing || isBacktest)
        {
            return true; // Allow position if Synth is disabled or in backtest mode
        }

        try
        {
            var asset = GetAssetSymbolFromTicker(ticker);
            var timeHorizon = 86400; // 24 hours for risk assessment

            // Estimate liquidation price based on money management
            var estimatedLiquidationPrice =
                EstimateLiquidationPrice(currentPrice, direction, botConfig.MoneyManagement);

            var liquidationProbability = await GetProbabilityOfTargetPriceAsync(
                asset, currentPrice, estimatedLiquidationPrice, timeHorizon, direction == TradeDirection.Long, config);

            if (liquidationProbability > config.MaxLiquidationProbability)
            {
                _logger.LogWarning(
                    $"🚫 **Synth Risk Block**\n" +
                    $"Liquidation probability too high: {liquidationProbability:P2}\n" +
                    $"📊 Max allowed: {config.MaxLiquidationProbability:P2}\n" +
                    $"💰 Est. liquidation: ${estimatedLiquidationPrice:F2}\n" +
                    $"⏱️ Time horizon: 24h");
                return false;
            }

            _logger.LogInformation(
                $"✅ **Synth Risk Check**\n" +
                $"Liquidation probability acceptable: {liquidationProbability:P2}\n" +
                $"📊 Max allowed: {config.MaxLiquidationProbability:P2}");

            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in Synth risk assessment, allowing position to proceed");
            return true; // Allow position on error to avoid blocking trades
        }
    }

    /// <summary>
    /// Monitors liquidation risk for an open position
    /// </summary>
    public async Task<SynthRiskResult> MonitorPositionRiskAsync(Ticker ticker, TradeDirection direction,
        decimal currentPrice, decimal liquidationPrice, Guid positionIdentifier, TradingBotConfig botConfig)
    {
        var result = new SynthRiskResult();
        var config = BuildConfigurationForTimeframe(botConfig.Timeframe, botConfig);

        if (!config.IsEnabled)
        {
            return result; // Return empty result if disabled
        }

        try
        {
            var asset = GetAssetSymbolFromTicker(ticker);
            var timeHorizon = 21600; // 6 hours for position monitoring

            var liquidationProbability = await GetProbabilityOfTargetPriceAsync(
                asset, currentPrice, liquidationPrice, timeHorizon, direction == TradeDirection.Long, config);

            result.LiquidationProbability = liquidationProbability;

            // Set warning threshold at 20%
            if (liquidationProbability > 0.20m)
            {
                result.ShouldWarn = true;
                result.WarningMessage =
                    $"⚠️ **High Liquidation Risk**\n" +
                    $"Position: `{positionIdentifier}`\n" +
                    $"🎲 Liquidation probability: {liquidationProbability:P2}\n" +
                    $"💰 Stop loss: ${liquidationPrice:F2}\n" +
                    $"📊 Current: ${currentPrice:F2}\n" +
                    $"⏱️ Next 6h forecast";
            }

            // Set auto-close threshold at 50% if dynamic stop-loss is enabled
            if (liquidationProbability > 0.50m && config.UseForDynamicStopLoss)
            {
                result.ShouldAutoClose = true;
                result.EmergencyMessage =
                    $"🚨 **Emergency Close**\n" +
                    $"Extremely high liquidation risk: {liquidationProbability:P2}\n" +
                    $"Auto-closing position for protection";
            }

            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error monitoring Synth liquidation risk for position {positionIdentifier}");
            return result;
        }
    }

    /// <summary>
    /// Estimates liquidation price based on money management settings
    /// </summary>
    public decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction,
        LightMoneyManagement moneyManagement)
    {
        // This is a simplified estimation - in reality, you'd use the actual money management logic
        var riskPercentage = 0.02m; // Default 2% risk

        if (direction == TradeDirection.Long)
        {
            return currentPrice * (1 - riskPercentage);
        }
        else
        {
            return currentPrice * (1 + riskPercentage);
        }
    }

    /// <summary>
    /// Converts ticker to asset symbol for Synth API
    /// </summary>
    private string GetAssetSymbolFromTicker(Ticker ticker)
    {
        // Map ticker enum to Synth API asset symbols
        return ticker switch
        {
            Ticker.BTC => "BTC",
            Ticker.ETH => "ETH",
            _ => "BTC" // Default fallback
        };
    }

    /// <summary>
    /// Gets cached predictions or fetches new ones if cache is expired
    /// </summary>
    private async Task<List<MinerPrediction>> GetCachedPredictionsAsync(string asset, int timeHorizonSeconds,
        SynthConfiguration config, bool isBacktest, DateTime signalDate)
    {
        var now = DateTime.UtcNow;

        // Check if we have cached leaderboard data first
        var leaderboardCacheKey = $"{asset}_{config.TimeIncrement}";
        if (isBacktest)
        {
            leaderboardCacheKey += $"_backtest_{signalDate:yyyy-MM-dd-HH}";
        }

        var cachedLeaderboard = await _synthRepository.GetLeaderboardAsync(leaderboardCacheKey);

        List<MinerInfo> leaderboard;
        if (cachedLeaderboard != null)
        {
            leaderboard = cachedLeaderboard.Miners;
            _logger.LogDebug($"📦 **Synth Cache** - Using cached leaderboard for {asset}");
        }
        else
        {
            if (isBacktest)
            {
                // For backtests, get historical leaderboard and predictions from the signal date
                var startTime = signalDate.AddMinutes(-30); // Get leaderboard 30 minutes before signal
                var endTime = signalDate.AddMinutes(30); // Allow 30 minute window for leaderboard data

                leaderboard = await _synthApiClient.GetHistoricalLeaderboardAsync(startTime, endTime, config);

                if (!leaderboard.Any())
                {
                    _logger.LogWarning(
                        $"No historical leaderboard data available for {signalDate:yyyy-MM-dd HH:mm}, falling back to current leaderboard");
                }
            }
            else
            {
                // For live trading, get current leaderboard
                leaderboard = await _synthApiClient.GetLeaderboardAsync(config);
            }

            // Cache the leaderboard data
            if (leaderboard.Any())
            {
                var leaderboardToCache = new SynthMinersLeaderboard
                {
                    Asset = asset,
                    TimeIncrement = config.TimeIncrement,
                    SignalDate = isBacktest ? signalDate : null,
                    IsBacktest = isBacktest,
                    Miners = leaderboard
                };
                await _synthRepository.SaveLeaderboardAsync(leaderboardToCache);
            }
        }

        var topMinerUids = leaderboard
            .OrderByDescending(m => m.Rank)
            .Take(config.TopMinersCount)
            .Select(m => m.NeuronUid)
            .ToList();

        if (!topMinerUids.Any())
        {
            _logger.LogWarning("No miners available from leaderboard");
            return new List<MinerPrediction>();
        }

        var timeLength = Math.Max(timeHorizonSeconds, config.DefaultTimeLength);

        // Try to get individual cached predictions first
        var cachedIndividualPredictions = await _synthRepository.GetIndividualPredictionsAsync(
            asset, config.TimeIncrement, timeLength, topMinerUids, isBacktest,
            isBacktest ? signalDate : null);

        // If we have all predictions cached, return them
        if (cachedIndividualPredictions.Count == topMinerUids.Count)
        {
            _logger.LogDebug(
                $"📦 **Synth Individual Cache** - Using all cached individual predictions for {asset} {(isBacktest ? "HISTORICAL" : "LIVE")}");

            return cachedIndividualPredictions.Select(p => p.Prediction).ToList();
        }

        // If we have partial cache, determine which miners need fresh data
        var cachedMinerUids = cachedIndividualPredictions.Select(p => p.MinerUid).ToHashSet();
        var missingMinerUids = topMinerUids.Where(uid => !cachedMinerUids.Contains(uid)).ToList();

        _logger.LogInformation(
            $"🔄 **Synth Individual Cache** - Partial cache hit for {asset}: {cachedIndividualPredictions.Count}/{topMinerUids.Count} cached, fetching {missingMinerUids.Count} fresh predictions {(isBacktest ? "HISTORICAL" : "LIVE")}");

        // Fetch missing predictions from API
        List<MinerPrediction> freshPredictions;
        if (isBacktest)
        {
            // For backtests, get historical predictions from the signal date
            freshPredictions = await _synthApiClient.GetHistoricalMinerPredictionsAsync(
                missingMinerUids, asset, signalDate, config.TimeIncrement, timeLength, config);

            if (!freshPredictions.Any() && missingMinerUids.Any())
            {
                _logger.LogWarning(
                    $"No historical predictions available for {missingMinerUids.Count} miners on {signalDate:yyyy-MM-dd HH:mm}");
            }
        }
        else
        {
            // For live trading, get current predictions
            freshPredictions = await _synthApiClient.GetMinerPredictionsAsync(
                missingMinerUids, asset, config.TimeIncrement, timeLength, config);
        }

        // Map MinerInfo to fresh MinerPrediction objects based on NeuronUid -> MinerUid mapping
        foreach (var prediction in freshPredictions)
        {
            var matchingMinerInfo = leaderboard.FirstOrDefault(m => m.NeuronUid == prediction.MinerUid);
            if (matchingMinerInfo != null)
            {
                prediction.MinerInfo = matchingMinerInfo;
            }
        }

        // Cache the fresh predictions individually
        if (freshPredictions.Any())
        {
            var individualPredictionsToCache = freshPredictions.Select(prediction => new SynthPrediction
            {
                Asset = asset,
                MinerUid = prediction.MinerUid,
                TimeIncrement = config.TimeIncrement,
                TimeLength = timeLength,
                SignalDate = isBacktest ? signalDate : null,
                IsBacktest = isBacktest,
                Prediction = prediction
            }).ToList();

            await _synthRepository.SaveIndividualPredictionsAsync(individualPredictionsToCache);
        }

        // Combine cached and fresh predictions
        var allPredictions = new List<MinerPrediction>();
        allPredictions.AddRange(cachedIndividualPredictions.Select(p => p.Prediction));
        allPredictions.AddRange(freshPredictions);

        // Map MinerInfo to cached predictions as well (in case they weren't mapped before)
        foreach (var prediction in allPredictions.Where(p => p.MinerInfo == null))
        {
            var matchingMinerInfo = leaderboard.FirstOrDefault(m => m.NeuronUid == prediction.MinerUid);
            if (matchingMinerInfo != null)
            {
                prediction.MinerInfo = matchingMinerInfo;
            }
        }

        _logger.LogInformation(
            $"📈 **Synth Individual Cache** - Total predictions assembled: {allPredictions.Count} for {asset} {(isBacktest ? "HISTORICAL" : "LIVE")}");

        return allPredictions;
    }

    /// <summary>
    /// Calculates probability from aggregated prediction paths
    /// </summary>
    private decimal CalculateProbabilityFromPaths(
        List<MinerPrediction> predictions,
        decimal currentPrice,
        decimal targetPrice,
        int timeHorizonSeconds,
        bool isLongPosition)
    {
        var allPaths = new List<List<PricePoint>>();

        // Aggregate all simulation paths from all miners
        foreach (var prediction in predictions)
        {
            if (prediction.Prediction != null)
            {
                allPaths.AddRange(prediction.Prediction);
            }
        }

        if (!allPaths.Any())
        {
            _logger.LogWarning("No simulation paths available for probability calculation");
            return 0m;
        }

        var pathsCrossingThreshold = 0;
        var totalPaths = allPaths.Count;
        var maxTimeFromNow = DateTime.UtcNow.AddSeconds(timeHorizonSeconds);

        foreach (var path in allPaths)
        {
            if (!path.Any()) continue;

            var predictionStartTime = predictions.FirstOrDefault()?.GetStartDateTime() ?? DateTime.UtcNow;

            foreach (var pricePoint in path)
            {
                var pointTime = pricePoint.GetDateTime();

                // Skip if point is beyond our time horizon
                if (pointTime > maxTimeFromNow) break;

                // Check if target price is reached based on position direction
                bool targetReached = false;

                if (isLongPosition)
                {
                    // For long positions, we're interested in price dropping to target (liquidation/stop-loss)
                    targetReached = pricePoint.Price <= targetPrice;
                }
                else
                {
                    // For short positions, we're interested in price rising to target (liquidation/stop-loss)
                    targetReached = pricePoint.Price >= targetPrice;
                }

                if (targetReached)
                {
                    pathsCrossingThreshold++;
                    break; // This path crossed the threshold, move to next path
                }
            }
        }

        var probability = totalPaths > 0 ? (decimal)pathsCrossingThreshold / totalPaths : 0m;

        _logger.LogDebug(
            $"🧮 **Probability Calculation** - {pathsCrossingThreshold}/{totalPaths} paths crossed threshold = {probability:P2}");

        return probability;
    }
}