# source: https://raw.githubusercontent.com/4mrm4/crypto-agent-bot/a62f63278fbb7d8edebc6b59a1bbe0d9014294c0/backtesting/strategy_templates.py
# directory_url: https://github.com/4mrm4/crypto-agent-bot/blob/master/backtesting/
# User: 4mrm4
# Repository: crypto-agent-bot
# --------------------"""Strategy template snippets injected into temporary Freqtrade strategy files.

Contains the string templates for all strategy types (SMA crossover, MACD,
RSI, Bollinger Bands, combined signals, momentum, breakout, mean reversion,
volatility squeeze, sentiment-driven, multi-timeframe) along with the
STRATEGY_REGISTRY dict that maps strategy type names to their code snippets
and default parameters.
"""

from typing import Any, Dict


STRATEGY_TEMPLATE = '''"""
Auto-generated strategy by crypto_agent_bot.
Do not edit manually — generated on $timestamp.
"""
from freqtrade.strategy import IStrategy, IntParameter
import pandas as pd
import talib.abstract as ta


class Github_4mrm4_crypto_agent_bot__strategy_templates__20260616_234037(IStrategy):
    # --- User-defined parameters (set by agent) ---
    timeframe = "$timeframe"
    minimal_roi = $minimal_roi
    stoploss = $stoploss
    trailing_stop = $trailing_stop
    trailing_stop_positive = $trailing_stop_positive
    trailing_stop_positive_offset = $trailing_stop_positive_offset
    trailing_only_offset_is_reached = $trailing_only_offset_is_reached
    startup_candle_count = $startup_candle_count
    process_only_new_candles = True
    use_exit_signal = True
    can_short = False

    # --- Indicator parameters ---
$indicator_params_block
    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        # Coerce string-typed columns upfront (covers PyArrow backend which stores
        # strings as pd.ArrowDtype(pa.string()), not caught by simple 'string' check)
        import pandas.api.types as ptypes
        for col in dataframe.columns:
            if ptypes.is_string_dtype(dataframe[col]):
                dataframe[col] = pd.to_numeric(dataframe[col], errors='coerce')
        $indicator_code
        # Second pass: catch any new columns created by indicator code
        for col in dataframe.columns:
            if ptypes.is_string_dtype(dataframe[col]):
                dataframe[col] = pd.to_numeric(dataframe[col], errors='coerce')
        return dataframe

    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        dataframe.loc[
            (
                $entry_condition
            ),
            "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        dataframe.loc[
            (
                $exit_condition
            ),
            "exit_long"] = 1
        return dataframe
'''


# ── Default SMA crossover indicator/entry/exit snippets ──

SMA_CROSSOVER_INDICATOR = """
        dataframe['fast_ma'] = ta.SMA(dataframe, timeperiod=self.fast_ma.value)
        dataframe['slow_ma'] = ta.SMA(dataframe, timeperiod=self.slow_ma.value)
"""

SMA_CROSSOVER_ENTRY = """
        (dataframe['fast_ma'].shift(1) <= dataframe['slow_ma'].shift(1)) &
        (dataframe['fast_ma'] > dataframe['slow_ma'])
"""

SMA_CROSSOVER_EXIT = """
        (dataframe['fast_ma'].shift(1) >= dataframe['slow_ma'].shift(1)) &
        (dataframe['fast_ma'] < dataframe['slow_ma'])
"""

# ── MACD Crossover snippets ──

MACD_CROSSOVER_INDICATOR = """
        macd_data = ta.MACD(
            dataframe,
            fastperiod=self.macd_fast.value,
            slowperiod=self.macd_slow.value,
            signalperiod=self.macd_signal.value,
        )
        dataframe['macd'] = macd_data['macd'].astype(float)
        dataframe['macdsignal'] = macd_data['macdsignal'].astype(float)
        dataframe['macd_hist'] = (dataframe['macd'] - dataframe['macdsignal']).astype(float)
"""

MACD_CROSSOVER_ENTRY = """
        (dataframe['macd_hist'].shift(1) <= 0) & (dataframe['macd_hist'] > 0)
"""

MACD_CROSSOVER_EXIT = """
        (dataframe['macd_hist'].shift(1) >= 0) & (dataframe['macd_hist'] < 0)
