# source: https://raw.githubusercontent.com/lam123-cell/vector-quantization-trading/b1e41695d6e9564c269a6dfc9d41ff4e3c6c0a37/vq-trading-binance/freqtrade_setup/user_data/strategies/FQ_TurboQuantStrategy.py
from __future__ import annotations

from pathlib import Path

import numpy as np
import pandas as pd
from pandas import DataFrame

from freqtrade.strategy import IStrategy


class Github_lam123_cell_vector_quantization_trading__FQ_TurboQuantStrategy__20260420_131015(IStrategy):
    """TurboQuant strategy using OHLCV + tq_xhat_* features from freqtrade_dataset.csv.

    Core thesis-oriented logic:
    - Decision is driven primarily by reconstructed features `tq_xhat_*`.
    - Distortion-aware filters are enforced via `tq_error`, `tq_score`, `tq_confidence`.
    """

    INTERFACE_VERSION = 3

    timeframe = "1m"
    can_short = False
    process_only_new_candles = True
    startup_candle_count = 240

    minimal_roi = {
        "0": 0.025,
        "45": 0.012,
        "120": 0.0,
    }

    stoploss = -0.03

    trailing_stop = True
    trailing_stop_positive = 0.009
    trailing_stop_positive_offset = 0.015
    trailing_only_offset_is_reached = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    protections = [
        {
            "method": "CooldownPeriod",
            "stop_duration_candles": 35,
        }
    ]

    # Thresholds on reconstructed normalized features (tq_xhat_*)
    tqx_min_trend = 0.05
    tqx_max_volatility = 0.70
    tqx_max_atr = 0.70
    tqx_exit_trend = -0.15

    # Distortion / quality thresholds for thesis claims.
    tq_max_error = 0.27
    tq_exit_error = 0.34
    tq_min_score = 0.18
    tq_exit_score = -0.06
    tq_min_confidence = 0.76
    tq_exit_confidence = 0.62

    plot_config = {
        "main_plot": {
            "tqx_trend": {},
            "tqx_momentum": {},
        },
        "subplots": {
            "TurboQuant": {
                "tq_xhat_rsi": {},
                "tq_xhat_volatility": {},
                "tq_xhat_atr": {},
                "tqx_trend": {},
                "tqx_momentum": {},
            },
            "Quality": {
                "tq_error": {},
                "tq_score": {},
                "tq_confidence": {},
            },
            "OHLCV": {
                "volume_ratio": {},
                "ohlcv_body": {},
            },
        },
    }

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self._tqx_features: DataFrame = pd.DataFrame()
        self._tqx_last_mtime_ns: int | None = None

    def _dataset_path(self) -> Path:
        # In docker compose, only ./user_data is mounted to /freqtrade/user_data.
        return Path(__file__).resolve().parents[1] / "freqtrade_dataset.csv"

    def _reload_tqx_features_if_needed(self) -> None:
        dataset_path = self._dataset_path()
        if not dataset_path.exists():
            self._tqx_features = pd.DataFrame()
            self._tqx_last_mtime_ns = None
            return

        stat = dataset_path.stat()
        if self._tqx_last_mtime_ns == stat.st_mtime_ns and not self._tqx_features.empty:
            return

        required_cols = [
            "time",
            "tq_xhat_log_return",
            "tq_xhat_return_5",
            "tq_xhat_log_volume",
            "tq_xhat_candle_body",
            "tq_xhat_rsi",
            "tq_xhat_macd",
            "tq_xhat_macd_signal",
            "tq_xhat_volatility",
            "tq_xhat_atr",
            "tq_error",
            "tq_score",
            "tq_confidence",
            "tq_regime",
        ]

        tqx_df = pd.read_csv(dataset_path, usecols=required_cols)
        # Align with Freqtrade candle index (UTC-aware) to prevent all-NaN reindex.
        tqx_df["date"] = pd.to_datetime(tqx_df["time"], errors="coerce", utc=True)
        tqx_df = tqx_df.dropna(subset=["date"])

        for col in required_cols[1:]:
            tqx_df[col] = pd.to_numeric(tqx_df[col], errors="coerce")

        tqx_df = tqx_df[["date"] + required_cols[1:]]
        tqx_df = tqx_df.drop_duplicates(subset=["date"], keep="last").sort_values("date")
        self._tqx_features = tqx_df.set_index("date")
        self._tqx_last_mtime_ns = stat.st_mtime_ns

    def _merge_tqx_features(self, dataframe: DataFrame) -> DataFrame:
        self._reload_tqx_features_if_needed()

        dataframe = dataframe.copy()
        dataframe["date"] = pd.to_datetime(dataframe["date"], errors="coerce", utc=True)

        tqx_cols = [
            "tq_xhat_log_return",
            "tq_xhat_return_5",
            "tq_xhat_log_volume",
            "tq_xhat_candle_body",
            "tq_xhat_rsi",
            "tq_xhat_macd",
            "tq_xhat_macd_signal",
            "tq_xhat_volatility",
            "tq_xhat_atr",
            "tq_error",
            "tq_score",
            "tq_confidence",
            "tq_regime",
        ]

        if self._tqx_features.empty:
            for col in tqx_cols:
                dataframe[col] = 0.0
            dataframe["tq_error"] = 999.0
            return dataframe

        tqx_aligned = self._tqx_features.reindex(pd.DatetimeIndex(dataframe["date"]))
        for col in tqx_cols:
            dataframe[col] = tqx_aligned[col].to_numpy()

        for col in tqx_cols:
            if col == "tq_error":
                dataframe[col] = dataframe[col].fillna(999.0)
            elif col == "tq_regime":
                dataframe[col] = dataframe[col].fillna(0.0)
            else:
                dataframe[col] = dataframe[col].fillna(0.0)

        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = dataframe.copy()

        dataframe["volume_mean"] = dataframe["volume"].rolling(20, min_periods=20).mean()
        dataframe["volume_ratio"] = dataframe["volume"] / dataframe["volume_mean"].replace(0, np.nan)
        dataframe["ohlcv_body"] = (dataframe["close"] - dataframe["open"]) / dataframe["open"].replace(0, np.nan)

        dataframe = self._merge_tqx_features(dataframe)

        dataframe["tqx_trend"] = dataframe["tq_xhat_macd"] - dataframe["tq_xhat_macd_signal"]
        dataframe["tqx_momentum"] = dataframe["tq_xhat_log_return"] + 0.35 * dataframe["tq_xhat_return_5"]

        numeric_cols = [
            "volume_mean",
            "volume_ratio",
            "ohlcv_body",
            "tq_xhat_log_return",
            "tq_xhat_return_5",
            "tq_xhat_rsi",
            "tq_xhat_macd",
            "tq_xhat_macd_signal",
            "tq_xhat_volatility",
            "tq_xhat_atr",
            "tq_error",
            "tq_score",
            "tq_confidence",
            "tq_regime",
            "tqx_trend",
            "tqx_momentum",
        ]

        dataframe[numeric_cols] = dataframe[numeric_cols].replace([np.inf, -np.inf], np.nan)
        dataframe["tq_error"] = dataframe["tq_error"].fillna(999.0)
        dataframe[[c for c in numeric_cols if c != "tq_error"]] = dataframe[
            [c for c in numeric_cols if c != "tq_error"]
        ].fillna(0.0)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = [
            dataframe["volume"] > 0,
            dataframe["volume_ratio"] > 1.25,
            dataframe["tq_xhat_rsi"].between(-0.50, 0.65),
            dataframe["tqx_trend"] >= self.tqx_min_trend,
            dataframe["tqx_momentum"] > 0.03,
            dataframe["tq_xhat_volatility"] <= self.tqx_max_volatility,
            dataframe["tq_xhat_atr"] <= self.tqx_max_atr,
            dataframe["tq_error"] <= self.tq_max_error,
            dataframe["tq_score"] >= self.tq_min_score,
            dataframe["tq_confidence"] >= self.tq_min_confidence,
            dataframe["tq_regime"] > 0,
            dataframe["ohlcv_body"] > -0.02,
        ]

        dataframe.loc[
            pd.concat(conditions, axis=1).all(axis=1),
            "enter_long",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = [
            dataframe["volume"] > 0,
            (
                (dataframe["tqx_trend"] <= self.tqx_exit_trend)
                | (dataframe["tqx_momentum"] < -0.25)
                | (dataframe["tq_xhat_volatility"] > 0.95)
                | (dataframe["tq_xhat_atr"] > 0.95)
                | (dataframe["tq_error"] >= self.tq_exit_error)
                | (dataframe["tq_score"] <= self.tq_exit_score)
                | (dataframe["tq_confidence"] < self.tq_exit_confidence)
                | (dataframe["tq_regime"] < 0)
            ),
        ]

        dataframe.loc[
            pd.concat(conditions, axis=1).all(axis=1),
            "exit_long",
        ] = 1

        return dataframe