# source: https://raw.githubusercontent.com/4tie/fortiesr/d5cc40760e3cbaff381058a0ea8733d2be469578/backend/services/auto_quant/generator/basic_generators.py
"""Basic strategy template generators for the Auto-Quant pipeline.

Produces simple, valid freqtrade strategies with standard entry logic patterns
including adaptive regime detection, categorical entry selection, and momentum.
"""

from __future__ import annotations


def generate_strategy_source_adaptive(class_name: str) -> str:
    """Return a complete freqtrade strategy with ATR-based regime detection.

    Two parameter sets (Aggressive = trending market, Conservative = ranging
    market) are selected at runtime based on a 14-period ATR relative to its
    50-period rolling median.  Both sets are IntParameters so freqtrade's
    hyperopt can tune them independently via the 'buy' space.
    """
    return f'''\
# Auto-generated by Strategy Lab — Auto-Quant Factory (Adaptive Regime Edition)
# Regime is detected from ATR vs its rolling median (high ATR → trending).
# Hyperopt should use: freqtrade hyperopt --strategy Github_4tie_fortiesr__basic_generators__20260709_213639 --spaces buy roi stoploss

from freqtrade.strategy import IntParameter, DecimalParameter, IStrategy
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np


class Github_4tie_fortiesr__basic_generators__20260709_213639(IStrategy):
    INTERFACE_VERSION: int = 3

    minimal_roi = {{
        "0": 0.08,
        "30": 0.04,
        "60": 0.02,
        "120": 0,
    }}

    stoploss = -0.05
    timeframe = "5m"
    trailing_stop = False
    process_only_new_candles = True

    # ── Regime-adaptive entry parameters ─────────────────────────────────────
    # Trending (high ATR) thresholds — more aggressive
    rsi_entry_trend   = IntParameter(20, 40, default=28, space="buy", optimize=True)
    macd_hist_min_trend = DecimalParameter(0.0, 0.005, default=0.001, space="buy", optimize=True)

    # Ranging (low ATR) thresholds — more conservative
    rsi_entry_range   = IntParameter(25, 45, default=35, space="buy", optimize=True)
    bb_entry_pct      = DecimalParameter(0.95, 1.0, default=0.98, space="buy", optimize=True)

    # ATR regime window
    atr_regime_window = IntParameter(30, 100, default=50, space="buy", optimize=False)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # ATR + rolling median for regime detection
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
        dataframe["atr_median"] = (
            dataframe["atr"]
            .rolling(window=self.atr_regime_window.value, min_periods=1)
            .median()
        )
        # 1 = trending (ATR above median), 0 = ranging
        dataframe["regime"] = np.where(
            dataframe["atr"] > dataframe["atr_median"], 1, 0
        )

        # MACD
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd"]       = macd["macd"]
        dataframe["macdsignal"] = macd["macdsignal"]
        dataframe["macdhist"]   = macd["macdhist"]

        # RSI
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)

        # Bollinger Bands
        bollinger = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe), window=20, stds=2
        )
        dataframe["bb_lowerband"] = bollinger["lower"]
        dataframe["bb_upperband"] = bollinger["upper"]

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Trending regime: MACD cross + RSI filter
        trend_cond = (
            (dataframe["regime"] == 1)
            & (dataframe["macdhist"] > self.macd_hist_min_trend.value)
            & qtpylib.crossed_above(dataframe["macd"], dataframe["macdsignal"])
            & (dataframe["rsi"] < self.rsi_entry_trend.value + 30)
            & (dataframe["volume"] > 0)
        )

        # Ranging regime: RSI oversold + price near lower BB
        range_cond = (
            (dataframe["regime"] == 0)
            & (dataframe["rsi"] < self.rsi_entry_range.value)
            & (dataframe["close"] <= dataframe["bb_lowerband"] * (1 + (1 - self.bb_entry_pct.value)))
            & (dataframe["volume"] > 0)
        )

        dataframe.loc[trend_cond | range_cond, "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe
'''