"""

# ── RSI Oversold/Overbought snippets ──

RSI_INDICATOR = """
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
"""

RSI_OVERSOLD_ENTRY = """
        (dataframe['rsi'] < self.rsi_buy_threshold.value) & (dataframe['rsi'].shift(1) >= self.rsi_buy_threshold.value)
"""

RSI_OVERSOLD_EXIT = """
        (dataframe['rsi'] > self.rsi_sell_threshold.value) & (dataframe['rsi'].shift(1) <= self.rsi_sell_threshold.value)
"""

# ── Bollinger Bands snippets ──

BB_INDICATOR = """
        upper, middle, lower = ta.BBANDS(
            dataframe['close'].astype(float),
            timeperiod=self.bb_period.value,
            nbdevup=2.0,
            nbdevdn=2.0,
        )
        dataframe['bb_upper'] = upper.astype(float)
        dataframe['bb_middle'] = middle.astype(float)
        dataframe['bb_lower'] = lower.astype(float)
"""

BB_ENTRY = """
        (dataframe['close'] < dataframe['bb_lower']) & (dataframe['close'].shift(1) >= dataframe['bb_lower'].shift(1))
"""

BB_EXIT = """
        (dataframe['close'] > dataframe['bb_upper']) & (dataframe['close'].shift(1) <= dataframe['bb_upper'].shift(1))
"""

# ── Combined SMA + RSI filter snippets ──

SMA_RSI_INDICATOR = """
        dataframe['fast_ma'] = ta.SMA(dataframe, timeperiod=self.fast_ma.value)
        dataframe['slow_ma'] = ta.SMA(dataframe, timeperiod=self.slow_ma.value)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
"""

SMA_RSI_ENTRY = """
        (dataframe['fast_ma'].shift(1) <= dataframe['slow_ma'].shift(1)) &
        (dataframe['fast_ma'] > dataframe['slow_ma']) &
        (dataframe['rsi'] > 30) & (dataframe['rsi'] < 70)
"""

SMA_RSI_EXIT = """
        (dataframe['fast_ma'].shift(1) >= dataframe['slow_ma'].shift(1)) &
        (dataframe['fast_ma'] < dataframe['slow_ma'])
"""

# ── Strategy registry: maps type to its snippets and defaults ──

STRATEGY_REGISTRY: Dict[str, Dict[str, Any]] = {
    "sma_crossover": {
        "indicator_code": SMA_CROSSOVER_INDICATOR,
        "entry_condition": SMA_CROSSOVER_ENTRY,
        "exit_condition": SMA_CROSSOVER_EXIT,
        "indicator_params_block": """
    fast_ma = IntParameter(5, 50, default=$fast_ma, space="buy")
    slow_ma = IntParameter(20, 200, default=$slow_ma, space="buy")
""",
        "default_params": {"fast_ma": 10, "slow_ma": 30, "startup_candle_count": 30},
    },
    "macd_crossover": {
        "indicator_code": MACD_CROSSOVER_INDICATOR,
        "entry_condition": MACD_CROSSOVER_ENTRY,
        "exit_condition": MACD_CROSSOVER_EXIT,
        "indicator_params_block": """
    macd_fast = IntParameter(8, 20, default=$macd_fast, space="buy")
    macd_slow = IntParameter(20, 40, default=$macd_slow, space="buy")
    macd_signal = IntParameter(6, 14, default=$macd_signal, space="buy")
""",
        "default_params": {"macd_fast": 12, "macd_slow": 26, "macd_signal": 9, "startup_candle_count": 33},
    },
    "rsi_oversold": {
        "indicator_code": RSI_INDICATOR,
        "entry_condition": RSI_OVERSOLD_ENTRY,
        "exit_condition": RSI_OVERSOLD_EXIT,
        "indicator_params_block": """
    rsi_period = IntParameter(10, 21, default=$rsi_period, space="buy")
    rsi_buy_threshold = IntParameter(25, 35, default=$rsi_buy_threshold, space="buy")
    rsi_sell_threshold = IntParameter(65, 80, default=$rsi_sell_threshold, space="sell")
""",
        "default_params": {"rsi_period": 14, "rsi_buy_threshold": 30, "rsi_sell_threshold": 70, "startup_candle_count": 20},
    },
    "bollinger_bands": {
        "indicator_code": BB_INDICATOR,
        "entry_condition": BB_ENTRY,
        "exit_condition": BB_EXIT,
        "indicator_params_block": """
    bb_period = IntParameter(15, 30, default=$bb_period, space="buy")
