"""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 {class_name} --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 {class_name}(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 {class_name} --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 {class_name}(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 {class_name} --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 {class_name}(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 '''