# src/backtest/strategy.py
"""
Clase base para estrategias de trading
"""
from abc import ABC, abstractmethod
from enum import Enum
import pandas as pd
from typing import Optional, Dict, Any

class Signal(Enum):
    """Señales de Trading"""
    BUY = 1
    SELL = -1
    HOLD = 0

class Strategy(ABC):
    """
    Clase base abstracta para todas las estrategias
    
    Para crear una estrategia:
    1. Heredar de Strategy
    2. Implementar init_indicators()
    3. Implementar generate_signal()
    """

    def __init__(self, name: str, params: Optional[Dict[str, Any]] = 0):
        """
        Inicializa la estrategia
        
        Args:
            name: Nombre de la estrategia
            params: Parámetros configurables de la estrategia
        """
        self.name = name
        self.params = params or {}
        self.data: Optional[pd.DataFrame] = None
        self.current_position = 0   # 1 = long, -1 = short, 0 = sin posición

    @abstractmethod
    def init_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
        """
        Inicializa y calcula indicadores técnicos sobre los datos
        
        Args:
            data: DataFrame con datos OHLCV
            
        Returns:
            DataFrame con indicadores añadidos
        """
        pass

    @abstractmethod
    def generate_signal(self, idx: int) -> Signal:
        """
        Genera señal de trading para un punto específico en el tiempo
        
        Args:
            idx: Índice del DataFrame (posición temporal)
            
        Returns:
            Signal (BUY, SELL, HOLD)
        """
        pass

    def set_data(self, data: pd.DataFrame):
        """
        Establece los datos y calcula indicadores
        """
        self.data = self.init_indicators(data.copy())

    def get_indicator_value(self, idx: int, indicator_name: str) -> Any:
        """
        Obtiene el valor de un indicador en un punto temporal específico
        """
        if self.data is None:
            raise ValueError("Data no ha sido establecida. Llama a set_data() primero.")
        
        if indicator_name not in self.data.columns:
            raise ValueError(f"Indicador '{indicator_name}' no existe en los datos.")
        
        return self.data.iloc[idx][indicator_name]

    def __repr__(self):
        params_str = ", ".join([f"{k}={v}" for k, v in self.params.items()])
        return f"{self.name}({params_str})"
    
# ============================================================================
# Funciones auxiliares para indicadores técnicos comunes
# ============================================================================

def calculate_sma(data: pd.Series, period: int) -> pd.Series:
    """
    Simple Moving Average
    
    Args:
        data: Serie de precios
        period: Periodo de la media
        
    Returns:
        Serie con SMA
    """
    return data.rolling(window=period).mean()

def calculate_ema(data: pd.Series, period: int) -> pd.Series:
    """
    Exponential Moving Average
    
    Args:
        data: Serie de precios
        period: Periodo de la media
        
    Returns:
        Serie con EMA
    """
    return data.ewm(span=period, adjust=False).mean()

def calculate_rsi(data: pd.Series, period: int = 14) -> pd.Series:
    """
    Relative Strength Index
    
    Args:
        data: Serie de precios
        period: Periodo del RSI
        
    Returns:
        Serie con RSI
    """
    delta = data.diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    
    rs = gain / loss
    rsi = 100 - (100 / (1 + rs))
    return rsi

def calculate_macd(data: pd.Series, fast: int = 12, slow: int = 26, signal: int = 9) -> tuple:
    """
    MACD (Moving Average Convergence Divergence)
    
    Args:
        data: Serie de precios
        fast: Periodo EMA rápida
        slow: Periodo EMA lenta
        signal: Periodo de la línea de señal
        
    Returns:
        Tupla (MACD, Signal Line, Histogram)
    """
    ema_fast = calculate_ema(data, fast)
    ema_slow = calculate_ema(data, slow)
    
    macd_line = ema_fast - ema_slow
    signal_line = calculate_ema(macd_line, signal)
    histogram = macd_line - signal_line
    
    return macd_line, signal_line, histogram

def calculate_bollinger_bands(data: pd.Series, period: int = 20, std_dev: float = 2) -> tuple:
    """
    Bollinger Bands
    
    Args:
        data: Serie de precios
        period: Periodo de la media móvil
        std_dev: Número de desviaciones estándar
        
    Returns:
        Tupla (Upper Band, Middle Band, Lower Band)
    """
    middle = calculate_sma(data, period)
    std = data.rolling(window=period).std()
    
    upper = middle + (std * std_dev)
    lower = middle - (std * std_dev)
    
    return upper, middle, lower

def calculate_atr(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14) -> pd.Series:
    """
    Average True Range (volatilidad)
    
    Args:
        high: Serie de precios máximos
        low: Serie de precios mínimos
        close: Serie de precios de cierre
        period: Periodo del ATR
        
    Returns:
        Serie con ATR
    """
    tr1 = high - low
    tr2 = abs(high - close.shift())
    tr3 = abs(low - close.shift())
    
    tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
    atr = tr.rolling(window=period).mean()
    
    return atr