""",
        "default_params": {"bb_period": 20, "startup_candle_count": 26},
    },
    "combined_sma_rsi": {
        "indicator_code": SMA_RSI_INDICATOR,
        "entry_condition": SMA_RSI_ENTRY,
        "exit_condition": SMA_RSI_EXIT,
        "indicator_params_block": """
    fast_ma = IntParameter(5, 50, default=$fast_ma, space="buy")
    slow_ma = IntParameter(20, 200, default=$slow_ma, space="buy")
""",
        "default_params": {"fast_ma": 10, "slow_ma": 30, "startup_candle_count": 30},
    },
    "custom": {
        "indicator_code": "",
        "entry_condition": "",
        "exit_condition": "",
        "indicator_params_block": "",
        "default_params": {"startup_candle_count": 20},
    },

    "momentum": {
        "indicator_code": """
        dataframe['roc'] = ta.ROC(dataframe, timeperiod=10)
        dataframe['volume_ma'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
    """,
        "entry_condition": """
        (dataframe['roc'] > 2.0) &
        (dataframe['volume'] > dataframe['volume_ma'] * 1.5) &
        (dataframe['rsi'] > 50) & (dataframe['rsi'] < 75)
    """,
        "exit_condition": """
        (dataframe['roc'] < 0) | (dataframe['rsi'] > 75)
    """,
        "indicator_params_block": "",
        "default_params": {"startup_candle_count": 25},
    },

    "breakout": {
        "indicator_code": """
        dataframe['highest_high'] = dataframe['high'].rolling(20).max().shift(1)
        dataframe['volume_ma'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
    """,
        "entry_condition": """
        (dataframe['close'] > dataframe['highest_high']) &
        (dataframe['volume'] > dataframe['volume_ma'] * 1.3)
    """,
        "exit_condition": """
        (dataframe['close'] < dataframe['highest_high'] - dataframe['atr'] * 2)
    """,
        "indicator_params_block": "",
        "default_params": {"startup_candle_count": 25},
    },

    "mean_reversion": {
        "indicator_code": """
        bb_upper, bb_middle, bb_lower = ta.BBANDS(
            dataframe['close'], timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe['bb_upper'] = bb_upper.astype(float)
        dataframe['bb_middle'] = bb_middle.astype(float)
        dataframe['bb_lower'] = bb_lower.astype(float)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['distance_from_mean'] = (dataframe['close'] - dataframe['bb_middle']) / dataframe['bb_middle']
    """,
        "entry_condition": """
        (dataframe['close'] < dataframe['bb_lower']) &
        (dataframe['rsi'] < 35) &
        (dataframe['distance_from_mean'] < -0.02)
    """,
        "exit_condition": """
        (dataframe['close'] > dataframe['bb_middle']) | (dataframe['rsi'] > 60)
    """,
        "indicator_params_block": "",
        "default_params": {"startup_candle_count": 25},
    },

    "volatility_squeeze": {
        "indicator_code": """
        bb_upper, bb_middle, bb_lower = ta.BBANDS(
            dataframe['close'], timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe['bb_upper'] = bb_upper.astype(float)
        dataframe['bb_middle'] = bb_middle.astype(float)
        dataframe['bb_lower'] = bb_lower.astype(float)
        dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle']
        dataframe['bb_width_min'] = dataframe['bb_width'].rolling(120).min()
        dataframe['macd'], dataframe['macdsignal'], _ = [
            x.astype(float) for x in ta.MACD(dataframe['close'].astype(float))]
    """,
        "entry_condition": """
        (dataframe['bb_width'] <= dataframe['bb_width_min'] * 1.05) &
        (dataframe['macd'] > dataframe['macdsignal'])
    """,
        "exit_condition": """
        (dataframe['bb_width'] > dataframe['bb_width_min'] * 3) |
        (dataframe['macd'] < dataframe['macdsignal'])
    """,
        "indicator_params_block": "",
        "default_params": {"startup_candle_count": 130},
    },

    "sentiment_driven": {
        "indicator_code": """
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
    """,
        "entry_condition": """
        (dataframe['rsi'] < 40) &
        (dataframe['close'] > dataframe['sma50'])
    """,
        "exit_condition": """
        (dataframe['rsi'] > 65) | (dataframe['close'] < dataframe['sma50'])
    """,
        "indicator_params_block": "",
        "default_params": {"startup_candle_count": 55},
    },

    "multi_timeframe": {
        "indicator_code": """
        # Primary timeframe indicators
        dataframe['fast_sma'] = ta.SMA(dataframe, timeperiod=self.fast_ma.value)
        dataframe['slow_sma'] = ta.SMA(dataframe, timeperiod=self.slow_ma.value)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
        # Higher timeframe proxies (longer SMAs on same data)
        dataframe['sma80'] = ta.SMA(dataframe, timeperiod=self.higher_tf_fast.value)
        dataframe['sma200'] = ta.SMA(dataframe, timeperiod=self.higher_tf_slow.value)
        dataframe['adx'] = ta.ADX(dataframe, timeperiod=self.adx_period.value)
    """,
        "entry_condition": """
        # Short-term signal: fast SMA crosses above slow SMA
        (dataframe['fast_sma'].shift(1) <= dataframe['slow_sma'].shift(1)) &
        (dataframe['fast_sma'] > dataframe['slow_sma']) &
        # Long-term confirmation: price above 200 SMA (higher timeframe proxy)
        (dataframe['close'] > dataframe['sma200']) &
        # Trend strength: ADX above threshold
        (dataframe['adx'] > self.adx_threshold.value) &
        # RSI not overbought/oversold
        (dataframe['rsi'] > self.rsi_oversold.value) & (dataframe['rsi'] < self.rsi_overbought.value)
    """,
        "exit_condition": """
        (dataframe['fast_sma'].shift(1) >= dataframe['slow_sma'].shift(1)) &
        (dataframe['fast_sma'] < dataframe['slow_sma']) |
        (dataframe['close'] < dataframe['sma200'])
    """,
        "indicator_params_block": """
    fast_ma = IntParameter(5, 50, default=$fast_ma, space="buy")
    slow_ma = IntParameter(20, 100, default=$slow_ma, space="buy")
    adx_period = IntParameter(7, 21, default=$adx_period, space="buy")
    adx_threshold = IntParameter(15, 40, default=$adx_threshold, space="buy")
    rsi_period = IntParameter(10, 21, default=$rsi_period, space="buy")
    rsi_oversold = IntParameter(30, 50, default=$rsi_oversold, space="buy")
    rsi_overbought = IntParameter(60, 80, default=$rsi_overbought, space="sell")
    higher_tf_fast = IntParameter(60, 120, default=$higher_tf_fast, space="buy")
    higher_tf_slow = IntParameter(150, 250, default=$higher_tf_slow, space="buy")
""",
        "default_params": {"fast_ma": 10, "slow_ma": 30, "adx_period": 14, "adx_threshold": 20, "rsi_period": 14, "rsi_oversold": 40, "rsi_overbought": 70, "higher_tf_fast": 80, "higher_tf_slow": 200, "startup_candle_count": 205},
    },

    "vwap_deviation": {
        "indicator_code": """
        typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
        dataframe['vwap'] = (typical_price * dataframe['volume']).rolling(self.vwap_period.value).sum() / dataframe['volume'].rolling(self.vwap_period.value).sum()
        dataframe['vwap_deviation'] = (dataframe['close'] - dataframe['vwap']) / dataframe['vwap'] * 100
    """,
        "entry_condition": """
        (dataframe['vwap_deviation'] < -self.deviation_threshold.value) &
        (dataframe['vwap_deviation'].shift(1) >= -self.deviation_threshold.value)
    """,
        "exit_condition": """
        (dataframe['vwap_deviation'] >= 0) |
        (dataframe['vwap_deviation'].shift(1) < 0)
    """,
        "indicator_params_block": """
    vwap_period = IntParameter(10, 30, default=$vwap_period, space="buy")
    deviation_threshold = IntParameter(5, 30, default=$deviation_threshold, space="buy")
""",
        "default_params": {"vwap_period": 20, "deviation_threshold": 15, "startup_candle_count": 25},
    },

    "ema_ribbon": {
        "indicator_code": """
        # Generate EMA list from ribbon params (e.g. 3,6,9,12,18,24)
        ema_periods = list(range(self.ema_min.value, int(self.ema_max.value) + 1, int(self.ema_step.value)))
        for p in ema_periods:
            dataframe[f'ema_{p}'] = ta.EMA(dataframe, timeperiod=p)
        # Store the list for entry/exit conditions
        dataframe['_ema_shortest'] = dataframe[f'ema_{ema_periods[0]}']
        dataframe['_ema_longest'] = dataframe[f'ema_{ema_periods[-1]}']
    """,
        "entry_condition": """
        # All EMAs strictly aligned bullish (shortest > longest, each above next)
        (dataframe['_ema_shortest'] > dataframe['_ema_longest']) &
        (dataframe['_ema_shortest'].shift(1) <= dataframe['_ema_longest'].shift(1))
    """,
        "exit_condition": """
        (dataframe['_ema_shortest'] < dataframe['_ema_longest']) &
        (dataframe['_ema_shortest'].shift(1) >= dataframe['_ema_longest'].shift(1))
    """,
        "indicator_params_block": """
    ema_min = IntParameter(2, 10, default=$ema_min, space="buy")
    ema_max = IntParameter(15, 50, default=$ema_max, space="buy")
    ema_step = IntParameter(2, 10, default=$ema_step, space="buy")
""",
        "default_params": {"ema_min": 3, "ema_max": 24, "ema_step": 3, "startup_candle_count": 30},
    },

    "stoch_rsi": {
        "indicator_code": """
        rsi = ta.RSI(dataframe, timeperiod=self.stoch_rsi_period.value)
        rsi_min = rsi.rolling(self.stoch_rsi_period.value).min()
        rsi_max = rsi.rolling(self.stoch_rsi_period.value).max()
        stoch_rsi_raw = ((rsi - rsi_min) / (rsi_max - rsi_min).replace(0, float('nan'))) * 100
        dataframe['stoch_rsi_k'] = stoch_rsi_raw.rolling(self.stoch_rsi_k.value).mean()
        dataframe['stoch_rsi_d'] = dataframe['stoch_rsi_k'].rolling(self.stoch_rsi_d.value).mean()
    """,
        "entry_condition": """
        (dataframe['stoch_rsi_k'] < self.oversold.value) &
        (dataframe['stoch_rsi_k'] > dataframe['stoch_rsi_d']) &
        (dataframe['stoch_rsi_k'].shift(1) <= dataframe['stoch_rsi_d'].shift(1))
    """,
        "exit_condition": """
        (dataframe['stoch_rsi_k'] > self.overbought.value) &
        (dataframe['stoch_rsi_k'] < dataframe['stoch_rsi_d']) &
        (dataframe['stoch_rsi_k'].shift(1) >= dataframe['stoch_rsi_d'].shift(1))
    """,
        "indicator_params_block": """
    stoch_rsi_period = IntParameter(7, 21, default=$stoch_rsi_period, space="buy")
    stoch_rsi_k = IntParameter(2, 5, default=$stoch_rsi_k, space="buy")
    stoch_rsi_d = IntParameter(2, 5, default=$stoch_rsi_d, space="buy")
    oversold = IntParameter(10, 30, default=$oversold, space="buy")
    overbought = IntParameter(70, 90, default=$overbought, space="sell")
""",
        "default_params": {"stoch_rsi_period": 14, "stoch_rsi_k": 3, "stoch_rsi_d": 3, "oversold": 20, "overbought": 80, "startup_candle_count": 20},
    },

    "adx_filter": {
        "indicator_code": """
        adx_data = ta.ADX(dataframe, timeperiod=self.adx_period.value)
        dataframe['adx'] = adx_data['adx'].astype(float)
        dataframe['plus_di'] = adx_data['plus_di'].astype(float)
        dataframe['minus_di'] = adx_data['minus_di'].astype(float)
    """,
        "entry_condition": """
        (dataframe['adx'] > self.adx_threshold.value) &
        (dataframe['plus_di'] > dataframe['minus_di'])
    """,
        "exit_condition": """
        (dataframe['adx'] < 20) |
        (dataframe['plus_di'] < dataframe['minus_di'])
    """,
        "indicator_params_block": """
    adx_period = IntParameter(7, 21, default=$adx_period, space="buy")
    adx_threshold = IntParameter(15, 40, default=$adx_threshold, space="buy")
""",
        "default_params": {"adx_period": 14, "adx_threshold": 25, "startup_candle_count": 25},
    },
}
