# ----------- 1. IMPORTAR LIBRERÍAS Y CONFIGURACIÓN ----------- 
2025-01-28
Category: None

# ----------- 1. IMPORTAR LIBRERÍAS Y CONFIGURACIÓN -----------
import backtrader as bt
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import TimeSeriesSplit, GridSearchCV
from sklearn.metrics import classification_report
import joblib
import logging

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# ----------- 2. CLASE DE PREPARACIÓN DE DATOS -----------
class ForexDataPreprocessor:
    def __init__(self, data_path):
        self.data_path = data_path
        self.data = None
    
    def load_and_preprocess(self):
        """Carga datos y calcula características técnicas."""
        self.data = pd.read_csv(self.data_path, parse_dates= , index_col='time')
        self._add_technical_features()
        self._create_labels()
        return self.data.dropna()

    def _add_technical_features(self):
        # Indicadores
        self.data  = np.log(self.data  / self.data .shift(1))
        self.data  = self._calculate_rsi(period=14)
        self.data  = self.data .ewm(span=50).mean()
        self.data  = self.data .ewm(span=200).mean()
        self.data  = self.data .ewm(span=12).mean() - self.data .ewm(span=26).mean()
        self.data  = self._calculate_atr(period=14)
        self.data  = self.data  - self.data 
        self.data  = self.data .rolling(20).mean()

    def _create_labels(self):
        # Crear variable objetivo con filtro de volatilidad
        future_returns = self.data .shift(-3)
        self.data  = np.where(
            (future_returns > 0.001) & (self.data  > 0.0005), 1, 
            np.where((future_returns < -0.001) & (self.data  > 0.0005), -1, 0)
        )

    def _calculate_rsi(self, period):
        delta = self.data .diff()
        gain = delta.clip(lower=0)
        loss = -delta.clip(upper=0)
        avg_gain = gain.ewm(alpha=1/period).mean()
        avg_loss = loss.ewm(alpha=1/period).mean()
        rs = avg_gain / avg_loss
        return 100 - (100 / (1 + rs))

    def _calculate_atr(self, period):
        high_low = self.data  - self.data 
        high_close = np.abs(self.data  - self.data .shift())
        low_close = np.abs(self.data  - self.data .shift())
        true_range = pd.concat( , axis=1).max(axis=1)
        return true_range.ewm(span=period).mean()

# ----------- 3. CLASE DE ENTRENAMIENTO DE MODELO -----------
class ForexModelTrainer:
    def __init__(self, features, target):
        self.features = features
        self.target = target
        self.model = None
        self.scaler = None
    
    def train_model(self, data):
        # Pipeline y validación cruzada temporal
        pipeline = Pipeline( )

        param_grid = {
            'model__max_depth':  ,
            'model__min_samples_split':  
        }

        tscv = TimeSeriesSplit(n_splits=5)
        grid_search = GridSearchCV(pipeline, param_grid, cv=tscv, scoring='f1_weighted', n_jobs=-1)
        grid_search.fit(self.features, self.target)

        self.model = grid_search.best_estimator_
        self.scaler = grid_search.named_steps 
        
        logger.info(f"Mejores parámetros: {grid_search.best_params_}")
        logger.info(classification_report(self.target, self.model.predict(self.features)))
        
        # Guardar modelo
        joblib.dump(self.model, 'forex_model.pkl')
        joblib.dump(self.scaler, 'forex_scaler.pkl')
        return self.model, self.scaler

