# source: https://raw.githubusercontent.com/rkendel1/freq/ea7fa938e8b1a2772d80f75b948412531ba93d1f/freqtrade/ui/backtest_connector.py
"""
Backtesting Connector - Integrates automated exploit with freqtrade backtesting.

This connector allows the AutomatedExploit to be tested using real historical
data through the existing freqtrade backtesting infrastructure.

Usage:
    python -m freqtrade backtesting --strategy Github_rkendel1_freq__backtest_connector__20260109_210603 --config config.json
"""

import logging
from datetime import datetime
from typing import Optional

import pandas as pd
from pandas import DataFrame

from freqtrade.strategy import IStrategy, informative
from freqtrade.core.actions import Action, ActionType, Side
from freqtrade.exploits.exploit_module import ExecutionState
from freqtrade.ui.automated_exploit import AutomatedExploit


logger = logging.getLogger(__name__)


class Github_rkendel1_freq__backtest_connector__20260109_210603(IStrategy):
    """
    Strategy wrapper that uses the AutomatedExploit for backtesting.
    
    This allows testing the automated exploit module with real historical
    data through freqtrade's backtesting infrastructure.
    """
    
    # Strategy configuration
    minimal_roi = {
        "0": 0.05  # 5% minimum ROI (overridden by exploit logic)
    }
    
    stoploss = -0.03  # 3% stop loss (overridden by exploit logic)
    
    # Trailing stop configuration
    trailing_stop = False
    
    # Optimal timeframe for the strategy
    timeframe = '5m'
    
    # Run "populate_indicators()" only for new candle
    process_only_new_candles = True
    
    # Use exit signal
    use_exit_signal = True
    exit_profit_only = False
    exit_profit_offset = 0.0
    
    # Number of candles the strategy requires before producing valid signals
    startup_candle_count: int = 20
    
    # Position sizing
    position_adjustment_enable = False
    
    def __init__(self, config: dict) -> None:
        """Initialize strategy with automated exploit."""
        super().__init__(config)
        
        # Initialize the automated exploit
        self.exploit = AutomatedExploit(config)
        
        # Track price history per pair
        self.price_history: dict[str, list[float]] = {}
        
        logger.info("Github_rkendel1_freq__backtest_connector__20260109_210603 initialized with AutomatedExploit")
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate indicators for the strategy.
        
        The automated exploit uses simple moving averages, so we calculate those here.
        
        Args:
            dataframe: Raw OHLCV data
            metadata: Pair metadata
            
        Returns:
            DataFrame with indicators added
        """
        pair = metadata['pair']
        
        # Calculate moving averages for the exploit's analysis
        dataframe['fast_ma'] = dataframe['close'].rolling(window=5).mean()
        dataframe['slow_ma'] = dataframe['close'].rolling(window=10).mean()
        
        # Calculate momentum
        dataframe['momentum'] = dataframe['close'].pct_change(periods=5)
        
        # Store price history for the exploit
        if pair not in self.price_history:
            self.price_history[pair] = []
        
        # Keep last 20 prices for analysis
        self.price_history[pair] = dataframe['close'].tail(20).tolist()
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate entry signals using the automated exploit.
        
        Args:
            dataframe: DataFrame with indicators
            metadata: Pair metadata
            
        Returns:
            DataFrame with entry signals
        """
        pair = metadata['pair']
        
        # Initialize entry columns
        dataframe['enter_long'] = 0
        dataframe['enter_short'] = 0
        dataframe['enter_tag'] = ''
        
        # Iterate through candles
        for idx in range(len(dataframe)):
            if idx < self.startup_candle_count:
                continue
            
            row = dataframe.iloc[idx]
            
            # Update exploit's price history
            if pair in self.price_history:
                # Keep rolling window of 20 prices
                self.exploit.price_history.clear()
                for price in self.price_history[pair][-20:]:
                    self.exploit.price_history.append(price)
            
            # Create execution state
            exec_state = ExecutionState(
                symbol=pair,
                available_capital=10000.0,  # Placeholder, actual capital managed by freqtrade
                deployed_capital=0.0,
                open_positions=[],
                recent_trades=[],
                current_price=float(row['close']),
                timestamp=int(row['date'].timestamp() * 1000) if hasattr(row['date'], 'timestamp') else 0,
            )
            
            # Get actions from exploit
            actions = self.exploit.evaluate(exec_state)
            
            # Process actions
            for action in actions:
                if action.type == ActionType.OPEN:
                    if action.side == Side.LONG:
                        dataframe.loc[idx, 'enter_long'] = 1
                        dataframe.loc[idx, 'enter_tag'] = action.reason[:50]  # Truncate reason
                    elif action.side == Side.SHORT:
                        dataframe.loc[idx, 'enter_short'] = 1
                        dataframe.loc[idx, 'enter_tag'] = action.reason[:50]
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate exit signals using the automated exploit.
        
        Args:
            dataframe: DataFrame with indicators
            metadata: Pair metadata
            
        Returns:
            DataFrame with exit signals
        """
        pair = metadata['pair']
        
        # Initialize exit columns
        dataframe['exit_long'] = 0
        dataframe['exit_short'] = 0
        dataframe['exit_tag'] = ''
        
        # The exploit's exit logic is triggered when we have open positions
        # In backtesting, this is handled by custom_exit() which is called
        # for each open trade
        
        return dataframe
    
    def custom_exit(
        self,
        pair: str,
        trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs
    ) -> Optional[str]:
        """
        Custom exit logic using the automated exploit.
        
        This is called for each open trade to determine if it should be closed.
        
        Args:
            pair: Trading pair
            trade: Trade object
            current_time: Current timestamp
            current_rate: Current price
            current_profit: Current profit percentage
            **kwargs: Additional parameters
            
        Returns:
            Exit reason if trade should be closed, None otherwise
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # Update exploit's price history
        if pair in self.price_history:
            self.exploit.price_history.clear()
            for price in self.price_history[pair][-20:]:
                self.exploit.price_history.append(price)
        
        # Add simulated position to exploit for exit analysis
        entry_price = trade.open_rate
        position_side = Side.LONG if trade.is_long else Side.SHORT
        
        # Temporarily add position
        self.exploit.add_simulated_position(
            symbol=pair,
            side=position_side,
            entry_price=entry_price,
            size=trade.stake_amount,
        )
        
        # Create execution state
        exec_state = ExecutionState(
            symbol=pair,
            available_capital=10000.0,
            deployed_capital=trade.stake_amount,
            open_positions=[],
            recent_trades=[],
            current_price=current_rate,
            timestamp=int(current_time.timestamp() * 1000),
        )
        
        # Get actions from exploit
        actions = self.exploit.evaluate(exec_state)
        
        # Clear the temporary position
        self.exploit.simulated_positions.clear()
        
        # Check if exploit wants to close
        for action in actions:
            if action.type == ActionType.CLOSE:
                return action.reason[:50]  # Return exit reason
        
        return None
    
    def custom_stake_amount(
        self,
        pair: str,
        current_time: datetime,
        current_rate: float,
        proposed_stake: float,
        min_stake: Optional[float],
        max_stake: float,
        leverage: float,
        entry_tag: Optional[str],
        side: str,
        **kwargs
    ) -> float:
        """
        Customize stake amount based on exploit's position sizing.
        
        The exploit uses 15% of capital per position.
        
        Args:
            pair: Trading pair
            current_time: Current timestamp
            current_rate: Current price
            proposed_stake: Proposed stake from freqtrade
            min_stake: Minimum stake
            max_stake: Maximum stake
            leverage: Leverage
            entry_tag: Entry tag
            side: Trade side (long/short)
            **kwargs: Additional parameters
            
        Returns:
            Stake amount to use
        """
        # Use 15% of available capital (as configured in exploit)
        wallet_balance = self.wallets.get_total_stake_amount()
        stake = wallet_balance * self.exploit.position_size
        
        # Ensure within bounds
        if min_stake and stake < min_stake:
            stake = min_stake
        if stake > max_stake:
            stake = max_stake
        
        return stake


