# source: https://raw.githubusercontent.com/shatianming5/Agent_market/8c0fefbce46f5910f461758b1284d50b395381d7/workspace/strategies/ml_strategy_v3.py
from __future__ import annotations
import sys, json
from pathlib import Path
import numpy as np
from pandas import DataFrame
_ROOT = Path(__file__).resolve().parents[2]
if str(_ROOT / "src") not in sys.path:
    sys.path.insert(0, str(_ROOT / "src"))
    sys.path.insert(0, str(_ROOT))
from freqtrade.strategy import IStrategy
from agent_market.freqai.features import apply_configured_features
from agent_market.freqai.model.base import ModelRegistry
import agent_market.freqai.model
from workspace.model_loader import scan_and_register


class Github_shatianming5_Agent_market__ml_strategy_v3__20260330_154001(IStrategy):
    INTERFACE_VERSION = 3

    timeframe = "5m"
    can_short = False

    minimal_roi = {"0": 0.10}
    stoploss = -0.10
    trailing_stop = False

    process_only_new_candles = True
    startup_candle_count = 200

    # Prediction threshold for entries
    threshold: float = 0.0

    # Internal cache
    _ml_model = None
    _ml_feature_cols: list[str] | None = None
    _ml_ready: bool = False
    _ml_error: str | None = None

    MODEL_DIR = Path("/Users/shatianming/Downloads/Agent_market/workspace/results/model_auto_ml_v3")
    REGISTRY_NAME = "auto_ml_v3"

    @staticmethod
    def _extract_feature_columns(summary: dict) -> list[str]:
        for key in ("feature_columns", "features", "training_features", "used_features", "feature_names", "columns"):
            val = summary.get(key)
            if isinstance(val, list) and all(isinstance(x, str) for x in val):
                return val

        # Common nested shapes
        for key in ("data", "train", "training", "meta", "metadata"):
            nest = summary.get(key)
            if isinstance(nest, dict):
                cols = Github_shatianming5_Agent_market__ml_strategy_v3__20260330_154001._extract_feature_columns(nest)
                if cols:
                    return cols

        raise KeyError(
            "Unable to find feature column names in training_summary.json. "
            "Expected keys like 'feature_columns' or 'features'."
        )

    def _ensure_model_loaded(self) -> None:
        if self._ml_ready:
            return

        try:
            model_dir = self.MODEL_DIR
            if not model_dir.exists():
                raise FileNotFoundError(f"Model directory not found: {model_dir}")

            feature_cfg_path = _ROOT / "user_data" / "freqai_features_real.json"
            if not feature_cfg_path.exists():
                raise FileNotFoundError(f"Feature config not found: {feature_cfg_path}")

            training_summary_path = model_dir / "training_summary.json"
            if not training_summary_path.exists():
                raise FileNotFoundError(f"training_summary.json not found: {training_summary_path}")

            cfg = json.loads(feature_cfg_path.read_text(encoding="utf-8"))
            summary = json.loads(training_summary_path.read_text(encoding="utf-8"))
            feature_cols = self._extract_feature_columns(summary)

            # Ensure custom model classes are registered before create()
            scan_and_register()

            config = {
                "feature_config_path": str(feature_cfg_path),
                "model_dir": str(model_dir),
                "training_summary": summary,
                "feature_columns": feature_cols,
            }
            model = ModelRegistry.create(self.REGISTRY_NAME, config)
            model.load(str(model_dir))

            self._ml_model = model
            self._ml_feature_cols = feature_cols
            self._ml_ready = True
            self._ml_error = None
        except Exception as e:
            self._ml_model = None
            self._ml_feature_cols = None
            self._ml_ready = False
            self._ml_error = f"{type(e).__name__}: {e}"

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Always provide the prediction column (safe default)
        if dataframe.empty:
            dataframe["prediction"] = np.nan
            return dataframe

        self._ensure_model_loaded()

        try:
            feature_cfg_path = _ROOT / "user_data" / "freqai_features_real.json"
            cfg = json.loads(feature_cfg_path.read_text(encoding="utf-8"))

            # Apply configured features
            dataframe = apply_configured_features(dataframe, cfg)

            if not self._ml_ready or self._ml_model is None or not self._ml_feature_cols:
                dataframe["prediction"] = 0.0
                return dataframe

            # Build feature matrix in training feature order
            missing = [c for c in self._ml_feature_cols if c not in dataframe.columns]
            if missing:
                raise KeyError(f"Missing required feature columns in dataframe: {missing[:20]}")

            X = dataframe.loc[:, self._ml_feature_cols].to_numpy(dtype=np.float32, copy=False)
            # Replace NaN/inf to keep model inference stable
            X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)

            preds = self._ml_model.predict(X)
            preds = np.asarray(preds, dtype=np.float32).reshape(-1)
            if len(preds) != len(dataframe):
                raise ValueError(f"Prediction length mismatch: got {len(preds)} expected {len(dataframe)}")

            dataframe["prediction"] = preds
            return dataframe
        except Exception:
            dataframe["prediction"] = 0.0
            return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if dataframe.empty:
            return dataframe

        if "prediction" not in dataframe.columns:
            dataframe["prediction"] = 0.0

        cond = dataframe["prediction"] > float(self.threshold)
        dataframe.loc[cond, "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if dataframe.empty:
            return dataframe

        if "prediction" not in dataframe.columns:
            dataframe["prediction"] = 0.0

        cond = dataframe["prediction"] < 0.0
        dataframe.loc[cond, "exit_long"] = 1
        return dataframe