# source: https://raw.githubusercontent.com/afsarali273/TradingBot_AI/17c2203f14a27ccc2fd6a771e9d9998939346265/user_data/strategies/AIStrategy.py
from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

import pandas as pd
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy


class Github_afsarali273_TradingBot_AI__AIStrategy__20260308_154405(IStrategy):
    timeframe = "1m"
    process_only_new_candles = True
    startup_candle_count = 50

    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # ROI is managed mostly via custom_exit + AI targets.
    minimal_roi = {"0": 0.10}
    stoploss = -0.99

    can_short = False

    base_dir = Path(__file__).resolve().parents[1]
    ai_strategy_file = base_dir / "ai_strategy.json"
    signals_file = base_dir / "signals.json"

    def _load_json(self, path: Path, default):
        if not path.exists():
            return default
        try:
            with path.open("r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            return default

    def _load_ai_cfg(self) -> dict:
        default = {
            "pairs": ["DOGE/USD"],
            "timeframe": "1m",
            "target_profit": 0.01,
            "stop_loss": 0.01,
            "confidence_threshold": 0.7,
            "max_daily_loss": 0.01,
            "max_daily_profit": 0.05,
        }
        return self._load_json(self.ai_strategy_file, default)

    def _load_signals(self) -> dict:
        return self._load_json(self.signals_file, {"signals": []})

    def informative_pairs(self):
        cfg = self._load_ai_cfg()
        pairs = [p for p in cfg.get("pairs", []) if isinstance(p, str)]
        return [(p, self.timeframe) for p in pairs]

    def _signal_for_pair(self, pair: str) -> Optional[dict]:
        pair_dash = pair.replace("/", "-")
        data = self._load_signals()
        for sig in data.get("signals", []):
            if sig.get("coin") == pair_dash:
                return sig
        return None

    def _ai_pair_allowed(self, pair: str) -> bool:
        cfg = self._load_ai_cfg()
        return pair in cfg.get("pairs", [])

    def _daily_pnl_ratio(self) -> float:
        """
        Sum realized close_profit (ratio) for current UTC day.
        Example: two closed trades at +1% and -0.5% => +0.5% day ratio (0.005).
        """
        today = datetime.now(timezone.utc).date()
        closed = Trade.get_trades_proxy(is_open=False)
        profits: list[float] = []
        for trade in closed:
            if trade.close_date_utc and trade.close_date_utc.date() == today:
                profits.append(float(trade.close_profit or 0.0))
        return float(sum(profits))

    def _risk_limits_reached(self) -> bool:
        cfg = self._load_ai_cfg()
        pnl = self._daily_pnl_ratio()
        max_loss = -abs(float(cfg.get("max_daily_loss", 0.01)))
        max_profit = abs(float(cfg.get("max_daily_profit", 0.05)))
        return pnl <= max_loss or pnl >= max_profit

    def _threshold(self) -> float:
        cfg = self._load_ai_cfg()
        return float(cfg.get("confidence_threshold", 0.7))

    def _target_profit_ratio(self, signal: Optional[dict]) -> float:
        cfg = self._load_ai_cfg()
        if signal and signal.get("take_profit_ratio") is not None:
            try:
                return abs(float(signal["take_profit_ratio"]))
            except Exception:
                pass
        return abs(float(cfg.get("target_profit", 0.01)))

    def _stop_loss_ratio(self, signal: Optional[dict]) -> float:
        cfg = self._load_ai_cfg()
        if signal and signal.get("stop_loss_ratio") is not None:
            try:
                return abs(float(signal["stop_loss_ratio"]))
            except Exception:
                pass
        return abs(float(cfg.get("stop_loss", 0.01)))

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        return dataframe

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

        pair = metadata["pair"]
        if not self._ai_pair_allowed(pair):
            return dataframe

        if self._risk_limits_reached():
            return dataframe

        sig = self._signal_for_pair(pair)
        threshold = self._threshold()

        if sig and sig.get("action") == "BUY" and float(sig.get("confidence", 0.0)) >= threshold:
            dataframe.loc[dataframe.index[-1], "enter_long"] = 1

        return dataframe

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

        pair = metadata["pair"]
        sig = self._signal_for_pair(pair)
        if sig and sig.get("action") == "SELL":
            dataframe.loc[dataframe.index[-1], "exit_long"] = 1

        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        after_fill: bool = False,
        **kwargs,
    ) -> float:
        sig = self._signal_for_pair(pair)
        stop_ratio = self._stop_loss_ratio(sig)
        # Freqtrade expects negative ratio.
        return -abs(stop_ratio)

    def custom_exit(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ):
        sig = self._signal_for_pair(pair)

        if self._risk_limits_reached():
            return "daily_limit_reached"

        if sig and sig.get("action") == "SELL":
            return "ai_sell_signal"

        target = self._target_profit_ratio(sig)
        if current_profit >= target:
            return "ai_take_profit"

        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:
        if not self._ai_pair_allowed(pair):
            return 0.0
        if self._risk_limits_reached():
            return 0.0
        sig = self._signal_for_pair(pair)
        if not sig:
            return 0.0
        if sig.get("action") != "BUY":
            return 0.0
        if float(sig.get("confidence", 0.0)) < self._threshold():
            return 0.0

        stake = min(proposed_stake, max_stake)
        if min_stake is not None:
            stake = max(stake, min_stake)
        return stake