def generate_strategy_source(class_name: str) -> str:
    """Return a complete freqtrade strategy source string.

    The generated strategy includes:
    - CategoricalParameter('entry_logic') with three choices
    - populate_indicators computing MACD, RSI, and Bollinger Bands always
    - populate_entry_trend routing on self.entry_logic.value
    - A minimal populate_exit_trend stub (relies on ROI/stoploss)
    """
    return f'''\
# Auto-generated by Strategy Lab — Auto-Quant Factory
# Entry logic is controlled by the CategoricalParameter `entry_logic`.
# Optimise it with: freqtrade hyperopt --strategy Github_4tie_fortiesr__basic_generators__20260709_213639 --spaces buy

from freqtrade.strategy import CategoricalParameter, IStrategy
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib


class Github_4tie_fortiesr__basic_generators__20260709_213639(IStrategy):
    INTERFACE_VERSION: int = 3

    # --- ROI / stoploss / timeframe defaults ----------------------------
    minimal_roi = {{
        "0": 0.10,
        "30": 0.05,
        "60": 0.02,
        "120": 0,
    }}

    stoploss = -0.05
    timeframe = "5m"

    trailing_stop = False

    # --- Categorical entry-logic selector --------------------------------
    # Hyperopt will search this space when --spaces buy is used.
    entry_logic = CategoricalParameter(
        ["macd_cross", "rsi_oversold", "bb_breakout"],
        default="macd_cross",
        space="buy",
        optimize=True,
    )

    # --- Indicator computation -------------------------------------------

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # MACD (fast=12, slow=26, signal=9)
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd"] = macd["macd"]
        dataframe["macdsignal"] = macd["macdsignal"]
        dataframe["macdhist"] = macd["macdhist"]

        # RSI (period=14)
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)

        # Bollinger Bands (period=20, stddev=2)
        bollinger = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe), window=20, stds=2
        )
        dataframe["bb_lowerband"] = bollinger["lower"]
        dataframe["bb_middleband"] = bollinger["mid"]
        dataframe["bb_upperband"] = bollinger["upper"]

        return dataframe

    # --- Entry logic router ----------------------------------------------

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        logic = self.entry_logic.value

        if logic == "macd_cross":
            dataframe.loc[
                (
                    qtpylib.crossed_above(dataframe["macd"], dataframe["macdsignal"])
                    & (dataframe["volume"] > 0)
                ),
                "enter_long",
            ] = 1

        elif logic == "rsi_oversold":
            dataframe.loc[
                (
                    (dataframe["rsi"] < 30)
                    & (dataframe["volume"] > 0)
                ),
                "enter_long",
            ] = 1

        elif logic == "bb_breakout":
            dataframe.loc[
                (
                    (dataframe["close"] < dataframe["bb_lowerband"])
                    & (dataframe["volume"] > 0)
                ),
                "enter_long",
            ] = 1

        return dataframe

    # --- Exit logic stub (relies on ROI / stoploss) ----------------------

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe
'''


def generate_strategy_source_momentum(class_name: str) -> str:
    """Return a complete freqtrade strategy using EMA crossover + ATR volatility gate.

    Entry fires when the fast EMA crosses above the slow EMA and the current
    ATR(14) exceeds its rolling median over ``atr_window`` bars (trending-market
    filter).  All three period parameters are IntParameters in the ``buy`` space
    so freqtrade's hyperopt can tune them.
    """
    return f'''\
# Auto-generated by Strategy Lab — Auto-Quant Factory (Momentum / EMA Crossover Edition)
# Entry: EMA fast/slow crossover filtered by ATR > rolling median (trending markets only).
# Optimise with: freqtrade hyperopt --strategy Github_4tie_fortiesr__basic_generators__20260709_213639 --spaces buy roi stoploss

from freqtrade.strategy import IntParameter, IStrategy
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np


class Github_4tie_fortiesr__basic_generators__20260709_213639(IStrategy):
    INTERFACE_VERSION: int = 3

    minimal_roi = {{
        "0": 0.08,
        "30": 0.04,
        "60": 0.02,
        "120": 0,
    }}

    stoploss = -0.05
    timeframe = "5m"
    trailing_stop = False
    process_only_new_candles = True

    # ── Tunable EMA periods ───────────────────────────────────────────────────
    ema_fast   = IntParameter(5,  20, default=9,  space="buy", optimize=True)
    ema_slow   = IntParameter(15, 50, default=21, space="buy", optimize=True)

    # ── ATR rolling-median window (volatility gate) ───────────────────────────
    atr_window = IntParameter(10, 50, default=14, space="buy", optimize=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Dynamic EMA pair — recalculated for the current parameter values
        dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=self.ema_fast.value)
        dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=self.ema_slow.value)

        # ATR(14) and its rolling median for regime gating
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
        dataframe["atr_median"] = (
            dataframe["atr"]
            .rolling(window=self.atr_window.value, min_periods=1)
            .median()
        )

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # EMA crossover in a trending (high-ATR) market only
        dataframe.loc[
            (
                qtpylib.crossed_above(dataframe["ema_fast"], dataframe["ema_slow"])
                & (dataframe["atr"] > dataframe["atr_median"])
                & (dataframe["volume"] > 0)
            ),
            "enter_long",
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe
'''
