# source: https://raw.githubusercontent.com/whitneyjohn61/freqtrade-strategy-lab/cd9ba36b9b9d121c6002194e02dfa41f5fc72ff0/user_data/strategies/StarterStrategy_V01.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa
# isort: skip_file
"""
Starter FreqAI strategy for freqtrade-strategy-lab.
Simplified regression setup: targets &s_close / &s_volatility, XGBoostRegressor in config.
Replace thresholds and logic as you iterate — see CLAUDE.md and user_data/info/PROJECT_NOTES.md.
"""
import numpy as np
import pandas as pd
from pandas import DataFrame
from datetime import datetime

import talib.abstract as ta

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


class Github_whitneyjohn61_freqtrade_strategy_lab__StarterStrategy_V01__20260323_214946(IStrategy):
    minimal_roi = {"0": 0.10, "240": 0.05, "480": 0}
    stoploss = -0.12
    use_custom_stoploss = True

    trailing_stop = True
    trailing_stop_positive = 0.015
    trailing_stop_positive_offset = 0.04
    trailing_only_offset_is_reached = True

    timeframe = "15m"
    inf_1h = "1h"

    can_short = True
    startup_candle_count = 1000

    # Fixed lab thresholds — move to Hyperopt DecimalParameter when ready
    ai_direction_threshold = 0.012
    ai_volatility_threshold = 0.012
    di_max_threshold = 0.45
    stoploss_multiplier = 1.4
    stoploss_hard_cap = 0.04

    def feature_engineering_expand_all(
        self, dataframe: DataFrame, period: int, metadata: dict, **kwargs
    ) -> DataFrame:
        dataframe[f"%-rsi-{period}"] = ta.RSI(dataframe, timeperiod=period)
        dataframe[f"%-roc-{period}"] = ta.ROC(dataframe, timeperiod=period)
        dataframe[f"%-atr-{period}"] = ta.ATR(dataframe, timeperiod=period)
        safe_atr = dataframe[f"%-atr-{period}"].replace(0, np.nan)
        dataframe[f"%-rsi_atr_ratio-{period}"] = dataframe[f"%-rsi-{period}"] / safe_atr
        ema = ta.EMA(dataframe, timeperiod=period)
        dataframe[f"%-ema_dist-{period}"] = (dataframe["close"] - ema) / ema
        bollinger = ta.BBANDS(dataframe, timeperiod=period, nbdevup=2.0, nbdevdn=2.0, matype=0)
        dataframe[f"%-bb_width-{period}"] = (
            bollinger["upperband"] - bollinger["lowerband"]
        ) / bollinger["middleband"]
        return dataframe

    def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs) -> DataFrame:
        dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
        dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
        return dataframe

    def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
        label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
        dataframe["&s_close"] = np.log(dataframe["close"].shift(-label_period) / dataframe["close"])
        future_high = dataframe["high"].rolling(label_period).max().shift(-label_period)
        future_low = dataframe["low"].rolling(label_period).min().shift(-label_period)
        dataframe["&s_volatility"] = (future_high - future_low) / dataframe["close"]
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = self.freqai.start(dataframe, metadata, self)
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=9)
        dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=21)
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if not self.config.get("freqai", {}).get("enabled", False):
            return dataframe
        if "do_predict" not in dataframe.columns:
            return dataframe
        valid_prediction = dataframe["do_predict"] == 1
        if "DI_values" in dataframe.columns:
            safe_situation = dataframe["DI_values"] < self.di_max_threshold
        else:
            safe_situation = pd.Series(True, index=dataframe.index)
        if "&s_close" not in dataframe.columns or "&s_volatility" not in dataframe.columns:
            return dataframe

        ai_long = (
            (dataframe["&s_close"] > self.ai_direction_threshold)
            & (dataframe["&s_volatility"] > self.ai_volatility_threshold)
        )
        tech_long = (dataframe["rsi"] < 70) & (dataframe["ema_fast"] > dataframe["ema_slow"])
        dataframe.loc[
            valid_prediction & safe_situation & ai_long & tech_long & (dataframe["volume"] > 0),
            "enter_long",
        ] = 1

        ai_short = (
            (dataframe["&s_close"] < -self.ai_direction_threshold)
            & (dataframe["&s_volatility"] > self.ai_volatility_threshold)
        )
        tech_short = (dataframe["rsi"] > 30) & (dataframe["ema_fast"] < dataframe["ema_slow"])
        dataframe.loc[
            valid_prediction & safe_situation & ai_short & tech_short & (dataframe["volume"] > 0),
            "enter_short",
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if not self.config.get("freqai", {}).get("enabled", False):
            return dataframe
        if "&s_close" not in dataframe.columns or "DI_values" not in dataframe.columns:
            return dataframe
        di_breach = dataframe["DI_values"] >= self.di_max_threshold
        dataframe.loc[(dataframe["&s_close"] < 0) | di_breach, "exit_long"] = 1
        dataframe.loc[(dataframe["&s_close"] > 0) | di_breach, "exit_short"] = 1
        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        if "atr" in last_candle and pd.notna(last_candle["atr"]):
            atr_pct = last_candle["atr"] / trade.open_rate
            dynamic_stop_pct = atr_pct * self.stoploss_multiplier
            dynamic_stop_pct = max(0.010, min(self.stoploss_hard_cap, dynamic_stop_pct))
            if trade.is_short:
                stop_price = trade.open_rate * (1 + dynamic_stop_pct)
                rel_stop = (current_rate - stop_price) / current_rate
            else:
                stop_price = trade.open_rate * (1 - dynamic_stop_pct)
                rel_stop = (stop_price - current_rate) / current_rate
            return rel_stop
        return -0.025

    def leverage(
        self,
        pair: str,
        current_time: datetime,
        current_rate: float,
        proposed_leverage: float,
        max_leverage: float,
        entry_tag: str,
        side: str,
        **kwargs,
    ) -> float:
        return min(3.0, max_leverage)
