"""
Trading Metrics - Phase 2
Comprehensive metrics for trading performance evaluation

This module provides:
1. PerformanceMetrics - Complete performance metrics dataclass
2. TradingMetrics - Trading-specific metrics (legacy compatibility)
3. MetricsCalculator - Calculator class with all metric methods

Target metrics from spec:
- Direction Accuracy >= 60%
- Sharpe Ratio >= 1.5 (ensemble)
- Max Drawdown <= 15%
- Win Rate for 80% effectiveness target

Author: ML-Specialist (NEXUS v4.0)
Version: 2.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, timedelta
from loguru import logger


@dataclass
class PerformanceMetrics:
    """
    Complete performance metrics for backtesting validation.

    Contains all metrics needed for strategy evaluation including:
    - Returns metrics (total, annualized, daily)
    - Risk-adjusted returns (Sharpe, Sortino, Calmar)
    - Drawdown metrics
    - Trade statistics
    - Direction accuracy
    - Consistency metrics

    Target thresholds:
    - Direction Accuracy: >= 60%
    - Sharpe Ratio: >= 1.5
    - Max Drawdown: <= 15%
    - Win Rate: target 80% effectiveness
    """

    # Returns metrics
    total_return: float = 0.0
    total_return_pct: float = 0.0
    annualized_return: float = 0.0
    daily_return_mean: float = 0.0
    daily_return_std: float = 0.0

    # Risk-adjusted returns
    sharpe_ratio: float = 0.0
    sortino_ratio: float = 0.0
    calmar_ratio: float = 0.0
    information_ratio: float = 0.0
    omega_ratio: float = 0.0

    # Drawdown metrics
    max_drawdown: float = 0.0
    max_drawdown_pct: float = 0.0
    max_drawdown_duration: int = 0
    avg_drawdown: float = 0.0
    avg_drawdown_duration: float = 0.0
    recovery_factor: float = 0.0

    # Trade statistics
    total_trades: int = 0
    winning_trades: int = 0
    losing_trades: int = 0
    breakeven_trades: int = 0
    win_rate: float = 0.0
    loss_rate: float = 0.0

    # Profit metrics
    gross_profit: float = 0.0
    gross_loss: float = 0.0
    net_profit: float = 0.0
    profit_factor: float = 0.0
    avg_win: float = 0.0
    avg_loss: float = 0.0
    avg_trade: float = 0.0
    avg_win_loss_ratio: float = 0.0
    expectancy: float = 0.0
    expectancy_ratio: float = 0.0

    # Direction accuracy
    direction_accuracy: float = 0.0
    long_accuracy: float = 0.0
    short_accuracy: float = 0.0
    long_trades: int = 0
    short_trades: int = 0

    # Extremes
    largest_win: float = 0.0
    largest_loss: float = 0.0
    largest_win_streak: int = 0
    largest_loss_streak: int = 0

    # Consistency metrics
    positive_periods_ratio: float = 0.0
    gain_to_pain_ratio: float = 0.0
    tail_ratio: float = 0.0
    common_sense_ratio: float = 0.0

    # Time metrics
    avg_trade_duration: float = 0.0
    avg_win_duration: float = 0.0
    avg_loss_duration: float = 0.0
    total_time_in_market: float = 0.0

    # Period info
    start_date: Optional[datetime] = None
    end_date: Optional[datetime] = None
    trading_days: int = 0
    total_periods: int = 0

    # Segmented metrics
    metrics_by_symbol: Dict[str, Dict[str, float]] = field(default_factory=dict)
    metrics_by_regime: Dict[str, Dict[str, float]] = field(default_factory=dict)
    metrics_by_confidence: Dict[str, Dict[str, float]] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            # Returns
            'total_return': float(self.total_return),
            'total_return_pct': float(self.total_return_pct),
            'annualized_return': float(self.annualized_return),
            'daily_return_mean': float(self.daily_return_mean),
            'daily_return_std': float(self.daily_return_std),

            # Risk-adjusted
            'sharpe_ratio': float(self.sharpe_ratio),
            'sortino_ratio': float(self.sortino_ratio),
            'calmar_ratio': float(self.calmar_ratio),
            'information_ratio': float(self.information_ratio),
            'omega_ratio': float(self.omega_ratio),

            # Drawdown
            'max_drawdown': float(self.max_drawdown),
            'max_drawdown_pct': float(self.max_drawdown_pct),
            'max_drawdown_duration': int(self.max_drawdown_duration),
            'avg_drawdown': float(self.avg_drawdown),
            'recovery_factor': float(self.recovery_factor),

            # Trade stats
            'total_trades': int(self.total_trades),
            'winning_trades': int(self.winning_trades),
            'losing_trades': int(self.losing_trades),
            'win_rate': float(self.win_rate),

            # Profit
            'gross_profit': float(self.gross_profit),
            'gross_loss': float(self.gross_loss),
            'net_profit': float(self.net_profit),
            'profit_factor': float(self.profit_factor),
            'avg_win': float(self.avg_win),
            'avg_loss': float(self.avg_loss),
            'avg_win_loss_ratio': float(self.avg_win_loss_ratio),
            'expectancy': float(self.expectancy),

            # Direction
            'direction_accuracy': float(self.direction_accuracy),
            'long_accuracy': float(self.long_accuracy),
            'short_accuracy': float(self.short_accuracy),

            # Extremes
            'largest_win': float(self.largest_win),
            'largest_loss': float(self.largest_loss),
            'largest_win_streak': int(self.largest_win_streak),
            'largest_loss_streak': int(self.largest_loss_streak),

            # Consistency
            'positive_periods_ratio': float(self.positive_periods_ratio),
            'gain_to_pain_ratio': float(self.gain_to_pain_ratio),
            'tail_ratio': float(self.tail_ratio),

            # Time
            'avg_trade_duration': float(self.avg_trade_duration),
            'trading_days': int(self.trading_days),

            # Segmented
            'metrics_by_symbol': self.metrics_by_symbol,
            'metrics_by_regime': self.metrics_by_regime,
        }

    def meets_targets(
        self,
        direction_accuracy_target: float = 0.60,
        sharpe_target: float = 1.5,
        max_drawdown_target: float = 0.15,
        win_rate_target: float = 0.80
    ) -> Dict[str, bool]:
        """
        Check if metrics meet target thresholds.

        Args:
            direction_accuracy_target: Target for direction accuracy (default 60%)
            sharpe_target: Target for Sharpe ratio (default 1.5)
            max_drawdown_target: Maximum allowed drawdown (default 15%)
            win_rate_target: Target win rate (default 80%)

        Returns:
            Dictionary with target compliance for each metric
        """
        return {
            'direction_accuracy': self.direction_accuracy >= direction_accuracy_target,
            'sharpe_ratio': self.sharpe_ratio >= sharpe_target,
            'max_drawdown': self.max_drawdown_pct <= max_drawdown_target,
            'win_rate': self.win_rate >= win_rate_target,
            'all_targets_met': (
                self.direction_accuracy >= direction_accuracy_target and
                self.sharpe_ratio >= sharpe_target and
                self.max_drawdown_pct <= max_drawdown_target
            )
        }

    def print_summary(self, include_targets: bool = True):
        """Print formatted summary of metrics."""
        print("\n" + "=" * 70)
        print("PERFORMANCE METRICS SUMMARY")
        print("=" * 70)

        print("\n--- Returns ---")
        print(f"Total Return: ${self.total_return:,.2f} ({self.total_return_pct:.2%})")
        print(f"Annualized Return: {self.annualized_return:.2%}")
        print(f"Daily Return: {self.daily_return_mean:.4%} +/- {self.daily_return_std:.4%}")

        print("\n--- Risk-Adjusted Returns ---")
        print(f"Sharpe Ratio: {self.sharpe_ratio:.3f}")
        print(f"Sortino Ratio: {self.sortino_ratio:.3f}")
        print(f"Calmar Ratio: {self.calmar_ratio:.3f}")
        print(f"Omega Ratio: {self.omega_ratio:.3f}")

        print("\n--- Drawdown ---")
        print(f"Max Drawdown: ${self.max_drawdown:,.2f} ({self.max_drawdown_pct:.2%})")
        print(f"Max DD Duration: {self.max_drawdown_duration} periods")
        print(f"Avg Drawdown: ${self.avg_drawdown:,.2f}")
        print(f"Recovery Factor: {self.recovery_factor:.2f}")

        print("\n--- Trade Statistics ---")
        print(f"Total Trades: {self.total_trades}")
        print(f"Win Rate: {self.win_rate:.2%} ({self.winning_trades}W / {self.losing_trades}L)")
        print(f"Profit Factor: {self.profit_factor:.2f}")
        print(f"Expectancy: ${self.expectancy:.2f} per trade")

        print("\n--- Direction Accuracy ---")
        print(f"Overall: {self.direction_accuracy:.2%}")
        print(f"Long: {self.long_accuracy:.2%} ({self.long_trades} trades)")
        print(f"Short: {self.short_accuracy:.2%} ({self.short_trades} trades)")

        print("\n--- Profit/Loss ---")
        print(f"Net Profit: ${self.net_profit:,.2f}")
        print(f"Gross Profit: ${self.gross_profit:,.2f}")
        print(f"Gross Loss: ${self.gross_loss:,.2f}")
        print(f"Avg Win: ${self.avg_win:,.2f}")
        print(f"Avg Loss: ${self.avg_loss:,.2f}")
        print(f"Avg Win/Loss Ratio: {self.avg_win_loss_ratio:.2f}")
        print(f"Largest Win: ${self.largest_win:,.2f}")
        print(f"Largest Loss: ${self.largest_loss:,.2f}")

        print("\n--- Streaks ---")
        print(f"Largest Win Streak: {self.largest_win_streak}")
        print(f"Largest Loss Streak: {self.largest_loss_streak}")

        if include_targets:
            targets = self.meets_targets()
            print("\n--- Target Compliance ---")
            print(f"Direction Accuracy >= 60%: {'PASS' if targets['direction_accuracy'] else 'FAIL'}")
            print(f"Sharpe Ratio >= 1.5: {'PASS' if targets['sharpe_ratio'] else 'FAIL'}")
            print(f"Max Drawdown <= 15%: {'PASS' if targets['max_drawdown'] else 'FAIL'}")
            print(f"All Targets Met: {'YES' if targets['all_targets_met'] else 'NO'}")

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


@dataclass
class TradingMetrics:
    """
    Complete trading metrics for Phase 2.

    Legacy class maintained for backward compatibility.
    For new code, prefer using PerformanceMetrics.
    """

    # Basic counts
    total_trades: int = 0
    winning_trades: int = 0
    losing_trades: int = 0
    breakeven_trades: int = 0

    # Win rate
    winrate: float = 0.0

    # Profit metrics
    gross_profit: float = 0.0
    gross_loss: float = 0.0
    net_profit: float = 0.0
    profit_factor: float = 0.0

    # Average metrics
    avg_win: float = 0.0
    avg_loss: float = 0.0
    avg_trade: float = 0.0
    avg_rr_achieved: float = 0.0

    # Extremes
    largest_win: float = 0.0
    largest_loss: float = 0.0

    # Risk metrics
    max_drawdown: float = 0.0
    max_drawdown_pct: float = 0.0
    max_drawdown_duration: int = 0  # In bars/trades

    # Streaks
    max_consecutive_wins: int = 0
    max_consecutive_losses: int = 0
    current_streak: int = 0

    # Advanced ratios
    sharpe_ratio: float = 0.0
    sortino_ratio: float = 0.0
    calmar_ratio: float = 0.0

    # Win rate by R:R
    winrate_by_rr: Dict[str, float] = field(default_factory=dict)

    # Duration
    avg_trade_duration: float = 0.0  # In minutes
    avg_win_duration: float = 0.0
    avg_loss_duration: float = 0.0

    # Time period
    start_date: Optional[datetime] = None
    end_date: Optional[datetime] = None
    trading_days: int = 0

    def to_dict(self) -> Dict:
        """Convert to dictionary."""
        return {
            'total_trades': self.total_trades,
            'winning_trades': self.winning_trades,
            'losing_trades': self.losing_trades,
            'winrate': self.winrate,
            'gross_profit': self.gross_profit,
            'gross_loss': self.gross_loss,
            'net_profit': self.net_profit,
            'profit_factor': self.profit_factor,
            'avg_win': self.avg_win,
            'avg_loss': self.avg_loss,
            'avg_trade': self.avg_trade,
            'avg_rr_achieved': self.avg_rr_achieved,
            'largest_win': self.largest_win,
            'largest_loss': self.largest_loss,
            'max_drawdown': self.max_drawdown,
            'max_drawdown_pct': self.max_drawdown_pct,
            'max_consecutive_wins': self.max_consecutive_wins,
            'max_consecutive_losses': self.max_consecutive_losses,
            'sharpe_ratio': self.sharpe_ratio,
            'sortino_ratio': self.sortino_ratio,
            'calmar_ratio': self.calmar_ratio,
            'winrate_by_rr': self.winrate_by_rr,
            'avg_trade_duration': self.avg_trade_duration
        }

    def print_summary(self):
        """Print formatted summary."""
        print("\n" + "=" * 50)
        print("TRADING METRICS SUMMARY")
        print("=" * 50)
        print(f"Total Trades: {self.total_trades}")
        print(f"Win Rate: {self.winrate:.2%}")
        print(f"Profit Factor: {self.profit_factor:.2f}")
        print(f"\nNet Profit: ${self.net_profit:,.2f}")
        print(f"Gross Profit: ${self.gross_profit:,.2f}")
        print(f"Gross Loss: ${self.gross_loss:,.2f}")
        print(f"\nAvg Win: ${self.avg_win:,.2f}")
        print(f"Avg Loss: ${self.avg_loss:,.2f}")
        print(f"Avg R:R Achieved: {self.avg_rr_achieved:.2f}")
        print(f"\nMax Drawdown: ${self.max_drawdown:,.2f} ({self.max_drawdown_pct:.2%})")
        print(f"Max Consecutive Losses: {self.max_consecutive_losses}")
        print(f"\nSharpe Ratio: {self.sharpe_ratio:.2f}")
        print(f"Sortino Ratio: {self.sortino_ratio:.2f}")

        if self.winrate_by_rr:
            print("\nWin Rate by R:R:")
            for rr, rate in self.winrate_by_rr.items():
                print(f"  {rr}: {rate:.2%}")

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


@dataclass
class TradeRecord:
    """Individual trade record"""
    id: int
    entry_time: datetime
    exit_time: Optional[datetime] = None
    direction: str = 'long'       # 'long' or 'short'
    entry_price: float = 0.0
    exit_price: float = 0.0
    sl_price: float = 0.0
    tp_price: float = 0.0
    sl_distance: float = 0.0
    tp_distance: float = 0.0
    rr_config: str = 'rr_2_1'
    result: str = 'open'          # 'tp', 'sl', 'timeout', 'open'
    pnl: float = 0.0
    pnl_pct: float = 0.0
    pnl_r: float = 0.0           # PnL in R units
    duration_minutes: float = 0.0
    horizon: str = '15m'
    amd_phase: Optional[str] = None
    volatility_regime: Optional[str] = None
    confidence: float = 0.0
    prob_tp_first: float = 0.0

    def to_dict(self) -> Dict:
        return {
            'id': self.id,
            'entry_time': self.entry_time.isoformat() if self.entry_time else None,
            'exit_time': self.exit_time.isoformat() if self.exit_time else None,
            'direction': self.direction,
            'entry_price': self.entry_price,
            'exit_price': self.exit_price,
            'sl_price': self.sl_price,
            'tp_price': self.tp_price,
            'rr_config': self.rr_config,
            'result': self.result,
            'pnl': self.pnl,
            'pnl_r': self.pnl_r,
            'duration_minutes': self.duration_minutes,
            'horizon': self.horizon,
            'amd_phase': self.amd_phase,
            'volatility_regime': self.volatility_regime,
            'confidence': self.confidence,
            'prob_tp_first': self.prob_tp_first
        }


class MetricsCalculator:
    """
    Calculator for trading performance metrics.

    Provides comprehensive methods for calculating:
    - Returns (total, annualized, daily)
    - Risk-adjusted returns (Sharpe, Sortino, Calmar)
    - Drawdown analysis
    - Trade statistics
    - Direction accuracy
    - Win/loss analysis

    Target metrics from spec:
    - Direction Accuracy >= 60%
    - Sharpe Ratio >= 1.5 (ensemble)
    - Max Drawdown <= 15%
    - Win Rate for 80% effectiveness target

    Usage:
        calculator = MetricsCalculator(risk_free_rate=0.02)

        # From equity curve
        returns = calculator.calculate_returns(equity_curve)
        sharpe = calculator.calculate_sharpe_ratio(returns)

        # From trades
        metrics = calculator.calculate_all_metrics(backtest_result)
        metrics.print_summary()
    """

    ANNUALIZATION_FACTOR = 252  # Trading days per year
    TARGET_DIRECTION_ACCURACY = 0.60
    TARGET_SHARPE_RATIO = 1.5
    TARGET_MAX_DRAWDOWN = 0.15
    TARGET_WIN_RATE = 0.80

    def __init__(
        self,
        risk_free_rate: float = 0.02,
        periods_per_year: int = 252
    ):
        """
        Initialize calculator.

        Args:
            risk_free_rate: Annual risk-free rate for Sharpe calculation
            periods_per_year: Number of periods per year for annualization
        """
        self.risk_free_rate = risk_free_rate
        self.periods_per_year = periods_per_year

    def calculate_returns(
        self,
        equity_curve: Union[np.ndarray, pd.Series, List[float]],
        method: str = 'simple'
    ) -> np.ndarray:
        """
        Calculate returns from equity curve.

        Args:
            equity_curve: Array of equity values over time
            method: 'simple' for arithmetic returns, 'log' for logarithmic

        Returns:
            Array of period returns
        """
        equity = np.asarray(equity_curve).ravel()

        if len(equity) < 2:
            return np.array([0.0])

        if method == 'log':
            # Log returns
            returns = np.diff(np.log(equity))
        else:
            # Simple returns
            returns = np.diff(equity) / equity[:-1]

        # Handle inf/nan from division
        returns = np.nan_to_num(returns, nan=0.0, posinf=0.0, neginf=0.0)

        return returns

    def calculate_sharpe_ratio(
        self,
        returns: Union[np.ndarray, pd.Series, List[float]],
        risk_free_rate: Optional[float] = None,
        annualize: bool = True
    ) -> float:
        """
        Calculate Sharpe ratio.

        Sharpe = (mean_return - risk_free) / std_return * sqrt(periods)

        Args:
            returns: Array of period returns
            risk_free_rate: Risk-free rate (uses instance default if None)
            annualize: Whether to annualize the ratio

        Returns:
            Sharpe ratio (higher is better, >= 1.5 target for ensemble)
        """
        returns = np.asarray(returns).ravel()

        if len(returns) < 2:
            return 0.0

        rf = risk_free_rate if risk_free_rate is not None else self.risk_free_rate
        rf_period = rf / self.periods_per_year

        mean_return = np.mean(returns)
        std_return = np.std(returns, ddof=1)

        if std_return < 1e-10:
            return 0.0

        excess_return = mean_return - rf_period
        sharpe = excess_return / std_return

        if annualize:
            sharpe *= np.sqrt(self.periods_per_year)

        return float(sharpe)

    def calculate_sortino_ratio(
        self,
        returns: Union[np.ndarray, pd.Series, List[float]],
        risk_free_rate: Optional[float] = None,
        annualize: bool = True
    ) -> float:
        """
        Calculate Sortino ratio (only downside deviation).

        Sortino = (mean_return - risk_free) / downside_std * sqrt(periods)

        Args:
            returns: Array of period returns
            risk_free_rate: Risk-free rate (uses instance default if None)
            annualize: Whether to annualize the ratio

        Returns:
            Sortino ratio (higher is better)
        """
        returns = np.asarray(returns).ravel()

        if len(returns) < 2:
            return 0.0

        rf = risk_free_rate if risk_free_rate is not None else self.risk_free_rate
        rf_period = rf / self.periods_per_year

        mean_return = np.mean(returns)

        # Calculate downside deviation (only negative returns below target)
        target_return = rf_period
        downside_returns = returns[returns < target_return]

        if len(downside_returns) == 0:
            # No downside, return high positive value
            return float('inf') if mean_return > rf_period else 0.0

        downside_std = np.sqrt(np.mean((downside_returns - target_return) ** 2))

        if downside_std < 1e-10:
            return 0.0

        excess_return = mean_return - rf_period
        sortino = excess_return / downside_std

        if annualize:
            sortino *= np.sqrt(self.periods_per_year)

        return float(sortino)

    def calculate_max_drawdown(
        self,
        equity_curve: Union[np.ndarray, pd.Series, List[float]]
    ) -> Tuple[float, float, int]:
        """
        Calculate maximum drawdown and duration.

        Args:
            equity_curve: Array of equity values over time

        Returns:
            Tuple of (max_drawdown_value, max_drawdown_pct, max_drawdown_duration)
        """
        equity = np.asarray(equity_curve).ravel()

        if len(equity) < 2:
            return (0.0, 0.0, 0)

        # Running maximum
        running_max = np.maximum.accumulate(equity)

        # Drawdown at each point
        drawdown = running_max - equity
        drawdown_pct = np.divide(
            drawdown,
            running_max,
            out=np.zeros_like(drawdown),
            where=running_max > 0
        )

        # Maximum drawdown
        max_dd_idx = np.argmax(drawdown)
        max_dd = float(drawdown[max_dd_idx])
        max_dd_pct = float(drawdown_pct[max_dd_idx])

        # Drawdown duration (longest period below peak)
        in_drawdown = drawdown > 0
        max_duration = 0
        current_duration = 0

        for in_dd in in_drawdown:
            if in_dd:
                current_duration += 1
                max_duration = max(max_duration, current_duration)
            else:
                current_duration = 0

        return (max_dd, max_dd_pct, max_duration)

    def calculate_calmar_ratio(
        self,
        returns: Union[np.ndarray, pd.Series, List[float]],
        max_drawdown: Optional[float] = None,
        equity_curve: Optional[np.ndarray] = None
    ) -> float:
        """
        Calculate Calmar ratio (annualized return / max drawdown).

        Args:
            returns: Array of period returns
            max_drawdown: Maximum drawdown percentage (0-1)
            equity_curve: Equity curve to calculate max_drawdown if not provided

        Returns:
            Calmar ratio (higher is better)
        """
        returns = np.asarray(returns).ravel()

        if len(returns) < 2:
            return 0.0

        # Calculate annualized return
        cumulative_return = np.prod(1 + returns) - 1
        n_periods = len(returns)
        annualized_return = (1 + cumulative_return) ** (self.periods_per_year / n_periods) - 1

        # Get max drawdown if not provided
        if max_drawdown is None:
            if equity_curve is not None:
                _, max_dd_pct, _ = self.calculate_max_drawdown(equity_curve)
                max_drawdown = max_dd_pct
            else:
                # Calculate from returns
                equity = np.cumprod(1 + returns)
                _, max_dd_pct, _ = self.calculate_max_drawdown(equity)
                max_drawdown = max_dd_pct

        if max_drawdown < 1e-10:
            return float('inf') if annualized_return > 0 else 0.0

        return float(annualized_return / max_drawdown)

    def calculate_win_rate(
        self,
        trades: List['TradeRecord']
    ) -> float:
        """
        Calculate win rate from trades.

        Args:
            trades: List of trade records

        Returns:
            Win rate (0-1), target >= 0.80 for effectiveness
        """
        if not trades:
            return 0.0

        closed_trades = [t for t in trades if t.result != 'open']
        if not closed_trades:
            return 0.0

        winning = sum(1 for t in closed_trades if t.pnl > 0)
        return winning / len(closed_trades)

    def calculate_profit_factor(
        self,
        trades: List['TradeRecord']
    ) -> float:
        """
        Calculate profit factor (gross profit / gross loss).

        Args:
            trades: List of trade records

        Returns:
            Profit factor (> 1 is profitable, > 2 is good)
        """
        if not trades:
            return 0.0

        gross_profit = sum(t.pnl for t in trades if t.pnl > 0)
        gross_loss = abs(sum(t.pnl for t in trades if t.pnl < 0))

        if gross_loss < 1e-10:
            return float('inf') if gross_profit > 0 else 0.0

        return float(gross_profit / gross_loss)

    def calculate_avg_win_loss_ratio(
        self,
        trades: List['TradeRecord']
    ) -> float:
        """
        Calculate average win / average loss ratio.

        Args:
            trades: List of trade records

        Returns:
            Ratio of average win to average loss
        """
        if not trades:
            return 0.0

        wins = [t.pnl for t in trades if t.pnl > 0]
        losses = [abs(t.pnl) for t in trades if t.pnl < 0]

        if not wins or not losses:
            return 0.0

        avg_win = np.mean(wins)
        avg_loss = np.mean(losses)

        if avg_loss < 1e-10:
            return float('inf') if avg_win > 0 else 0.0

        return float(avg_win / avg_loss)

    def calculate_expectancy(
        self,
        trades: List['TradeRecord']
    ) -> float:
        """
        Calculate expectancy (expected profit per trade).

        Expectancy = (win_rate * avg_win) - (loss_rate * avg_loss)

        Args:
            trades: List of trade records

        Returns:
            Expected profit per trade
        """
        if not trades:
            return 0.0

        closed_trades = [t for t in trades if t.result != 'open']
        if not closed_trades:
            return 0.0

        wins = [t.pnl for t in closed_trades if t.pnl > 0]
        losses = [abs(t.pnl) for t in closed_trades if t.pnl < 0]

        win_rate = len(wins) / len(closed_trades)
        loss_rate = 1 - win_rate

        avg_win = np.mean(wins) if wins else 0.0
        avg_loss = np.mean(losses) if losses else 0.0

        expectancy = (win_rate * avg_win) - (loss_rate * avg_loss)

        return float(expectancy)

    def calculate_direction_accuracy(
        self,
        trades: List['TradeRecord']
    ) -> Dict[str, float]:
        """
        Calculate direction accuracy metrics.

        Args:
            trades: List of trade records with direction field

        Returns:
            Dictionary with overall, long, and short accuracy
        """
        if not trades:
            return {
                'overall': 0.0,
                'long': 0.0,
                'short': 0.0,
                'long_trades': 0,
                'short_trades': 0
            }

        closed_trades = [t for t in trades if t.result != 'open']
        if not closed_trades:
            return {
                'overall': 0.0,
                'long': 0.0,
                'short': 0.0,
                'long_trades': 0,
                'short_trades': 0
            }

        # Overall accuracy
        correct = sum(1 for t in closed_trades if t.pnl > 0)
        overall = correct / len(closed_trades)

        # Long accuracy
        long_trades = [t for t in closed_trades if t.direction == 'long']
        long_correct = sum(1 for t in long_trades if t.pnl > 0)
        long_accuracy = long_correct / len(long_trades) if long_trades else 0.0

        # Short accuracy
        short_trades = [t for t in closed_trades if t.direction == 'short']
        short_correct = sum(1 for t in short_trades if t.pnl > 0)
        short_accuracy = short_correct / len(short_trades) if short_trades else 0.0

        return {
            'overall': overall,
            'long': long_accuracy,
            'short': short_accuracy,
            'long_trades': len(long_trades),
            'short_trades': len(short_trades)
        }

    def calculate_omega_ratio(
        self,
        returns: Union[np.ndarray, pd.Series, List[float]],
        threshold: float = 0.0
    ) -> float:
        """
        Calculate Omega ratio.

        Omega = sum(returns > threshold) / sum(returns < threshold)

        Args:
            returns: Array of period returns
            threshold: Return threshold (default 0)

        Returns:
            Omega ratio (> 1 is good)
        """
        returns = np.asarray(returns).ravel()

        if len(returns) < 2:
            return 0.0

        gains = np.sum(np.maximum(returns - threshold, 0))
        losses = np.sum(np.maximum(threshold - returns, 0))

        if losses < 1e-10:
            return float('inf') if gains > 0 else 1.0

        return float(gains / losses)

    def calculate_tail_ratio(
        self,
        returns: Union[np.ndarray, pd.Series, List[float]],
        percentile: float = 0.05
    ) -> float:
        """
        Calculate tail ratio (right tail / left tail).

        Args:
            returns: Array of period returns
            percentile: Percentile for tail calculation

        Returns:
            Tail ratio (> 1 means right tail is fatter)
        """
        returns = np.asarray(returns).ravel()

        if len(returns) < 2:
            return 0.0

        right_tail = np.abs(np.percentile(returns, 100 * (1 - percentile)))
        left_tail = np.abs(np.percentile(returns, 100 * percentile))

        if left_tail < 1e-10:
            return float('inf') if right_tail > 0 else 1.0

        return float(right_tail / left_tail)

    def calculate_all_metrics(
        self,
        backtest_result: Any,
        initial_capital: float = 10000.0
    ) -> PerformanceMetrics:
        """
        Calculate all performance metrics from backtest result.

        Args:
            backtest_result: BacktestResult object with trades and equity_curve
            initial_capital: Initial capital for percentage calculations

        Returns:
            PerformanceMetrics object with all metrics calculated
        """
        metrics = PerformanceMetrics()

        # Extract data from backtest result
        trades = getattr(backtest_result, 'trades', [])
        equity_curve = getattr(backtest_result, 'equity_curve', np.array([initial_capital]))

        if isinstance(equity_curve, pd.Series):
            equity_curve = equity_curve.values

        if len(equity_curve) == 0:
            equity_curve = np.array([initial_capital])

        # Calculate returns
        returns = self.calculate_returns(equity_curve)

        # Returns metrics
        metrics.total_return = float(equity_curve[-1] - initial_capital)
        metrics.total_return_pct = float(metrics.total_return / initial_capital)

        if len(returns) > 0:
            cumulative_return = np.prod(1 + returns) - 1
            n_periods = len(returns)
            if n_periods > 0:
                metrics.annualized_return = float(
                    (1 + cumulative_return) ** (self.periods_per_year / n_periods) - 1
                )
            metrics.daily_return_mean = float(np.mean(returns))
            metrics.daily_return_std = float(np.std(returns, ddof=1)) if len(returns) > 1 else 0.0

        # Risk-adjusted returns
        metrics.sharpe_ratio = self.calculate_sharpe_ratio(returns)
        metrics.sortino_ratio = self.calculate_sortino_ratio(returns)
        max_dd, max_dd_pct, max_dd_duration = self.calculate_max_drawdown(equity_curve)
        metrics.calmar_ratio = self.calculate_calmar_ratio(returns, max_dd_pct)
        metrics.omega_ratio = self.calculate_omega_ratio(returns)

        # Drawdown metrics
        metrics.max_drawdown = max_dd
        metrics.max_drawdown_pct = max_dd_pct
        metrics.max_drawdown_duration = max_dd_duration
        metrics.recovery_factor = float(metrics.total_return / max_dd) if max_dd > 0 else 0.0

        # Trade statistics
        closed_trades = [t for t in trades if t.result != 'open']
        metrics.total_trades = len(closed_trades)

        if closed_trades:
            pnls = [t.pnl for t in closed_trades]

            metrics.winning_trades = sum(1 for p in pnls if p > 0)
            metrics.losing_trades = sum(1 for p in pnls if p < 0)
            metrics.breakeven_trades = sum(1 for p in pnls if p == 0)

            metrics.win_rate = metrics.winning_trades / len(closed_trades)
            metrics.loss_rate = metrics.losing_trades / len(closed_trades)

            # Profit metrics
            wins = [p for p in pnls if p > 0]
            losses = [p for p in pnls if p < 0]

            metrics.gross_profit = float(sum(wins))
            metrics.gross_loss = float(abs(sum(losses)))
            metrics.net_profit = float(sum(pnls))

            metrics.profit_factor = self.calculate_profit_factor(closed_trades)
            metrics.avg_win = float(np.mean(wins)) if wins else 0.0
            metrics.avg_loss = float(np.mean([abs(l) for l in losses])) if losses else 0.0
            metrics.avg_trade = float(np.mean(pnls))
            metrics.avg_win_loss_ratio = self.calculate_avg_win_loss_ratio(closed_trades)
            metrics.expectancy = self.calculate_expectancy(closed_trades)

            if metrics.avg_loss > 0:
                metrics.expectancy_ratio = metrics.expectancy / metrics.avg_loss

            # Extremes
            metrics.largest_win = float(max(pnls)) if pnls else 0.0
            metrics.largest_loss = float(min(pnls)) if pnls else 0.0

            # Streaks
            max_wins, max_losses = self._calculate_streaks(pnls)
            metrics.largest_win_streak = max_wins
            metrics.largest_loss_streak = max_losses

            # Direction accuracy
            direction_stats = self.calculate_direction_accuracy(closed_trades)
            metrics.direction_accuracy = direction_stats['overall']
            metrics.long_accuracy = direction_stats['long']
            metrics.short_accuracy = direction_stats['short']
            metrics.long_trades = direction_stats['long_trades']
            metrics.short_trades = direction_stats['short_trades']

            # Duration
            durations = [t.duration_minutes for t in closed_trades if t.duration_minutes > 0]
            if durations:
                metrics.avg_trade_duration = float(np.mean(durations))

            win_durations = [t.duration_minutes for t in closed_trades if t.pnl > 0]
            loss_durations = [t.duration_minutes for t in closed_trades if t.pnl < 0]

            metrics.avg_win_duration = float(np.mean(win_durations)) if win_durations else 0.0
            metrics.avg_loss_duration = float(np.mean(loss_durations)) if loss_durations else 0.0

        # Consistency metrics
        if len(returns) > 0:
            metrics.positive_periods_ratio = float(np.mean(returns > 0))
            metrics.tail_ratio = self.calculate_tail_ratio(returns)

            pain = np.sum(np.abs(returns[returns < 0]))
            if pain > 0:
                metrics.gain_to_pain_ratio = float(np.sum(returns) / pain)

        # Time metrics
        if closed_trades:
            entry_times = [t.entry_time for t in closed_trades if t.entry_time]
            if entry_times:
                metrics.start_date = min(entry_times)
                metrics.end_date = max(entry_times)
                if metrics.start_date and metrics.end_date:
                    metrics.trading_days = (metrics.end_date - metrics.start_date).days

        metrics.total_periods = len(equity_curve)

        return metrics

    def calculate_metrics(
        self,
        trades: List['TradeRecord'],
        initial_capital: float = 10000.0
    ) -> TradingMetrics:
        """
        Calculate all trading metrics from trade list

        Args:
            trades: List of TradeRecord objects
            initial_capital: Starting capital

        Returns:
            TradingMetrics object
        """
        if not trades:
            return TradingMetrics()

        metrics = TradingMetrics()

        # Filter closed trades
        closed_trades = [t for t in trades if t.result != 'open']
        if not closed_trades:
            return metrics

        # Basic counts
        metrics.total_trades = len(closed_trades)

        pnls = [t.pnl for t in closed_trades]
        pnl_array = np.array(pnls)

        metrics.winning_trades = sum(1 for pnl in pnls if pnl > 0)
        metrics.losing_trades = sum(1 for pnl in pnls if pnl < 0)
        metrics.breakeven_trades = sum(1 for pnl in pnls if pnl == 0)

        # Win rate
        metrics.winrate = metrics.winning_trades / metrics.total_trades if metrics.total_trades > 0 else 0

        # Profit metrics
        wins = [pnl for pnl in pnls if pnl > 0]
        losses = [pnl for pnl in pnls if pnl < 0]

        metrics.gross_profit = sum(wins) if wins else 0
        metrics.gross_loss = abs(sum(losses)) if losses else 0
        metrics.net_profit = metrics.gross_profit - metrics.gross_loss
        metrics.profit_factor = metrics.gross_profit / metrics.gross_loss if metrics.gross_loss > 0 else float('inf')

        # Averages
        metrics.avg_win = np.mean(wins) if wins else 0
        metrics.avg_loss = abs(np.mean(losses)) if losses else 0
        metrics.avg_trade = np.mean(pnls)

        # R:R achieved
        r_values = [t.pnl_r for t in closed_trades if t.pnl_r != 0]
        metrics.avg_rr_achieved = np.mean(r_values) if r_values else 0

        # Extremes
        metrics.largest_win = max(pnls) if pnls else 0
        metrics.largest_loss = min(pnls) if pnls else 0

        # Streaks
        metrics.max_consecutive_wins, metrics.max_consecutive_losses = self._calculate_streaks(pnls)

        # Drawdown
        equity_curve = self._calculate_equity_curve(pnls, initial_capital)
        metrics.max_drawdown, metrics.max_drawdown_pct, metrics.max_drawdown_duration = \
            self._calculate_drawdown(equity_curve, initial_capital)

        # Risk-adjusted returns
        metrics.sharpe_ratio = self._calculate_sharpe(pnls, initial_capital)
        metrics.sortino_ratio = self._calculate_sortino(pnls, initial_capital)
        metrics.calmar_ratio = self._calculate_calmar(pnls, metrics.max_drawdown, initial_capital)

        # Win rate by R:R
        metrics.winrate_by_rr = self.calculate_winrate_by_rr(closed_trades)

        # Duration
        durations = [t.duration_minutes for t in closed_trades if t.duration_minutes > 0]
        if durations:
            metrics.avg_trade_duration = np.mean(durations)

        win_durations = [t.duration_minutes for t in closed_trades if t.pnl > 0 and t.duration_minutes > 0]
        loss_durations = [t.duration_minutes for t in closed_trades if t.pnl < 0 and t.duration_minutes > 0]

        metrics.avg_win_duration = np.mean(win_durations) if win_durations else 0
        metrics.avg_loss_duration = np.mean(loss_durations) if loss_durations else 0

        # Time period
        if closed_trades:
            times = [t.entry_time for t in closed_trades if t.entry_time]
            if times:
                metrics.start_date = min(times)
                metrics.end_date = max(times)
                metrics.trading_days = (metrics.end_date - metrics.start_date).days

        return metrics

    def calculate_winrate_by_rr(
        self,
        trades: List[TradeRecord],
        rr_configs: List[str] = None
    ) -> Dict[str, float]:
        """
        Calculate win rate for each R:R configuration

        Args:
            trades: List of trade records
            rr_configs: List of R:R config names to calculate

        Returns:
            Dictionary mapping R:R config to win rate
        """
        if not trades:
            return {}

        if rr_configs is None:
            rr_configs = list(set(t.rr_config for t in trades))

        winrates = {}
        for rr in rr_configs:
            rr_trades = [t for t in trades if t.rr_config == rr]
            if rr_trades:
                wins = sum(1 for t in rr_trades if t.pnl > 0)
                winrates[rr] = wins / len(rr_trades)
            else:
                winrates[rr] = 0.0

        return winrates

    def calculate_profit_factor(
        self,
        trades: List[TradeRecord]
    ) -> float:
        """Calculate profit factor"""
        if not trades:
            return 0.0

        gross_profit = sum(t.pnl for t in trades if t.pnl > 0)
        gross_loss = abs(sum(t.pnl for t in trades if t.pnl < 0))

        if gross_loss == 0:
            return float('inf') if gross_profit > 0 else 0.0

        return gross_profit / gross_loss

    def segment_metrics(
        self,
        trades: List[TradeRecord],
        initial_capital: float = 10000.0
    ) -> Dict[str, Dict[str, TradingMetrics]]:
        """
        Calculate metrics segmented by different factors

        Args:
            trades: List of trade records
            initial_capital: Starting capital

        Returns:
            Nested dictionary with segmented metrics
        """
        segments = {
            'by_horizon': {},
            'by_rr_config': {},
            'by_amd_phase': {},
            'by_volatility': {},
            'by_direction': {}
        }

        if not trades:
            return segments

        # By horizon
        horizons = set(t.horizon for t in trades)
        for h in horizons:
            h_trades = [t for t in trades if t.horizon == h]
            segments['by_horizon'][h] = self.calculate_metrics(h_trades, initial_capital)

        # By R:R config
        rr_configs = set(t.rr_config for t in trades)
        for rr in rr_configs:
            rr_trades = [t for t in trades if t.rr_config == rr]
            segments['by_rr_config'][rr] = self.calculate_metrics(rr_trades, initial_capital)

        # By AMD phase
        phases = set(t.amd_phase for t in trades if t.amd_phase)
        for phase in phases:
            phase_trades = [t for t in trades if t.amd_phase == phase]
            segments['by_amd_phase'][phase] = self.calculate_metrics(phase_trades, initial_capital)

        # By volatility regime
        regimes = set(t.volatility_regime for t in trades if t.volatility_regime)
        for regime in regimes:
            regime_trades = [t for t in trades if t.volatility_regime == regime]
            segments['by_volatility'][regime] = self.calculate_metrics(regime_trades, initial_capital)

        # By direction
        for direction in ['long', 'short']:
            dir_trades = [t for t in trades if t.direction == direction]
            if dir_trades:
                segments['by_direction'][direction] = self.calculate_metrics(dir_trades, initial_capital)

        return segments

    def _calculate_equity_curve(
        self,
        pnls: List[float],
        initial_capital: float
    ) -> np.ndarray:
        """Calculate cumulative equity curve"""
        equity = np.zeros(len(pnls) + 1)
        equity[0] = initial_capital
        for i, pnl in enumerate(pnls):
            equity[i + 1] = equity[i] + pnl
        return equity

    def _calculate_drawdown(
        self,
        equity_curve: np.ndarray,
        initial_capital: float
    ) -> Tuple[float, float, int]:
        """Calculate maximum drawdown and duration"""
        # Running maximum
        running_max = np.maximum.accumulate(equity_curve)

        # Drawdown at each point
        drawdown = running_max - equity_curve
        drawdown_pct = drawdown / running_max

        # Maximum drawdown
        max_dd = np.max(drawdown)
        max_dd_pct = np.max(drawdown_pct)

        # Drawdown duration (longest period below peak)
        in_drawdown = drawdown > 0
        max_duration = 0
        current_duration = 0

        for in_dd in in_drawdown:
            if in_dd:
                current_duration += 1
                max_duration = max(max_duration, current_duration)
            else:
                current_duration = 0

        return max_dd, max_dd_pct, max_duration

    def _calculate_streaks(self, pnls: List[float]) -> Tuple[int, int]:
        """Calculate maximum win and loss streaks"""
        max_wins = 0
        max_losses = 0
        current_wins = 0
        current_losses = 0

        for pnl in pnls:
            if pnl > 0:
                current_wins += 1
                current_losses = 0
                max_wins = max(max_wins, current_wins)
            elif pnl < 0:
                current_losses += 1
                current_wins = 0
                max_losses = max(max_losses, current_losses)
            else:
                current_wins = 0
                current_losses = 0

        return max_wins, max_losses

    def _calculate_sharpe(
        self,
        pnls: List[float],
        initial_capital: float,
        periods_per_year: int = 252
    ) -> float:
        """Calculate Sharpe ratio"""
        if len(pnls) < 2:
            return 0.0

        returns = np.array(pnls) / initial_capital
        mean_return = np.mean(returns)
        std_return = np.std(returns)

        if std_return == 0:
            return 0.0

        # Annualized Sharpe
        excess_return = mean_return - (self.risk_free_rate / periods_per_year)
        sharpe = (excess_return / std_return) * np.sqrt(periods_per_year)

        return sharpe

    def _calculate_sortino(
        self,
        pnls: List[float],
        initial_capital: float,
        periods_per_year: int = 252
    ) -> float:
        """Calculate Sortino ratio (only downside deviation)"""
        if len(pnls) < 2:
            return 0.0

        returns = np.array(pnls) / initial_capital
        mean_return = np.mean(returns)

        # Downside deviation (only negative returns)
        negative_returns = returns[returns < 0]
        if len(negative_returns) == 0:
            return float('inf') if mean_return > 0 else 0.0

        downside_std = np.std(negative_returns)
        if downside_std == 0:
            return 0.0

        excess_return = mean_return - (self.risk_free_rate / periods_per_year)
        sortino = (excess_return / downside_std) * np.sqrt(periods_per_year)

        return sortino

    def _calculate_calmar(
        self,
        pnls: List[float],
        max_drawdown: float,
        initial_capital: float
    ) -> float:
        """Calculate Calmar ratio (return / max drawdown)"""
        if max_drawdown == 0:
            return 0.0

        total_return = sum(pnls) / initial_capital
        calmar = total_return / (max_drawdown / initial_capital)

        return calmar


if __name__ == "__main__":
    # Test metrics calculator
    from datetime import datetime, timedelta
    import random

    # Generate sample trades
    trades = []
    base_time = datetime(2024, 1, 1, 9, 0)

    for i in range(100):
        # Random outcome
        result = random.choices(['tp', 'sl'], weights=[0.45, 0.55])[0]

        sl_dist = 5.0
        tp_dist = 10.0

        if result == 'tp':
            pnl = tp_dist
            pnl_r = 2.0
        else:
            pnl = -sl_dist
            pnl_r = -1.0

        entry_time = base_time + timedelta(hours=i * 2)
        exit_time = entry_time + timedelta(minutes=random.randint(5, 60))

        trade = TradeRecord(
            id=i,
            entry_time=entry_time,
            exit_time=exit_time,
            direction='long',
            entry_price=2000.0,
            exit_price=2000.0 + pnl,
            sl_price=2000.0 - sl_dist,
            tp_price=2000.0 + tp_dist,
            sl_distance=sl_dist,
            tp_distance=tp_dist,
            rr_config='rr_2_1',
            result=result,
            pnl=pnl,
            pnl_r=pnl_r,
            duration_minutes=(exit_time - entry_time).seconds / 60,
            horizon='15m',
            amd_phase=random.choice(['accumulation', 'manipulation', 'distribution']),
            volatility_regime=random.choice(['low', 'medium', 'high']),
            confidence=random.uniform(0.5, 0.8),
            prob_tp_first=random.uniform(0.4, 0.7)
        )
        trades.append(trade)

    # Calculate metrics
    calculator = MetricsCalculator()
    metrics = calculator.calculate_metrics(trades, initial_capital=10000)

    # Print summary
    metrics.print_summary()

    # Segmented metrics
    print("\n=== Segmented Metrics ===")
    segments = calculator.segment_metrics(trades, initial_capital=10000)

    print("\nBy AMD Phase:")
    for phase, m in segments['by_amd_phase'].items():
        print(f"  {phase}: WR={m.winrate:.2%}, PF={m.profit_factor:.2f}, N={m.total_trades}")

    print("\nBy Volatility:")
    for regime, m in segments['by_volatility'].items():
        print(f"  {regime}: WR={m.winrate:.2%}, PF={m.profit_factor:.2f}, N={m.total_trades}")
