# source: https://raw.githubusercontent.com/Aidenkopec/crypto-bot-trading/d07ebc99e97d393d0453e6a45486cdcf61d8905c/user_data/strategies/DualAssetSelectionStrategy.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np
import pandas as pd
from pandas import DataFrame
from datetime import datetime
from typing import Optional, Union, List
from functools import reduce

from freqtrade.strategy import (
    IStrategy,
    Trade,
    Order,
    PairLocks,
    informative,
    merge_informative_pair,
    stoploss_from_open,
    stoploss_from_absolute,
)

# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib

# Import our custom pair selector
from user_data.strategies.pair_selector import PairSelector, calculate_setup_indicators


class Github_Aidenkopec_crypto_bot_trading__DualAssetSelectionStrategy__20250730_214930(IStrategy):
    """
    Master Dual-Asset Selection Strategy for Kraken BTC/USD and ETH/USD trading.

    Implements the research-defined approach:
    - Scans both BTC/USD and ETH/USD daily for best setups
    - Uses 7+ out of 10 scoring quality gate
    - Targets 60% BTC trades (swing), 40% ETH trades (momentum)
    - Single $1000 position focus with 4-6% minimum profit targets

    Based on Strategy Analysis Report and Kraken Trading Pairs Analysis.
    """

    # Strategy interface version
    INTERFACE_VERSION = 3

    # Dynamic timeframe (adjusted based on selected pair)
    timeframe = "1h"  # Default, will be overridden by pair selection

    # Can this strategy go short?
    can_short = False

    # Dynamic ROI (adjusted based on selected pair/strategy)
    minimal_roi = {
        "0": 0.08,  # 8% target (adjusted per pair)
        "60": 0.06,  # 6% after 1 hour
        "120": 0.04,  # 4% after 2 hours (minimum for fees)
        "240": 0.01,  # 1% after 4 hours (safety exit)
    }

    # Dynamic stoploss (adjusted based on pair volatility)
    stoploss = -0.03  # 3% default (2.5% for BTC, 3% for ETH)

    # Trailing stoploss
    trailing_stop = False

    # Dual-asset selection parameters
    startup_candle_count: int = 50  # Enough for all indicators

    # Pair selection settings
    quality_gate_threshold = 7.0  # 7+ out of 10 to trade
    btc_allocation_target = 0.60  # 60% BTC trades
    eth_allocation_target = 0.40  # 40% ETH trades

    # Initialize pair selector
    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.pair_selector = PairSelector()
        self.latest_selection_result = None

    def informative_pairs(self):
        """
        Define informative pairs for both BTC and ETH analysis
        """
        return [
            ("BTC/USD", "4h"),  # BTC trend context
            ("ETH/USD", "4h"),  # ETH trend context
        ]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Run all indicator calculations for the asset.
        """
        pair = metadata["pair"]
        asset_type = "BTC" if "BTC" in pair else "ETH"

        # Use the centralized indicator calculation function
        dataframe = calculate_setup_indicators(dataframe, asset_type=asset_type)

        # Merge informative data (for 4h trend context)
        if self.dp:
            informative_btc = self.dp.get_pair_dataframe("BTC/USD", "4h")
            informative_eth = self.dp.get_pair_dataframe("ETH/USD", "4h")

            if informative_btc is not None and not informative_btc.empty:
                informative_btc = self.populate_indicators_btc_4h(informative_btc, {})
                dataframe = merge_informative_pair(
                    dataframe, informative_btc, self.timeframe, "4h", ffill=True
                )

            if informative_eth is not None and not informative_eth.empty:
                informative_eth = self.populate_indicators_eth_4h(informative_eth, {})
                dataframe = merge_informative_pair(
                    dataframe, informative_eth, self.timeframe, "4h", ffill=True
                )

        return dataframe

    @informative("4h", "BTC/USD")
    def populate_indicators_btc_4h(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        """4h BTC trend context"""
        dataframe["ema_trend_4h"] = ta.EMA(dataframe, timeperiod=20) > ta.EMA(
            dataframe, timeperiod=50
        )
        dataframe["rsi_4h"] = ta.RSI(dataframe, timeperiod=14)
        macd_4h = ta.MACD(dataframe)
        dataframe["trend_strength_4h"] = macd_4h["macd"] > macd_4h["macdsignal"]
        return dataframe

    @informative("4h", "ETH/USD")
    def populate_indicators_eth_4h(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        """4h ETH trend context"""
        dataframe["ema_trend_4h"] = ta.EMA(dataframe, timeperiod=20) > ta.EMA(
            dataframe, timeperiod=50
        )
        dataframe["rsi_4h"] = ta.RSI(dataframe, timeperiod=14)
        macd_4h = ta.MACD(dataframe)
        dataframe["trend_strength_4h"] = macd_4h["macd"] > macd_4h["macdsignal"]
        dataframe["volume_trend_4h"] = dataframe["volume"] > ta.SMA(
            dataframe["volume"], timeperiod=10
        )
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Perform dual-asset selection and generate entry signal for the best pair.
        """
        # Initialize entry column
        dataframe["enter_long"] = 0

        # The dataprovider is required for multi-asset analysis
        if not self.dp:
            return dataframe

        # We only need to run the selection logic on one of the pairs.
        # Arbitrarily choose BTC to be the "main" one to avoid duplicate processing.
        if metadata["pair"] != "BTC/USD":
            return dataframe

        # Get the latest data for both assets
        btc_df = self.dp.get_pair_dataframe("BTC/USD", self.timeframe)
        eth_df = self.dp.get_pair_dataframe("ETH/USD", self.timeframe)

        # Ensure we have enough data to analyze
        if (
            btc_df.empty
            or len(btc_df) < self.startup_candle_count
            or eth_df.empty
            or len(eth_df) < self.startup_candle_count
        ):
            return dataframe

        # Run the pair selection logic
        selection_result = self.pair_selector.select_best_pair(btc_df, eth_df)
        self.latest_selection_result = selection_result  # Cache for other methods

        # If a pair was selected, find its corresponding dataframe and set the signal
        if selection_result and selection_result["selected_pair"]:
            selected_pair_name = selection_result["selected_pair"]["pair"]

            if selected_pair_name == "BTC/USD":
                # Set enter_long on the last candle of the BTC dataframe
                btc_df.loc[btc_df.index[-1], "enter_long"] = 1

            elif selected_pair_name == "ETH/USD":
                # Set enter_long on the last candle of the ETH dataframe
                # Note: We are modifying a different dataframe here. Freqtrade will process it.
                eth_df.loc[eth_df.index[-1], "enter_long"] = 1

        # Return the original dataframe. If BTC was selected, it will have the entry signal.
        # If ETH was selected, its dataframe was modified in place.
        return btc_df if metadata["pair"] == "BTC/USD" else eth_df

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Dynamic exit logic based on selected strategy type
        """
        pair = metadata["pair"]

        # Initialize exit signal
        dataframe["exit_long"] = 0

        if "BTC" in pair:
            dataframe = self._populate_btc_exit(dataframe)
        elif "ETH" in pair:
            dataframe = self._populate_eth_exit(dataframe)

        return dataframe

    def _populate_btc_exit(self, dataframe: DataFrame) -> DataFrame:
        """BTC swing trading exit conditions"""
        conditions = []

        # RSI overbought
        conditions.append(dataframe["rsi"] > 65)

        # MACD bearish
        conditions.append(
            (dataframe["macd"] < dataframe["macdsignal"]) | (dataframe["macdhist"] < 0)
        )

        # Volume declining
        conditions.append(dataframe["volume_ratio"] < 0.8)

        # Bollinger Band upper region
        conditions.append(dataframe["bb_percent"] > 0.8)

        if conditions:
            dataframe.loc[reduce(lambda x, y: x | y, conditions), "exit_long"] = 1

        return dataframe

    def _populate_eth_exit(self, dataframe: DataFrame) -> DataFrame:
        """ETH momentum trading exit conditions"""
        conditions = []

        # RSI extremely overbought
        conditions.append(dataframe["rsi"] > 75)

        # MACD momentum declining
        conditions.append(
            (dataframe["macd"] < dataframe["macdsignal"])
            | (dataframe["macdhist"] < dataframe["macdhist"].shift(1))
        )

        # Volume declining
        conditions.append(dataframe["volume_ratio"] < 0.7)

        # Overextended above BB
        conditions.append(dataframe["bb_percent"] > 1.1)

        # Momentum weakening
        conditions.append(~dataframe["momentum_positive"])

        if conditions:
            dataframe.loc[reduce(lambda x, y: x | y, conditions), "exit_long"] = 1

        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: "Trade",
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        """
        Dynamic stoploss based on pair volatility
        """
        if "BTC" in pair:
            return -0.025  # 2.5% for BTC (lower volatility)
        elif "ETH" in pair:
            return -0.030  # 3.0% for ETH (higher volatility)
        else:
            return self.stoploss

    def custom_sell(
        self,
        pair: str,
        trade: "Trade",
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> Optional[Union[str, bool]]:
        """
        Dynamic profit-taking based on asset and strategy type
        """
        if "BTC" in pair:
            # BTC swing trading targets
            if current_profit >= 0.08:
                return "btc_profit_8pct"
            elif current_profit >= 0.06:
                return "btc_profit_6pct"
            elif current_profit >= 0.04:
                return None  # Let exit signals handle

        elif "ETH" in pair:
            # ETH momentum trading targets
            if current_profit >= 0.09:
                return "eth_profit_9pct"
            elif current_profit >= 0.07:
                return "eth_profit_7pct"
            elif current_profit >= 0.05:
                return None  # Let exit signals handle

        return None

    def confirm_trade_entry(
        self,
        pair: str,
        order_type: str,
        amount: float,
        rate: float,
        time_in_force: str,
        current_time: datetime,
        entry_tag: Optional[str],
        side: str,
        **kwargs,
    ) -> bool:
        """
        Confirm the trade and record it for allocation tracking.
        """
        # Record the trade for allocation tracking
        if (
            self.latest_selection_result
            and self.latest_selection_result["selected_pair"]
        ):
            selected_pair_info = self.latest_selection_result["selected_pair"]
            if selected_pair_info["pair"] == pair:
                self.pair_selector.record_trade(
                    pair=pair, score=selected_pair_info["total_score"]
                )
                print(f"Trade confirmed for {pair}. Recorded for allocation tracking.")
                return True

        print(f"Trade confirmation failed for {pair}. No valid selection found.")
        return False

    def leverage(
        self,
        pair: str,
        current_time: datetime,
        current_rate: float,
        proposed_leverage: float,
        max_leverage: float,
        entry_tag: Optional[str],
        side: str,
        **kwargs,
    ) -> float:
        """
        No leverage for Kraken spot trading
        """
        return 1.0