def run_automated_backtest(
    config_path: str,
    data_dir: str = "user_data/data",
    timerange: str = "",
    pairs: list[str] = None,
):
    """
    Helper function to run backtest with automated strategy.
    
    Args:
        config_path: Path to config file
        data_dir: Directory with historical data
        timerange: Time range for backtest (e.g., "20230101-20230131")
        pairs: List of pairs to test (e.g., ["BTC/USDT", "ETH/USDT"])
    
    Example:
        run_automated_backtest(
            config_path="config.json",
            timerange="20230101-20230131",
            pairs=["BTC/USDT"]
        )
    """
    from freqtrade.configuration import Configuration
    from freqtrade.optimize.backtesting import Backtesting
    
    # Load configuration
    config = Configuration.from_files([config_path])
    
    # Override with automated strategy
    config['strategy'] = 'Github_rkendel1_freq__backtest_connector__20260109_210603'
    
    # Set data directory
    if data_dir:
        config['datadir'] = data_dir
    
    # Set timerange
    if timerange:
        config['timerange'] = timerange
    
    # Set pairs
    if pairs:
        config['pairs'] = pairs
    
    # Initialize backtesting
    backtesting = Backtesting(config)
    
    # Run backtest
    logger.info("Starting automated backtest with real historical data")
    backtesting.start()
    
    return backtesting
