"""
Confidence Analysis Module - Backtesting Validation
====================================================
Analyzes prediction confidence vs actual performance.

This module provides tools to:
- Generate calibration curves
- Create reliability diagrams
- Calculate Expected Calibration Error (ECE)
- Analyze performance by confidence bands

Ensures that model confidence scores are well-calibrated
with actual trading outcomes.

Author: ML-Specialist (NEXUS v4.0)
Version: 1.0.0
Created: 2026-01-25
"""

import numpy as np
import pandas as pd
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple, Any, Union
from datetime import datetime
from loguru import logger


@dataclass
class CalibrationData:
    """
    Data for calibration curve and reliability diagram.

    Contains binned confidence and accuracy values
    for visualization and analysis.
    """

    # Bin information
    bin_centers: np.ndarray = field(default_factory=lambda: np.array([]))
    bin_edges: np.ndarray = field(default_factory=lambda: np.array([]))

    # Calibration values
    bin_confidences: np.ndarray = field(default_factory=lambda: np.array([]))
    bin_accuracies: np.ndarray = field(default_factory=lambda: np.array([]))
    bin_counts: np.ndarray = field(default_factory=lambda: np.array([]))

    # Error metrics
    bin_errors: np.ndarray = field(default_factory=lambda: np.array([]))

    # Overall metrics
    mean_confidence: float = 0.0
    mean_accuracy: float = 0.0
    correlation: float = 0.0

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            'bin_centers': self.bin_centers.tolist(),
            'bin_edges': self.bin_edges.tolist(),
            'bin_confidences': self.bin_confidences.tolist(),
            'bin_accuracies': self.bin_accuracies.tolist(),
            'bin_counts': self.bin_counts.tolist(),
            'bin_errors': self.bin_errors.tolist(),
            'mean_confidence': float(self.mean_confidence),
            'mean_accuracy': float(self.mean_accuracy),
            'correlation': float(self.correlation)
        }


@dataclass
class ReliabilityDiagramData:
    """
    Data structure for reliability diagram visualization.

    Contains all data needed to plot a reliability diagram
    showing calibration quality.
    """

    # Main diagram data
    bin_centers: np.ndarray = field(default_factory=lambda: np.array([]))
    bin_accuracies: np.ndarray = field(default_factory=lambda: np.array([]))
    bin_confidences: np.ndarray = field(default_factory=lambda: np.array([]))
    bin_counts: np.ndarray = field(default_factory=lambda: np.array([]))

    # Reference lines
    perfect_calibration: np.ndarray = field(default_factory=lambda: np.array([]))

    # Gap bars (for gap visualization)
    gap_positive: np.ndarray = field(default_factory=lambda: np.array([]))
    gap_negative: np.ndarray = field(default_factory=lambda: np.array([]))

    # Histogram data
    histogram_bins: np.ndarray = field(default_factory=lambda: np.array([]))
    histogram_counts: np.ndarray = field(default_factory=lambda: np.array([]))

    # Summary statistics
    ece: float = 0.0
    mce: float = 0.0
    overconfidence_ratio: float = 0.0
    underconfidence_ratio: float = 0.0

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            'bin_centers': self.bin_centers.tolist(),
            'bin_accuracies': self.bin_accuracies.tolist(),
            'bin_confidences': self.bin_confidences.tolist(),
            'bin_counts': self.bin_counts.tolist(),
            'perfect_calibration': self.perfect_calibration.tolist(),
            'gap_positive': self.gap_positive.tolist(),
            'gap_negative': self.gap_negative.tolist(),
            'ece': float(self.ece),
            'mce': float(self.mce),
            'overconfidence_ratio': float(self.overconfidence_ratio),
            'underconfidence_ratio': float(self.underconfidence_ratio)
        }


