""" AdaptiveTrendStrategy — daily-timeframe trend follower designed to beat BTC+ETH HODL. Edge sources: 1. Avoid bear drawdowns — exit on trend reversal, sit in stables 2. Capture full bull legs — wide trailing stop, no premature TP 3. Multi-asset rotation — only hold strongest of {BTC, ETH, SOL} 4. Risk-adjusted entries — confirmed trend + volume + ADX Why daily timeframe: - Eliminates noise (95% of 15m moves are noise) - Few trades → low fees - Captures real trends — most crypto returns come from <20% of days """ from datetime import datetime from functools import reduce import numpy as np import pandas as pd import talib.abstract as ta from pandas import DataFrame from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter class AdaptiveTrendStrategy(IStrategy): INTERFACE_VERSION = 3 timeframe = "1d" can_short = False process_only_new_candles = True # Loose stops — only major bear regime should exit stoploss = -0.50 # 50% — basically disabled, let exit signal handle it minimal_roi = {"0": 100} # 100% TP — disabled, hold winners trailing_stop = False # let it ride use_exit_signal = True exit_profit_only = False startup_candle_count: int = 250 # ---- Hyperopt-able tunables ---- ema_fast = IntParameter(20, 60, default=50, space="buy") ema_slow = IntParameter(150, 250, default=200, space="buy") adx_min = IntParameter(15, 35, default=20, space="buy") rsi_max_entry = IntParameter(60, 80, default=70, space="buy") vol_mult = DecimalParameter(1.0, 2.5, default=1.2, space="buy", decimals=2) def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame: df["ema_fast"] = ta.EMA(df, timeperiod=self.ema_fast.value) df["ema_slow"] = ta.EMA(df, timeperiod=self.ema_slow.value) df["rsi"] = ta.RSI(df, timeperiod=14) df["adx"] = ta.ADX(df, timeperiod=14) df["atr"] = ta.ATR(df, timeperiod=14) # Trend strength = how far price above slow EMA, normalized by ATR df["trend_str"] = (df["close"] - df["ema_slow"]) / df["atr"] # Volume regime df["vol_sma_20"] = ta.SMA(df["volume"], timeperiod=20) df["vol_ratio"] = df["volume"] / df["vol_sma_20"] # MACD for confirmation macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9) df["macd"] = macd["macd"] df["macd_signal"] = macd["macdsignal"] # Higher-high / higher-low structure (last 10 candles) df["hh_10"] = df["high"].rolling(10).max() df["ll_10"] = df["low"].rolling(10).min() df["hh_break"] = (df["close"] > df["hh_10"].shift(1)).astype(int) return df def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: # Two entry types stack together (max_open_trades>1 handles per-pair sizing): # 1. Regime entry: price reclaims 200 EMA (bear → bull transition) regime_in = ( (df["close"] > df["ema_slow"]) & (df["close"].shift(1) <= df["ema_slow"].shift(1)) ) # 2. Bull-pullback entry: while in bull regime, buy when price dips to fast EMA and bounces # Captures continuation moves without waiting for full bear-to-bull cycle bull_dip = ( (df["close"] > df["ema_slow"]) & # bull regime (df["ema_fast"] > df["ema_slow"]) & # uptrend confirmed (df["low"].shift(1) <= df["ema_fast"].shift(1) * 1.02) & # touched fast EMA recently (df["close"] > df["ema_fast"]) & # back above (df["close"] > df["close"].shift(1)) # green candle ) df.loc[regime_in, ["enter_long", "enter_tag"]] = (1, "regime_in") df.loc[bull_dip, ["enter_long", "enter_tag"]] = (1, "bull_dip") return df def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: # Exit only on confirmed regime break — close below 200 EMA # Holds through bull pullbacks, exits sustained bear df.loc[ df["close"] < df["ema_slow"], "exit_long" ] = 1 return df