# source: https://raw.githubusercontent.com/A1-Systems-Integrators-LLC/A1SI-AITP/7b0133b13f687823d7140a75a9dd8160ecef49a0/freqtrade/user_data/strategies/SentimentEventTrader.py
"""Github_A1_Systems_Integrators_LLC_A1SI_AITP__SentimentEventTrader__20260317_160005 — News-driven extreme sentiment strategy.

Trades extreme sentiment spikes detected by the NLP pipeline (FinBERT/VADER).
Long on sentiment > 0.7, short on sentiment < -0.7.
Gate: RSI must not already be extended in the direction of the trade.
"""

import logging

import talib.abstract as ta
from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy
from pandas import DataFrame

logger = logging.getLogger(__name__)

try:
    from freqtrade.user_data.strategies._conviction_helpers import (
        check_conviction,
        check_exit_advice,
        get_position_modifier,
        get_regime_stop_multiplier,
        record_entry_regime,
        refresh_signals,
    )

    HAS_CONVICTION = True
except ImportError:
    HAS_CONVICTION = False

# Sentiment signal access
try:
    import importlib

    HAS_SENTIMENT = (
        importlib.util.find_spec("common.sentiment.scorer") is not None
        and importlib.util.find_spec("common.sentiment.signal") is not None
    )
except Exception:
    HAS_SENTIMENT = False


class Github_A1_Systems_Integrators_LLC_A1SI_AITP__SentimentEventTrader__20260317_160005(IStrategy):
    """Trades extreme sentiment events from NLP pipeline."""

    INTERFACE_VERSION = 3
    timeframe = "1h"
    can_short = True
    startup_candle_count = 50

    stoploss = -0.05
    use_custom_stoploss = True

    minimal_roi = {
        "0": 0.04,
        "60": 0.025,
        "240": 0.015,
        "480": 0.005,
    }

    # Hyperopt parameters
    buy_sentiment_threshold = DecimalParameter(0.5, 0.9, default=0.7, space="buy")
    sell_sentiment_threshold = DecimalParameter(-0.9, -0.5, default=-0.7, space="sell")
    buy_rsi_max = IntParameter(55, 75, default=65, space="buy")
    sell_rsi_min = IntParameter(25, 45, default=35, space="sell")
    atr_multiplier = DecimalParameter(1.0, 3.0, default=2.0, space="buy")

    # Cached sentiment scores per pair
    _sentiment_scores: dict[str, float] = {}

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
        dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20)
        dataframe["volume_sma"] = ta.SMA(dataframe["volume"], timeperiod=20)
        dataframe["volume_ratio"] = dataframe["volume"] / dataframe["volume_sma"]

        # Add sentiment score column from cached values
        pair = metadata.get("pair", "")
        sentiment = self._sentiment_scores.get(pair, 0.0)
        dataframe["sentiment_score"] = sentiment

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Long: extreme positive sentiment + RSI not overbought
        dataframe.loc[
            (
                (dataframe["sentiment_score"] > self.buy_sentiment_threshold.value)
                & (dataframe["rsi"] < self.buy_rsi_max.value)
                & (dataframe["rsi"] > 20)
                & (dataframe["volume"] > 0)
            ),
            "enter_long",
        ] = 1

        # Short: extreme negative sentiment + RSI not oversold
        dataframe.loc[
            (
                (dataframe["sentiment_score"] < self.sell_sentiment_threshold.value)
                & (dataframe["rsi"] > self.sell_rsi_min.value)
                & (dataframe["rsi"] < 80)
                & (dataframe["volume"] > 0)
            ),
            "enter_short",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exit long: sentiment reverses or RSI overbought
        dataframe.loc[
            (
                (dataframe["sentiment_score"] < 0)
                | (dataframe["rsi"] > 75)
            ),
            "exit_long",
        ] = 1

        # Exit short: sentiment reverses or RSI oversold
        dataframe.loc[
            (
                (dataframe["sentiment_score"] > 0)
                | (dataframe["rsi"] < 25)
            ),
            "exit_short",
        ] = 1

        return dataframe

    def bot_loop_start(self, **kwargs) -> None:
        if HAS_CONVICTION:
            refresh_signals(self)

        # Refresh sentiment scores for active pairs
        if HAS_SENTIMENT and hasattr(self, "dp") and self.dp is not None:
            try:
                pairs = self.dp.current_whitelist()
                for pair in pairs[:10]:
                    signal = self._get_cached_signal(pair)
                    if signal is not None:
                        self._sentiment_scores[pair] = signal.get("sentiment_score", 0.0)
            except Exception as e:
                logger.warning("Sentiment refresh failed: %s", e)

    def _get_cached_signal(self, pair: str) -> dict | None:
        """Get cached conviction signal with sentiment data."""
        if not HAS_CONVICTION:
            return None
        signals = getattr(self, "_signals", {})
        return signals.get(pair)

    def custom_leverage(self, pair: str, current_time, current_rate, proposed_leverage,
                        max_leverage, entry_tag, side, **kwargs) -> float:
        return min(3.0, max_leverage)

    def confirm_trade_entry(self, pair, order_type, amount, rate, time_in_force,
                            current_time, entry_tag, side, **kwargs) -> bool:
        if HAS_CONVICTION:
            if not check_conviction(self, pair):
                return False
            record_entry_regime(self, pair)
        return True

    def custom_stake_amount(self, current_time, current_rate, proposed_stake,
                            min_stake, max_stake, leverage, entry_tag, side,
                            **kwargs) -> float:
        if HAS_CONVICTION:
            modifier = get_position_modifier(self, kwargs.get("pair", ""))
            return proposed_stake * modifier
        return proposed_stake

    def custom_stoploss(self, pair, trade, current_time, current_rate,
                        current_profit, after_fill, **kwargs) -> float:
        atr = self.dp.get_pair_dataframe(pair, self.timeframe)["atr"].iloc[-1]
        regime_mult = 1.0
        if HAS_CONVICTION:
            regime_mult = get_regime_stop_multiplier(self, pair)

        atr_stop = (atr / current_rate) * self.atr_multiplier.value * regime_mult
        stop = -atr_stop

        if current_profit > 0.02:
            stop = max(stop, -0.025)
        if current_profit > 0.04:
            stop = max(stop, -0.015)

        return stop

    def custom_exit(self, pair, trade, current_time, current_rate,
                    current_profit, **kwargs):
        if HAS_CONVICTION:
            advice = check_exit_advice(self, pair, trade, current_time, current_profit)
            if advice:
                return advice
        return None