@dataclass
class ConfidenceBandAnalysis:
    """
    Analysis results for confidence bands.

    Breaks down performance metrics by confidence level.
    """

    bands: Dict[str, Dict[str, Any]] = field(default_factory=dict)
    overall_metrics: Dict[str, float] = field(default_factory=dict)
    recommendations: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            'bands': self.bands,
            'overall_metrics': self.overall_metrics,
            'recommendations': self.recommendations
        }

    def print_summary(self):
        """Print formatted band analysis summary."""
        print("\n" + "=" * 70)
        print("CONFIDENCE BAND ANALYSIS")
        print("=" * 70)

        print("\n--- Overall Metrics ---")
        for key, value in self.overall_metrics.items():
            if isinstance(value, float):
                print(f"  {key}: {value:.4f}")
            else:
                print(f"  {key}: {value}")

        print("\n--- Per Band Breakdown ---")
        for band_name, stats in sorted(self.bands.items()):
            print(f"\n  {band_name}:")
            print(f"    Trades: {stats.get('count', 0)}")
            print(f"    Win Rate: {stats.get('win_rate', 0):.2%}")
            print(f"    Avg Confidence: {stats.get('avg_confidence', 0):.2%}")
            print(f"    Calibration Error: {stats.get('calibration_error', 0):.4f}")
            print(f"    Profit Factor: {stats.get('profit_factor', 0):.2f}")

        if self.recommendations:
            print("\n--- Recommendations ---")
            for i, rec in enumerate(self.recommendations, 1):
                print(f"  {i}. {rec}")

        print("=" * 70 + "\n")


