# source: https://raw.githubusercontent.com/thaodo2512/lstm_cnn/41abaaa52a63ac2274275f4f9147957c742276aa/freqtrade_user_data/strategies/ichi_v1.py
from __future__ import annotations

# Ichimoku + FreqAI hybrid strategy (long-only)
# - Moves indicators into FreqAI feature hooks
# - Uses FreqAI predictions to gate entries
# - Trend filters via Ichimoku cloud and EMA fans

# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import talib.abstract as ta  # type: ignore
import freqtrade.vendor.qtpylib.indicators as qtpylib
import pandas as pd  # noqa

pd.options.mode.chained_assignment = None  # default='warn'
import technical.indicators as ftt  # type: ignore
from functools import reduce
from datetime import datetime, timedelta
from freqtrade.strategy import merge_informative_pair  # type: ignore
import numpy as np


class Github_thaodo2512_lstm_cnn__ichi_v1__20251004_131124(IStrategy):
    """Ichimoku + FreqAI hybrid strategy.

    Notes
    - Subclasses IStrategy (FreqAI integrates via self.freqai.start()).
    - Predictions column name follows the label name set in set_freqai_targets (here: '&-s_close').
    - If FreqAI does not provide '&-s_close_mean'/'&-s_close_std', a rolling mean/std fallback is used.
    """

    # Buy hyperspace params
    buy_params = {
        "buy_trend_above_senkou_level": 1,
        "buy_trend_bullish_level": 6,
        "buy_fan_magnitude_shift_value": 3,
        # NOTE: Good value (Win% ~70%), a lot of trades
        "buy_min_fan_magnitude_gain": 1.002,
        # "buy_min_fan_magnitude_gain": 1.008,  # very safe (Win% ~90%), fewer trades
    }

    # Sell hyperspace params
    sell_params = {
        # Use EMA trend cross for exits
        "sell_trend_indicator": "trend_close_2h",
    }

    # ROI table
    minimal_roi = {
        "0": 0.059,
        "10": 0.037,
        "41": 0.012,
        "114": 0,
    }

    # Stoploss
    stoploss = -0.275

    # Optimal timeframe for the strategy
    timeframe = "5m"

    startup_candle_count = 96
    process_only_new_candles = False

    trailing_stop = False
    # trailing_stop_positive = 0.002
    # trailing_stop_positive_offset = 0.025
    # trailing_only_offset_is_reached = True

    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    trend_period_map = {
        "trend_close_5m": 1,
        "trend_close_15m": 3,
        "trend_close_30m": 6,
        "trend_close_1h": 12,
        "trend_close_2h": 24,
        "trend_close_4h": 48,
        "trend_close_6h": 72,
        "trend_close_8h": 96,
    }

    freqai_info = {
        "model_per_pair": True,
        "feature_parameters": {
            "include_timeframes": ["5m", "15m", "30m", "1h", "2h", "4h"],
            "include_shifted_candles": 3,
            "include_corr_pairlist": [],
            "indicator_periods_candles": [1, 3, 6, 12, 24, 48, 72, 96],
            "label_period_candles": 24,
        },
        "data_split_parameters": {
            "test_size": 0.25,
        },
        "fit_live_predictions_candles": 300,
    }

    plot_config = {
        "main_plot": {
            # fill area between senkou_a and senkou_b
            "%%-senkou_a": {
                "color": "green",  # optional
                "fill_to": "%%-senkou_b",
                "fill_label": "Ichimoku Cloud",  # optional
                "fill_color": "rgba(255,76,46,0.2)",  # optional
            },
            # plot senkou_b, too. Not only the area to it.
            "%%-senkou_b": {},
            "%%-trend_close_period_1": {"color": "#FF5733"},
            "%%-trend_close_period_3": {"color": "#FF8333"},
            "%%-trend_close_period_6": {"color": "#FFB533"},
            "%%-trend_close_period_12": {"color": "#FFE633"},
            "%%-trend_close_period_24": {"color": "#E3FF33"},
            "%%-trend_close_period_48": {"color": "#C4FF33"},
            "%%-trend_close_period_72": {"color": "#61FF33"},
            "%%-trend_close_period_96": {"color": "#33FF7D"},
        },
        "subplots": {
            "fan_magnitude": {"%%-fan_magnitude": {}},
            "fan_magnitude_gain": {"%%-fan_magnitude_gain": {}},
        },
    }

    # -------------------- FreqAI feature hooks --------------------
    def feature_engineering_expand_all(
        self, dataframe: DataFrame, period: int, metadata: dict, **kwargs
    ) -> DataFrame:
        # Write explicit period-suffixed columns to avoid naming ambiguity across periods
        dataframe[f"%%-trend_close_period_{period}"] = ta.EMA(
            dataframe["close"], timeperiod=period
        )
        dataframe[f"%%-trend_open_period_{period}"] = ta.EMA(
            dataframe["open"], timeperiod=period
        )
        dataframe[f"%%-atr_period_{period}"] = ta.ATR(dataframe, timeperiod=period)
        return dataframe

    def feature_engineering_expand_basic(
        self, dataframe: DataFrame, metadata: dict, **kwargs
    ) -> DataFrame:
        dataframe["%-pct-change"] = dataframe["close"].pct_change()
        dataframe["%-raw_volume"] = dataframe["volume"]
        dataframe["%-raw_price"] = dataframe["close"]
        return dataframe

    def feature_engineering_standard(
        self, dataframe: DataFrame, metadata: dict, **kwargs
    ) -> DataFrame:
        if "date" in dataframe.columns:
            dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7
            dataframe["%-hour_of_day"] = (dataframe["date"].dt.hour + 1) / 25

        ichimoku = ftt.ichimoku(
            dataframe,
            conversion_line_period=20,
            base_line_periods=60,
            laggin_span=120,
            displacement=30,
        )
        dataframe["%%-chikou_span"] = ichimoku["chikou_span"]
        dataframe["%%-tenkan_sen"] = ichimoku["tenkan_sen"]
        dataframe["%%-kijun_sen"] = ichimoku["kijun_sen"]
        dataframe["%%-senkou_a"] = ichimoku["senkou_span_a"]
        dataframe["%%-senkou_b"] = ichimoku["senkou_span_b"]
        dataframe["%%-leading_senkou_span_a"] = ichimoku["leading_senkou_span_a"]
        dataframe["%%-leading_senkou_span_b"] = ichimoku["leading_senkou_span_b"]
        dataframe["%%-cloud_green"] = ichimoku["cloud_green"]
        dataframe["%%-cloud_red"] = ichimoku["cloud_red"]

        # Ensure required EMA fan columns exist even if expand_all hasn't populated them yet
        def _ema(series: pd.Series, n: int) -> pd.Series:
            try:
                return ta.EMA(series, timeperiod=n)
            except Exception:
                return series.ewm(span=max(1, n), adjust=False).mean()

        for p in [1, 3, 6, 12, 24, 48, 72, 96]:
            # Prefer underscore naming; also support legacy hyphen names by aliasing
            ccol = f"%%-trend_close_period_{p}"
            ocol = f"%%-trend_open_period_{p}"
            if ccol not in dataframe.columns:
                legacy = f"%%-trend_close-period_{p}"
                if legacy in dataframe.columns:
                    dataframe[ccol] = dataframe[legacy]
                else:
                    dataframe[ccol] = _ema(dataframe["close"], p)
            if ocol not in dataframe.columns:
                legacy = f"%%-trend_open-period_{p}"
                if legacy in dataframe.columns:
                    dataframe[ocol] = dataframe[legacy]
                else:
                    dataframe[ocol] = _ema(dataframe["open"], p)

        # Fan magnitude and acceleration (safe if columns were just created)
        dataframe["%%-fan_magnitude"] = (
            dataframe["%%-trend_close_period_12"] / dataframe["%%-trend_close_period_96"]
        )
        dataframe["%%-fan_magnitude_gain"] = (
            dataframe["%%-fan_magnitude"] / dataframe["%%-fan_magnitude"].shift(1)
        )

        return dataframe

    def set_freqai_targets(
        self, dataframe: DataFrame, metadata: dict, **kwargs
    ) -> DataFrame:
        # Predict forward return over the label_period (mean-close ratio - 1)
        lp = int(self.freqai_info["feature_parameters"]["label_period_candles"])
        dataframe["&-s_close"] = (
            dataframe["close"].shift(-lp).rolling(lp).mean() / dataframe["close"] - 1
        )
        return dataframe

    # -------------------- Strategy hooks --------------------
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        heikinashi = qtpylib.heikinashi(dataframe)
        dataframe["open"] = heikinashi["open"]
        # dataframe['close'] = heikinashi['close']
        dataframe["high"] = heikinashi["high"]
        dataframe["low"] = heikinashi["low"]

        dataframe = self.freqai.start(dataframe, metadata, self)
        return dataframe

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

        conditions = []

        # Integrate FreqAI prediction
        if "do_predict" in df.columns:
            conditions.append(df["do_predict"] == 1)

        # Fallback for target statistics if not provided by FreqAI
        if "&-s_close_mean" not in df.columns or "&-s_close_std" not in df.columns:
            pred = df.get("&-s_close", pd.Series(np.nan, index=df.index))
            roll = pred.rolling(100, min_periods=10)
            df["&-s_close_mean"] = roll.mean()
            df["&-s_close_std"] = roll.std(ddof=0)

        # Thresholded target
        df["target_roi"] = df["&-s_close_mean"] + df["&-s_close_std"] * 1.5
        if "&-s_close" in df.columns:
            conditions.append(df["&-s_close"] > df["target_roi"])

        # Trending market above cloud
        level = int(self.buy_params["buy_trend_above_senkou_level"])
        if level >= 1:
            conditions.append(df["%%-trend_close_period_1"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_1"] > df["%%-senkou_b"])
        if level >= 2:
            conditions.append(df["%%-trend_close_period_3"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_3"] > df["%%-senkou_b"])
        if level >= 3:
            conditions.append(df["%%-trend_close_period_6"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_6"] > df["%%-senkou_b"])
        if level >= 4:
            conditions.append(df["%%-trend_close_period_12"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_12"] > df["%%-senkou_b"])
        if level >= 5:
            conditions.append(df["%%-trend_close_period_24"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_24"] > df["%%-senkou_b"])
        if level >= 6:
            conditions.append(df["%%-trend_close_period_48"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_48"] > df["%%-senkou_b"])
        if level >= 7:
            conditions.append(df["%%-trend_close_period_72"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_72"] > df["%%-senkou_b"])
        if level >= 8:
            conditions.append(df["%%-trend_close_period_96"] > df["%%-senkou_a"])
            conditions.append(df["%%-trend_close_period_96"] > df["%%-senkou_b"])

        # Trends bullish (close EMA above open EMA)
        bull_level = int(self.buy_params["buy_trend_bullish_level"])
        if bull_level >= 1:
            conditions.append(df["%%-trend_close_period_1"] > df["%%-trend_open_period_1"])
        if bull_level >= 2:
            conditions.append(df["%%-trend_close_period_3"] > df["%%-trend_open_period_3"])
        if bull_level >= 3:
            conditions.append(df["%%-trend_close_period_6"] > df["%%-trend_open_period_6"])
        if bull_level >= 4:
            conditions.append(df["%%-trend_close_period_12"] > df["%%-trend_open_period_12"])
        if bull_level >= 5:
            conditions.append(df["%%-trend_close_period_24"] > df["%%-trend_open_period_24"])
        if bull_level >= 6:
            conditions.append(df["%%-trend_close_period_48"] > df["%%-trend_open_period_48"])
        if bull_level >= 7:
            conditions.append(df["%%-trend_close_period_72"] > df["%%-trend_open_period_72"])
        if bull_level >= 8:
            conditions.append(df["%%-trend_close_period_96"] > df["%%-trend_open_period_96"])

        # Fan magnitude acceleration
        conditions.append(df["%%-fan_magnitude_gain"] >= self.buy_params["buy_min_fan_magnitude_gain"])
        conditions.append(df["%%-fan_magnitude"] > 1)
        for x in range(int(self.buy_params["buy_fan_magnitude_shift_value"])):
            conditions.append(df["%%-fan_magnitude"].shift(x + 1) < df["%%-fan_magnitude"])

        if conditions:
            df.loc[reduce(lambda x, y: x & y, conditions), "enter_long"] = 1

        return df

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

        trend_indicator = self.sell_params["sell_trend_indicator"]
        period = int(self.trend_period_map[trend_indicator])
        cond = qtpylib.crossed_below(
            df["%%-trend_close_period_1"], df[f"%%-trend_close_period_{period}"]
        )
        df.loc[cond.fillna(False), "exit_long"] = 1
        return df