# ----------- 4. ESTRATEGIA PRECISA CON GESTIÓN DE RIESGO -----------
class PrecisionForexStrategy(bt.Strategy):
    params = (
        ('risk_per_trade', 0.015),
        ('max_daily_trades', 3),
        ('model_path', 'forex_model.pkl'),
        ('scaler_path', 'forex_scaler.pkl')
    )

    def __init__(self):
        # Cargar modelo entrenado
        self.model = joblib.load(self.params.model_path)
        self.scaler = joblib.load(self.params.scaler_path)
        
        # Indicadores en tiempo real
        self.ema50 = bt.indicators.EMA(period=50)
        self.ema200 = bt.indicators.EMA(period=200)
        self.rsi = bt.indicators.RSI(period=14)
        self.atr = bt.indicators.ATR(period=14)
        
        # Variables de estado
        self.trades_today = 0
        self.last_trade_date = None

    def next(self):
        current_date = self.data.datetime.date()
        if current_date != self.last_trade_date:
            self.trades_today = 0
            self.last_trade_date = current_date

        if self.trades_today >= self.params.max_daily_trades:
            return

        # Generar features para predicción
        features = np.array( [
             [self.rsi , 
             (self.ema50  - self.ema200 ),
             self.atr ,
             self.data.volume ]
        ])
        features_scaled = self.scaler.transform(features)
        prediction = self.model.predict(features_scaled) 

        # Condiciones de entrada estrictas
        entry_condition_long = (
            prediction == 1 and
            self.ema50  > self.ema200  and
            self.rsi  < 35 and
            self.atr  > 0.0005
        )

        entry_condition_short = (
            prediction == -1 and
            self.ema50  < self.ema200  and
            self.rsi  > 65 and
            self.atr  > 0.0005
        )

        # Ejecutar operación con gestión de riesgo
        if entry_condition_long and not self.position:
            size = self.calculate_position_size(side='long')
            self.buy(size=size)
            self.place_protective_orders(side='long')
            self.trades_today += 1

        elif entry_condition_short and not self.position:
            size = self.calculate_position_size(side='short')
            self.sell(size=size)
            self.place_protective_orders(side='short')
            self.trades_today += 1

    def calculate_position_size(self, side):
        account_value = self.broker.getvalue()
        risk_amount = account_value * self.params.risk_per_trade
        atr_value = self.atr 
        price = self.data.close 
        
        # Calcular tamaño basado en ATR
        risk_per_unit = atr_value * 1.5
        size = (risk_amount / risk_per_unit) / price
        
        return round(size, 2)

    def place_protective_orders(self, side):
        price = self.data.close 
        atr = self.atr 
        
        if side == 'long':
            stop_loss = price - (atr * 1.2)
            take_profit = price + (atr * 3)
        else:
            stop_loss = price + (atr * 1.2)
            take_profit = price - (atr * 3)
        
        # Órdenes OCO (One Cancels Other)
        self.order = self.order_target_value(target=0)
        self.sell(exectype=bt.Order.Stop, price=stop_loss)
        self.sell(exectype=bt.Order.Limit, price=take_profit)

    def notify_trade(self, trade):
        logger.info(f"Operación {'COMPRA' if trade.islong else 'VENTA'} | "
                    f"Resultado: {trade.pnl:.2f} | "
                    f"Duración: {trade.duration} barras")

# ----------- 5. PIPELINE COMPLETO DE BACKTEST -----------
def main():
    # Paso 1: Preparación de datos
    preprocessor = ForexDataPreprocessor('eurusd_h1.csv')
    data = preprocessor.load_and_preprocess()
    
    # Paso 2: Entrenamiento del modelo
    trainer = ForexModelTrainer(
        features=data [ ],
        target=data 
    )
    model, scaler = trainer.train_model(data)
    
    # Paso 3: Configurar Backtrader
    cerebro = bt.Cerebro(stdstats=False)
    
    # Añadir datos
    data_feed = bt.feeds.PandasData(
        dataname=data,
        datetime='time',
        open='open',
        high='high',
        low='low',
        close='close',
        volume='volume'
    )
    cerebro.adddata(data_feed)
    
    # Configurar estrategia
    cerebro.addstrategy(PrecisionForexStrategy)
    
    # Configuración del broker
    cerebro.broker.setcash(100000)
    cerebro.broker.setcommission(commission=0.0002, margin=0.03)  # Típico en Forex
    
    # Añadir analizadores
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.0)
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
    cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
    
    # Ejecutar backtest
    results = cerebro.run()
    
    # Reporte final
    strat = results 
    print(f"Capital Final: {cerebro.broker.getvalue():.2f}")
    print(f"Sharpe Ratio: {strat.analyzers.sharpe.get_analysis() :.2f}")
    print(f"Máximo Drawdown: {strat.analyzers.drawdown.get_analysis() :.2f}%")
    
    # Visualización
    cerebro.plot(style='candlestick', volume=False)

if __name__ == '__main__':
    main()