class ConfidenceAnalyzer:
    """
    Analyzes prediction confidence vs actual performance.

    Provides comprehensive analysis of how well confidence
    scores predict actual trading outcomes, including:
    - Calibration curves
    - Reliability diagrams
    - Expected Calibration Error (ECE)
    - Per-band performance analysis

    Usage:
        analyzer = ConfidenceAnalyzer(n_bins=15)

        # From predictions and actuals
        calibration = analyzer.calibration_curve(predictions, actuals)
        diagram = analyzer.reliability_diagram(predictions, actuals)
        ece = analyzer.expected_calibration_error(predictions, actuals)

        # From trades
        band_analysis = analyzer.analyze_confidence_bands(trades)
        band_analysis.print_summary()
    """

    DEFAULT_BINS = 15
    DEFAULT_BANDS = [0.5, 0.6, 0.7, 0.8, 0.9]

    def __init__(
        self,
        n_bins: int = 15,
        confidence_bands: Optional[List[float]] = None
    ):
        """
        Initialize the confidence analyzer.

        Args:
            n_bins: Number of bins for calibration analysis
            confidence_bands: Confidence thresholds for band analysis
        """
        self.n_bins = n_bins
        self.confidence_bands = confidence_bands or self.DEFAULT_BANDS

        logger.info(f"ConfidenceAnalyzer initialized with n_bins={n_bins}")

    def calibration_curve(
        self,
        predictions: Union[np.ndarray, List[float], pd.Series],
        actuals: Union[np.ndarray, List[int], pd.Series],
        n_bins: Optional[int] = None
    ) -> CalibrationData:
        """
        Generate calibration curve data.

        The calibration curve shows the relationship between
        predicted confidence and actual accuracy. A perfectly
        calibrated model has confidence = accuracy for all bins.

        Args:
            predictions: Predicted probabilities (0 to 1)
            actuals: Actual binary outcomes (0 or 1)
            n_bins: Number of bins (uses instance default if None)

        Returns:
            CalibrationData with binned confidence and accuracy
        """
        predictions = np.asarray(predictions).ravel()
        actuals = np.asarray(actuals).ravel()

        if len(predictions) != len(actuals):
            raise ValueError("predictions and actuals must have same length")

        n_bins = n_bins or self.n_bins

        # Clip predictions to valid range
        predictions = np.clip(predictions, 0.0, 1.0)

        # Create bins
        bin_edges = np.linspace(0, 1, n_bins + 1)
        bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2

        # Calculate statistics per bin
        bin_confidences = []
        bin_accuracies = []
        bin_counts = []
        bin_errors = []

        for i in range(n_bins):
            lower = bin_edges[i]
            upper = bin_edges[i + 1]

            # Include upper bound in last bin
            if i == n_bins - 1:
                in_bin = (predictions >= lower) & (predictions <= upper)
            else:
                in_bin = (predictions >= lower) & (predictions < upper)

            count = np.sum(in_bin)
            bin_counts.append(count)

            if count > 0:
                avg_conf = np.mean(predictions[in_bin])
                avg_acc = np.mean(actuals[in_bin])
                error = abs(avg_conf - avg_acc)
            else:
                avg_conf = bin_centers[i]
                avg_acc = np.nan
                error = np.nan

            bin_confidences.append(avg_conf)
            bin_accuracies.append(avg_acc)
            bin_errors.append(error)

        # Calculate correlation
        valid_mask = ~np.isnan(bin_accuracies)
        if np.sum(valid_mask) > 1:
            correlation = np.corrcoef(
                np.array(bin_confidences)[valid_mask],
                np.array(bin_accuracies)[valid_mask]
            )[0, 1]
        else:
            correlation = 0.0

        return CalibrationData(
            bin_centers=np.array(bin_centers),
            bin_edges=bin_edges,
            bin_confidences=np.array(bin_confidences),
            bin_accuracies=np.array(bin_accuracies),
            bin_counts=np.array(bin_counts),
            bin_errors=np.array(bin_errors),
            mean_confidence=float(np.mean(predictions)),
            mean_accuracy=float(np.mean(actuals)),
            correlation=float(correlation) if not np.isnan(correlation) else 0.0
        )

    def reliability_diagram(
        self,
        predictions: Union[np.ndarray, List[float], pd.Series],
        actuals: Union[np.ndarray, List[int], pd.Series],
        n_bins: Optional[int] = None
    ) -> ReliabilityDiagramData:
        """
        Generate data for reliability diagram visualization.

        The reliability diagram shows:
        - Bar chart of accuracy per confidence bin
        - Reference diagonal (perfect calibration)
        - Gap visualization (over/under confidence)
        - Histogram of prediction distribution

        Args:
            predictions: Predicted probabilities (0 to 1)
            actuals: Actual binary outcomes (0 or 1)
            n_bins: Number of bins (uses instance default if None)

        Returns:
            ReliabilityDiagramData for visualization
        """
        predictions = np.asarray(predictions).ravel()
        actuals = np.asarray(actuals).ravel()
        n_bins = n_bins or self.n_bins

        # Get calibration data
        calibration = self.calibration_curve(predictions, actuals, n_bins)

        # Perfect calibration line
        perfect_calibration = np.linspace(0, 1, 100)

        # Calculate gaps
        gap_positive = np.zeros(n_bins)
        gap_negative = np.zeros(n_bins)

        for i in range(n_bins):
            if not np.isnan(calibration.bin_accuracies[i]):
                gap = calibration.bin_accuracies[i] - calibration.bin_confidences[i]
                if gap > 0:
                    gap_positive[i] = gap
                else:
                    gap_negative[i] = abs(gap)

        # Calculate ECE and MCE
        ece = self.expected_calibration_error(predictions, actuals, n_bins)
        mce = self._maximum_calibration_error(predictions, actuals, n_bins)

        # Calculate over/under confidence ratios
        overconfident_bins = np.sum(
            (calibration.bin_confidences > calibration.bin_accuracies) &
            (calibration.bin_counts > 0) &
            ~np.isnan(calibration.bin_accuracies)
        )
        underconfident_bins = np.sum(
            (calibration.bin_confidences < calibration.bin_accuracies) &
            (calibration.bin_counts > 0) &
            ~np.isnan(calibration.bin_accuracies)
        )
        total_valid = np.sum(
            (calibration.bin_counts > 0) &
            ~np.isnan(calibration.bin_accuracies)
        )

        overconfidence_ratio = overconfident_bins / total_valid if total_valid > 0 else 0
        underconfidence_ratio = underconfident_bins / total_valid if total_valid > 0 else 0

        return ReliabilityDiagramData(
            bin_centers=calibration.bin_centers,
            bin_accuracies=calibration.bin_accuracies,
            bin_confidences=calibration.bin_confidences,
            bin_counts=calibration.bin_counts,
            perfect_calibration=perfect_calibration,
            gap_positive=gap_positive,
            gap_negative=gap_negative,
            histogram_bins=calibration.bin_edges,
            histogram_counts=calibration.bin_counts,
            ece=ece,
            mce=mce,
            overconfidence_ratio=overconfidence_ratio,
            underconfidence_ratio=underconfidence_ratio
        )

    def expected_calibration_error(
        self,
        predictions: Union[np.ndarray, List[float], pd.Series],
        actuals: Union[np.ndarray, List[int], pd.Series],
        n_bins: Optional[int] = None
    ) -> float:
        """
        Calculate Expected Calibration Error (ECE).

        ECE = sum_b (|B_b|/n) * |acc(B_b) - conf(B_b)|

        Lower ECE indicates better calibration.

        Args:
            predictions: Predicted probabilities (0 to 1)
            actuals: Actual binary outcomes (0 or 1)
            n_bins: Number of bins (uses instance default if None)

        Returns:
            ECE value (0 is perfect calibration)
        """
        predictions = np.asarray(predictions).ravel()
        actuals = np.asarray(actuals).ravel()
        n_bins = n_bins or self.n_bins

        if len(predictions) == 0:
            return 0.0

        # Clip predictions
        predictions = np.clip(predictions, 0.0, 1.0)

        # Create bins
        bin_edges = np.linspace(0, 1, n_bins + 1)

        ece = 0.0
        total_samples = len(predictions)

        for i in range(n_bins):
            lower = bin_edges[i]
            upper = bin_edges[i + 1]

            # Include upper bound in last bin
            if i == n_bins - 1:
                in_bin = (predictions >= lower) & (predictions <= upper)
            else:
                in_bin = (predictions >= lower) & (predictions < upper)

            count = np.sum(in_bin)

            if count > 0:
                avg_confidence = np.mean(predictions[in_bin])
                avg_accuracy = np.mean(actuals[in_bin])
                bin_weight = count / total_samples
                ece += bin_weight * abs(avg_confidence - avg_accuracy)

        return float(ece)

    def _maximum_calibration_error(
        self,
        predictions: Union[np.ndarray, List[float], pd.Series],
        actuals: Union[np.ndarray, List[int], pd.Series],
        n_bins: Optional[int] = None
    ) -> float:
        """
        Calculate Maximum Calibration Error (MCE).

        MCE = max_b |acc(B_b) - conf(B_b)|

        Args:
            predictions: Predicted probabilities
            actuals: Actual outcomes
            n_bins: Number of bins

        Returns:
            MCE value
        """
        predictions = np.asarray(predictions).ravel()
        actuals = np.asarray(actuals).ravel()
        n_bins = n_bins or self.n_bins

        if len(predictions) == 0:
            return 0.0

        predictions = np.clip(predictions, 0.0, 1.0)
        bin_edges = np.linspace(0, 1, n_bins + 1)

        max_error = 0.0

        for i in range(n_bins):
            lower = bin_edges[i]
            upper = bin_edges[i + 1]

            if i == n_bins - 1:
                in_bin = (predictions >= lower) & (predictions <= upper)
            else:
                in_bin = (predictions >= lower) & (predictions < upper)

            count = np.sum(in_bin)

            if count > 0:
                avg_confidence = np.mean(predictions[in_bin])
                avg_accuracy = np.mean(actuals[in_bin])
                error = abs(avg_confidence - avg_accuracy)
                max_error = max(max_error, error)

        return float(max_error)

    def analyze_confidence_bands(
        self,
        trades: List[Any],
        bands: Optional[List[float]] = None,
        confidence_field: str = 'confidence'
    ) -> ConfidenceBandAnalysis:
        """
        Analyze performance by confidence bands.

        Breaks down trading performance by confidence level
        to understand how prediction confidence relates to
        actual trading success.

        Args:
            trades: List of trade records
            bands: Confidence band thresholds
            confidence_field: Field name for confidence

        Returns:
            ConfidenceBandAnalysis with per-band metrics
        """
        bands = bands or self.confidence_bands
        result = ConfidenceBandAnalysis()

        if not trades:
            return result

        # Get closed trades only
        closed_trades = [
            t for t in trades
            if getattr(t, 'result', 'open') != 'open'
        ]

        if not closed_trades:
            return result

        # Extract confidence values
        def get_confidence(trade):
            conf = getattr(trade, confidence_field, None)
            if conf is None:
                conf = getattr(trade, 'prob_tp_first', 0.5)
            return conf if conf is not None else 0.5

        # Analyze each band
        all_bands = bands + [1.0]  # Add upper bound

        all_confidences = []
        all_actuals = []

        for i in range(len(all_bands) - 1):
            lower = all_bands[i]
            upper = all_bands[i + 1]
            band_name = f"{lower:.0%}-{upper:.0%}"

            # Filter trades in this band
            band_trades = [
                t for t in closed_trades
                if lower <= get_confidence(t) < upper
            ]

            if band_trades:
                # Extract metrics
                confidences = [get_confidence(t) for t in band_trades]
                pnls = [t.pnl for t in band_trades]
                outcomes = [1 if p > 0 else 0 for p in pnls]

                all_confidences.extend(confidences)
                all_actuals.extend(outcomes)

                # Calculate band statistics
                win_rate = np.mean(outcomes)
                avg_confidence = np.mean(confidences)
                calibration_error = abs(avg_confidence - win_rate)

                wins = [p for p in pnls if p > 0]
                losses = [abs(p) for p in pnls if p < 0]
                profit_factor = sum(wins) / sum(losses) if losses else float('inf')

                avg_pnl = np.mean(pnls)
                total_pnl = sum(pnls)

                result.bands[band_name] = {
                    'count': len(band_trades),
                    'win_rate': win_rate,
                    'avg_confidence': avg_confidence,
                    'calibration_error': calibration_error,
                    'profit_factor': profit_factor,
                    'avg_pnl': avg_pnl,
                    'total_pnl': total_pnl,
                    'confidence_range': (lower, upper),
                    'is_calibrated': calibration_error < 0.10
                }

        # Calculate overall metrics
        if all_confidences:
            result.overall_metrics = {
                'total_trades': len(closed_trades),
                'ece': self.expected_calibration_error(
                    np.array(all_confidences),
                    np.array(all_actuals)
                ),
                'mean_confidence': np.mean(all_confidences),
                'mean_accuracy': np.mean(all_actuals),
                'calibration_correlation': float(
                    np.corrcoef(all_confidences, all_actuals)[0, 1]
                ) if len(all_confidences) > 1 else 0.0
            }

        # Generate recommendations
        result.recommendations = self._generate_band_recommendations(result)

        return result

    def _generate_band_recommendations(
        self,
        analysis: ConfidenceBandAnalysis
    ) -> List[str]:
        """
        Generate recommendations based on band analysis.

        Args:
            analysis: ConfidenceBandAnalysis result

        Returns:
            List of recommendation strings
        """
        recommendations = []

        if not analysis.bands:
            recommendations.append("Insufficient data for band analysis")
            return recommendations

        # Check for overconfidence in high bands
        high_bands = {
            k: v for k, v in analysis.bands.items()
            if v.get('confidence_range', (0, 0))[0] >= 0.7
        }

        for band_name, stats in high_bands.items():
            if stats['avg_confidence'] > stats['win_rate'] + 0.15:
                recommendations.append(
                    f"High confidence band {band_name} is overconfident: "
                    f"confidence={stats['avg_confidence']:.0%} but win_rate={stats['win_rate']:.0%}"
                )

        # Check for underconfidence in low bands
        low_bands = {
            k: v for k, v in analysis.bands.items()
            if v.get('confidence_range', (1, 1))[1] <= 0.6
        }

        for band_name, stats in low_bands.items():
            if stats['win_rate'] > stats['avg_confidence'] + 0.15:
                recommendations.append(
                    f"Low confidence band {band_name} is underconfident: "
                    f"confidence={stats['avg_confidence']:.0%} but win_rate={stats['win_rate']:.0%}"
                )

        # Check overall ECE
        ece = analysis.overall_metrics.get('ece', 0)
        if ece > 0.15:
            recommendations.append(
                f"High calibration error (ECE={ece:.3f}): "
                "Consider applying calibration techniques"
            )
        elif ece > 0.10:
            recommendations.append(
                f"Moderate calibration error (ECE={ece:.3f}): "
                "Model could benefit from calibration tuning"
            )

        # Check for bands with poor performance
        poor_bands = [
            band_name for band_name, stats in analysis.bands.items()
            if stats['win_rate'] < 0.45 and stats['count'] >= 10
        ]

        if poor_bands:
            recommendations.append(
                f"Consider filtering trades in bands: {', '.join(poor_bands)} "
                "(win rate below 45%)"
            )

        # Check correlation
        correlation = analysis.overall_metrics.get('calibration_correlation', 0)
        if correlation < 0.3:
            recommendations.append(
                f"Low confidence-accuracy correlation ({correlation:.2f}): "
                "Confidence scores may not be predictive"
            )

        return recommendations

    def compare_calibration(
        self,
        before_predictions: np.ndarray,
        before_actuals: np.ndarray,
        after_predictions: np.ndarray,
        after_actuals: Optional[np.ndarray] = None
    ) -> Dict[str, Any]:
        """
        Compare calibration before and after calibration adjustment.

        Args:
            before_predictions: Original predictions
            before_actuals: Original actuals
            after_predictions: Calibrated predictions
            after_actuals: Actuals for calibrated (defaults to before_actuals)

        Returns:
            Comparison dictionary with improvement metrics
        """
        if after_actuals is None:
            after_actuals = before_actuals

        before_ece = self.expected_calibration_error(before_predictions, before_actuals)
        after_ece = self.expected_calibration_error(after_predictions, after_actuals)

        before_mce = self._maximum_calibration_error(before_predictions, before_actuals)
        after_mce = self._maximum_calibration_error(after_predictions, after_actuals)

        before_diagram = self.reliability_diagram(before_predictions, before_actuals)
        after_diagram = self.reliability_diagram(after_predictions, after_actuals)

        ece_improvement = (before_ece - after_ece) / before_ece if before_ece > 0 else 0
        mce_improvement = (before_mce - after_mce) / before_mce if before_mce > 0 else 0

        return {
            'before': {
                'ece': before_ece,
                'mce': before_mce,
                'overconfidence_ratio': before_diagram.overconfidence_ratio,
                'underconfidence_ratio': before_diagram.underconfidence_ratio
            },
            'after': {
                'ece': after_ece,
                'mce': after_mce,
                'overconfidence_ratio': after_diagram.overconfidence_ratio,
                'underconfidence_ratio': after_diagram.underconfidence_ratio
            },
            'improvement': {
                'ece_improvement_pct': ece_improvement * 100,
                'mce_improvement_pct': mce_improvement * 100,
                'ece_reduced': before_ece - after_ece,
                'mce_reduced': before_mce - after_mce
            },
            'is_improved': after_ece < before_ece
        }


