# source: https://raw.githubusercontent.com/pmatheus/themoneyhack/7a351c4e8f21230b669069a65f9e504a4872ee63/user_data/strategies/rl_strategy.py
###############################################
# Freqtrade Strategy integrating RL agent
###############################################

# IMPORTANT: Before using this strategy on live exchanges, make sure you have thoroughly backtested it,
# and that you have a well-trained RL agent with proper risk management. 

from freqtrade.strategy import IStrategy
import numpy as np
import pandas as pd
import talib.abstract as ta
import torch

# It is assumed that the RL agent and network classes were defined in main.py.
# For a production system, consider refactoring these classes into a separate module that can be imported here.

# For this demonstration, we import the DiscreteDDQNAgent from main.py. Adjust the import as necessary.
try:
    from main import DiscreteDDQNAgent
except ImportError:
    raise ImportError('Could not import DiscreteDDQNAgent from main.py. Please ensure main.py is in your PYTHONPATH.')


class Github_pmatheus_themoneyhack__rl_strategy__20260604_012955(IStrategy):
    # Basic Freqtrade strategy settings
    timeframe = '1h'
    minimal_roi = {"0": 0.03}
    stoploss = -0.10
    trailing_stop = False
    startup_candle_count = 32  # Must be >= lookback

    # You may adjust these parameters to match your training hyperparameters
    lookback = 32  # Example: ENV_LOOKBACK used during agent training
    # Define state and action dimensions. Adjust state_dim to match the features used in training.
    state_dim = 28  # e.g., 2 (dummy weights) + 26 technical features
    action_dim = 4  # Actions: 0=Open Long, 1=Open Short, 2=Close Long, 3=Close Short

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        # Initialize the agent
        self.agent = DiscreteDDQNAgent(
            state_dim=self.state_dim,
            action_dim=self.action_dim,
            lookback=self.lookback,
            device=self.device
        )
        # Load pretrained weights. Replace 'pretrained_model.pth' with the actual path to your model file.
        try:
            self.agent.online_net.load_state_dict(torch.load("pretrained_model.pth", map_location=self.device))
        except Exception as e:
            raise Exception(f"Error loading pretrained model: {e}")
        self.agent.online_net.eval()

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Compute technical indicators. Here we calculate a subset as an example.
        In practice, you should compute all the indicators used during training.
        """
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        # MACD
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']
        # You can add more indicators as needed
        # Fill missing values
        dataframe.fillna(method='ffill', inplace=True)
        return dataframe

    def get_state(self, dataframe: pd.DataFrame) -> torch.Tensor:
        """
        Convert the last `lookback` candles in the dataframe into a state tensor for the agent.
        For each candle, we create a feature vector that starts with two dummy values (for weight_long and weight_short)
        followed by selected features. Here we use open, high, low, close, volume, rsi, macd, macdsignal, macdhist.
        If the number of features is less than state_dim, we pad with zeros.
        """
        subset = dataframe.tail(self.lookback)
        state_list = []
        for idx, row in subset.iterrows():
            # Starting with dummy weights (could be integrated with current position info if available)
            features = [0.0, 0.0, 
                        row['open'], row['high'], row['low'], row['close'], row['volume'],
                        row['rsi'] if not np.isnan(row['rsi']) else 0.0,
                        row['macd'] if not np.isnan(row['macd']) else 0.0,
                        row['macdsignal'] if not np.isnan(row['macdsignal']) else 0.0,
                        row['macdhist'] if not np.isnan(row['macdhist']) else 0.0]
            # Pad with zeros if needed
            while len(features) < self.state_dim:
                features.append(0.0)
            state_list.append(features)
        # Convert to numpy array and then to torch tensor of shape (1, lookback, state_dim)
        state_array = np.array(state_list, dtype=np.float32)
        state_tensor = torch.tensor(state_array).unsqueeze(0).to(self.device)
        return state_tensor

    def populate_buy_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Determine buy signals using the RL agent. 
        Here we interpret the agent's action: if action==0 (Open Long), we signal a buy.
        """
        if len(dataframe) < self.startup_candle_count:
            dataframe['buy'] = 0
            return dataframe

        state = self.get_state(dataframe)
        with torch.no_grad():
            q_values = self.agent.online_net(state)
            action = torch.argmax(q_values, dim=1).item()

        # For this example, we consider action 0 as signal to open a long position
        dataframe['buy'] = 0
        if action == 0:
            dataframe.at[dataframe.index[-1], 'buy'] = 1
        return dataframe

    def populate_sell_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Determine sell signals using the RL agent. 
        Here we interpret the agent's action: if action==2 (Close Long), we signal a sell.
        """
        if len(dataframe) < self.startup_candle_count:
            dataframe['sell'] = 0
            return dataframe

        state = self.get_state(dataframe)
        with torch.no_grad():
            q_values = self.agent.online_net(state)
            action = torch.argmax(q_values, dim=1).item()

        dataframe['sell'] = 0
        if action == 2:
            dataframe.at[dataframe.index[-1], 'sell'] = 1
        return dataframe

# End of Github_pmatheus_themoneyhack__rl_strategy__20260604_012955

###############################################
# Disclaimer:
# This is a sample integration of a reinforcement learning trading agent with Freqtrade.
# Trading real money carries significant risk. Thoroughly backtest and paper trade
# before using on live exchanges, and make any necessary adjustments to risk management.
############################################### 