# source: https://raw.githubusercontent.com/RoboticAutomations/freqtradestrategies/52c4bd40414645f079a980cf4486dc7af4061efb/AIRA%20-%20open%20code%20v2/strategies/orderbook-volume/LLMTankAIHybrid__2.py
# directory_url: https://github.com/RoboticAutomations/freqtradestrategies/blob/main/AIRA - open code v2/strategies/orderbook-volume/
# User: RoboticAutomations
# Repository: freqtradestrategies
# --------------------# pragma pylint: disable=missing-docstring, invalid-name

import json
import time
import logging
import requests
import numpy as np
import talib.abstract as ta

from pandas import DataFrame
from freqtrade.strategy import IStrategy
from freqtrade.persistence import Trade

logger = logging.getLogger(__name__)


class Github_RoboticAutomations_freqtradestrategies__LLMTankAIHybrid__2__20260915_233254(IStrategy):

    # =============================
    # ===== OLLAMA SETTINGS =======
    # =============================
    OLLAMA_URL = "http://localhost:11434/api/generate"
    OLLAMA_MODEL = "tradingagent:latest"
    OLLAMA_TIMEOUT = 25

    # =============================
    # ===== STRATEGY CONFIG =======
    # =============================
    timeframe = "15m"
    startup_candle_count = 200
    process_only_new_candles = True

    can_short = False

    stoploss = -0.15  # hard safety

    exit_profit_only = True
    ignore_roi_if_entry_signal = True

    trailing_stop = False

    decision_confidence_threshold = 0.65
    min_expected_profit = 0.012

    ATR_PROFIT_MULTIPLIER = 1.5
    MAX_DRAWDOWN_EXIT = -0.20  # 🔥 emergency exit

    IMPULSE_LOOKBACK = 3

    # ===== DCA CONFIG =====
    position_adjustment_enable = True
    max_entry_position_adjustment = 2
    DCA_ATR_MULTIPLIER = 1.2
    DCA_MIN_CONFIDENCE = 0.55

    # ===== PARTIAL TP CONFIG =====
    minimal_roi = {
        "0": 0.06,
        "30": 0.04,
        "90": 0.02,
    }

    # =============================
    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.last_llm_decision = {}

    # =============================
    # ===== INDICATORS ============
    # =============================
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe["rsi"] = ta.RSI(dataframe["close"], 14)
        dataframe["atr"] = ta.ATR(
            dataframe["high"], dataframe["low"], dataframe["close"], 14
        )

        tp = (dataframe["high"] + dataframe["low"] + dataframe["close"]) / 3
        dataframe["vwap"] = (tp * dataframe["volume"]).cumsum() / dataframe["volume"].cumsum()
        dataframe["price_vs_vwap"] = (dataframe["close"] - dataframe["vwap"]) / dataframe["vwap"]

        body = dataframe["close"] - dataframe["open"]
        dataframe["body_atr"] = body.abs() / dataframe["atr"]

        dataframe["bull_impulse"] = np.where(body > 0, dataframe["body_atr"], 0)
        dataframe["bear_impulse"] = np.where(body < 0, dataframe["body_atr"], 0)

        dataframe["bull_impulse_cluster"] = dataframe["bull_impulse"].rolling(
            self.IMPULSE_LOOKBACK
        ).sum()

        dataframe["bear_impulse_cluster"] = dataframe["bear_impulse"].rolling(
            self.IMPULSE_LOOKBACK
        ).sum()

        dataframe["llm_decision"] = None
        dataframe["llm_confidence"] = np.nan
        dataframe["llm_projected_profit"] = np.nan

        if len(dataframe) > self.startup_candle_count:
            self._run_llm(metadata["pair"], dataframe)

        return dataframe

    # =============================
    # ===== LLM ===================
    # =============================
    def _run_llm(self, pair: str, dataframe: DataFrame):
        last = dataframe.iloc[-1]

        trade = Trade.get_trades(
            [Trade.pair == pair, Trade.is_open.is_(True)]
        ).first()

        position = {
            "in_position": bool(trade),
            "price": last["close"],
            "unrealized": trade.calc_profit_ratio(last["close"]) if trade else 0
        }

        prompt = f"""
Evaluate trade continuation quality.

RSI: {last["rsi"]:.2f}
ATR: {last["atr"]:.6f}
Price vs VWAP: {last["price_vs_vwap"]:.4f}

Bull impulse cluster: {last["bull_impulse_cluster"]:.3f}
Bear impulse cluster: {last["bear_impulse_cluster"]:.3f}

Position:
{json.dumps(position)}

Respond ONLY in JSON:
{{
  "decision": "BUY" | "HOLD" | "SELL",
  "confidence": 0-1,
  "projected_profit": 0-0.05
}}
"""

        try:
            r = requests.post(
                self.OLLAMA_URL,
                json={
                    "model": self.OLLAMA_MODEL,
                    "prompt": prompt,
                    "stream": False,
                    "format": "json",
                },
                timeout=self.OLLAMA_TIMEOUT,
            )

            raw = r.json().get("response", "")
            logger.info(f"[LLM RAW] {pair}: {raw}")

            start, end = raw.find("{"), raw.rfind("}")
            if start == -1 or end == -1:
                return

            parsed = json.loads(raw[start:end + 1])

            dataframe.loc[dataframe.index[-1], "llm_decision"] = parsed.get("decision")
            dataframe.loc[dataframe.index[-1], "llm_confidence"] = float(parsed.get("confidence", 0))
            dataframe.loc[dataframe.index[-1], "llm_projected_profit"] = float(
                parsed.get("projected_profit", np.nan)
            )

        except Exception:
            pass

    # =============================
    # ===== ENTRY =================
    # =============================
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["enter_long"] = 0

        last = dataframe.iloc[-1]

        if (
            last["llm_decision"] == "BUY"
            and last["llm_confidence"] >= self.decision_confidence_threshold
            and last["bull_impulse_cluster"] > last["bear_impulse_cluster"]
            and last["price_vs_vwap"] < 0.004
        ):
            dataframe.loc[dataframe.index[-1], "enter_long"] = 1
            logger.info(f"[ENTRY] {metadata['pair']}")

        return dataframe

    # =============================
    # ===== SMART DCA =============
    # =============================
    def adjust_trade_position(
        self,
        trade: Trade,
        current_time,
        current_rate,
        current_profit,
        **kwargs
    ):
        if trade.nr_of_successful_entries >= self.max_entry_position_adjustment:
            return None

        if current_profit > -0.01:
            return None

        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        last = dataframe.iloc[-1]

        atr_trigger = -self.DCA_ATR_MULTIPLIER * (last["atr"] / current_rate)

        if (
            current_profit < atr_trigger
            and last["bull_impulse_cluster"] > last["bear_impulse_cluster"]
            and last["llm_confidence"] >= self.DCA_MIN_CONFIDENCE
        ):
            logger.info(f"[DCA] {trade.pair} | profit={current_profit:.3f}")
            return trade.stake_amount

        return None

    # =============================
    # ===== EXIT ==================
    # =============================
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["exit_long"] = 0

        pair = metadata["pair"]
        last = dataframe.iloc[-1]

        trade = Trade.get_trades(
            [Trade.pair == pair, Trade.is_open.is_(True)]
        ).first()

        if not trade:
            return dataframe

        profit = trade.calc_profit_ratio(last["close"])

        # 🔥 HARD SAFETY
        if profit <= self.MAX_DRAWDOWN_EXIT:
            dataframe.loc[dataframe.index[-1], "exit_long"] = 1
            logger.warning(f"[EMERGENCY EXIT] {pair} drawdown={profit:.3f}")
            return dataframe

        impulse_break = last["bull_impulse_cluster"] < last["bear_impulse_cluster"]

        # Partial TP logic via ROI table handles scaling
        if (
            impulse_break
            and profit > 0
            and last["llm_decision"] == "SELL"
        ):
            dataframe.loc[dataframe.index[-1], "exit_long"] = 1
            logger.info(f"[LLM EXIT] {pair} profit={profit:.3f}")

        return dataframe