def analyze_trade_confidence(
    trades: List[Any],
    n_bins: int = 15,
    confidence_bands: Optional[List[float]] = None
) -> Tuple[CalibrationData, ConfidenceBandAnalysis]:
    """
    Convenience function to analyze trade confidence.

    Args:
        trades: List of trade records
        n_bins: Number of bins for calibration
        confidence_bands: Band thresholds

    Returns:
        Tuple of (CalibrationData, ConfidenceBandAnalysis)
    """
    analyzer = ConfidenceAnalyzer(n_bins=n_bins, confidence_bands=confidence_bands)

    # Extract predictions and actuals
    closed_trades = [t for t in trades if getattr(t, 'result', 'open') != 'open']

    predictions = []
    actuals = []

    for t in closed_trades:
        conf = getattr(t, 'confidence', None) or getattr(t, 'prob_tp_first', 0.5)
        actual = 1 if t.pnl > 0 else 0
        predictions.append(conf)
        actuals.append(actual)

    calibration = analyzer.calibration_curve(
        np.array(predictions),
        np.array(actuals)
    )

    band_analysis = analyzer.analyze_confidence_bands(trades)

    return calibration, band_analysis


if __name__ == "__main__":
    # Test confidence analyzer
    from dataclasses import dataclass as dc
    import random

    print("Testing ConfidenceAnalyzer")
    print("=" * 60)

    # Create mock trade class
    @dc
    class MockTrade:
        id: int
        pnl: float
        confidence: float
        prob_tp_first: float
        result: str

    # Generate sample trades with realistic confidence-accuracy relationship
    random.seed(42)
    trades = []

    for i in range(200):
        # Confidence level
        confidence = random.uniform(0.4, 0.95)

        # Win probability increases with confidence (but not perfectly calibrated)
        # Add some overconfidence bias
        true_win_prob = 0.35 + confidence * 0.5  # Maps [0.4, 0.95] to [0.55, 0.825]
        true_win_prob = min(true_win_prob, 0.90)

        is_win = random.random() < true_win_prob

        trade = MockTrade(
            id=i,
            pnl=10.0 if is_win else -5.0,
            confidence=confidence,
            prob_tp_first=confidence,
            result='tp' if is_win else 'sl'
        )
        trades.append(trade)

    # Initialize analyzer
    analyzer = ConfidenceAnalyzer(n_bins=10)

    # Extract predictions and actuals
    predictions = np.array([t.confidence for t in trades])
    actuals = np.array([1 if t.pnl > 0 else 0 for t in trades])

    # Test calibration curve
    print("\n--- Calibration Curve ---")
    calibration = analyzer.calibration_curve(predictions, actuals)
    print(f"Mean Confidence: {calibration.mean_confidence:.3f}")
    print(f"Mean Accuracy: {calibration.mean_accuracy:.3f}")
    print(f"Correlation: {calibration.correlation:.3f}")

    # Test reliability diagram
    print("\n--- Reliability Diagram Data ---")
    diagram = analyzer.reliability_diagram(predictions, actuals)
    print(f"ECE: {diagram.ece:.4f}")
    print(f"MCE: {diagram.mce:.4f}")
    print(f"Overconfidence Ratio: {diagram.overconfidence_ratio:.2%}")
    print(f"Underconfidence Ratio: {diagram.underconfidence_ratio:.2%}")

    # Test ECE directly
    print("\n--- Expected Calibration Error ---")
    ece = analyzer.expected_calibration_error(predictions, actuals)
    print(f"ECE: {ece:.4f}")

    # Test band analysis
    print("\n--- Confidence Band Analysis ---")
    band_analysis = analyzer.analyze_confidence_bands(trades)
    band_analysis.print_summary()

    # Test comparison
    print("\n--- Calibration Comparison ---")
    # Simulate calibrated predictions (adjusted to be better calibrated)
    calibrated_predictions = predictions * 0.85 + 0.1  # Reduce overconfidence
    calibrated_predictions = np.clip(calibrated_predictions, 0.01, 0.99)

    comparison = analyzer.compare_calibration(
        predictions, actuals,
        calibrated_predictions, actuals
    )

    print(f"Before ECE: {comparison['before']['ece']:.4f}")
    print(f"After ECE: {comparison['after']['ece']:.4f}")
    print(f"ECE Improvement: {comparison['improvement']['ece_improvement_pct']:.1f}%")
    print(f"Is Improved: {comparison['is_improved']}")

    print("\nConfidenceAnalyzer test complete!")
