# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # flake8: noqa: F401 # isort: skip_file # --- Do not remove these libs --- import numpy as np import pandas as pd from pandas import DataFrame from datetime import datetime from typing import Optional, Union from freqtrade.strategy import ( IStrategy, Trade, Order, PairLocks, informative, merge_informative_pair, stoploss_from_open, stoploss_from_absolute, ) # -------------------------------- # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib class ETHMomentumStrategy(IStrategy): """ ETH-optimized momentum trading strategy for ETH/USD on Kraken. Designed for Ethereum's 15.63% volatility and explosive move patterns. Uses 1h primary timeframe with 4h trend context for momentum plays. Target: 5-8% profit moves, 60-70% win rate Based on research from Strategy Analysis Report. """ # Strategy interface version INTERFACE_VERSION = 3 # ETH-optimized timeframe (1h primary for momentum) timeframe = '1h' # Use 4h for trend context informative_timeframe = '4h' # Can this strategy go short? can_short = False # ETH-specific ROI targeting 5-8% moves (higher volatility) minimal_roi = { "0": 0.09, # 9% aggressive target (ETH's explosive potential) "30": 0.07, # 7% after 30 minutes (momentum target) "60": 0.05, # 5% after 1 hour (minimum for ETH) "120": 0.01 # 1% after 2 hours (safety exit) } # ETH-optimized stoploss (wider due to higher volatility) stoploss = -0.03 # 3% maximum loss (ETH's bigger swings) # Trailing stoploss for momentum plays trailing_stop = False # ETH-specific parameters # Bollinger Band breakout parameters bb_period = 20 bb_std = 2 bb_breakout_threshold = 0.02 # 2% above upper band # RSI parameters for momentum rsi_period = 14 rsi_momentum_threshold = 50 # Higher for momentum entry rsi_exit_threshold = 75 # Higher exit for ETH volatility # Volume surge parameters (key for ETH momentum) volume_surge_multiplier = 2.0 # 2x average volume volume_sma_period = 20 # MACD parameters for momentum confirmation macd_fast = 12 macd_slow = 26 macd_signal = 9 def informative_pairs(self): """ Define additional informative pairs for 4h trend context """ pairs = self.dp.current_whitelist() informative_pairs = [] for pair in pairs: informative_pairs.append((pair, self.informative_timeframe)) return informative_pairs def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Populate indicators optimized for ETH momentum trading """ # Bollinger Bands (primary for breakout detection) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=self.bb_period, stds=self.bb_std) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe['bb_percent'] = ( (dataframe['close'] - dataframe['bb_lowerband']) / (dataframe['bb_upperband'] - dataframe['bb_lowerband']) ) dataframe['bb_width'] = ( (dataframe['bb_upperband'] - dataframe['bb_lowerband']) / dataframe['bb_middleband'] ) # Bollinger Band breakout signals dataframe['bb_breakout_up'] = dataframe['close'] > (dataframe['bb_upperband'] * (1 + self.bb_breakout_threshold)) dataframe['bb_squeeze'] = dataframe['bb_width'] < 0.03 # Low volatility squeeze # RSI for momentum dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period) dataframe['rsi_momentum'] = dataframe['rsi'] > self.rsi_momentum_threshold # MACD for momentum confirmation macd = ta.MACD(dataframe, fastperiod=self.macd_fast, slowperiod=self.macd_slow, signalperiod=self.macd_signal) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] dataframe['macd_momentum'] = (dataframe['macd'] > dataframe['macdsignal']) & (dataframe['macdhist'] > 0) # Volume surge detection (critical for ETH momentum) dataframe['volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=self.volume_sma_period) dataframe['volume_surge'] = dataframe['volume'] > (dataframe['volume_sma'] * self.volume_surge_multiplier) dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma'] # EMA for trend direction dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=12) dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=26) dataframe['ema_trend'] = dataframe['ema_fast'] > dataframe['ema_slow'] # ATR for volatility measurement dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) dataframe['atr_high'] = dataframe['atr'] > dataframe['atr'].rolling(window=20).mean() * 1.2 # Momentum oscillator dataframe['momentum'] = ta.MOM(dataframe, timeperiod=10) dataframe['momentum_positive'] = dataframe['momentum'] > 0 # Price velocity (rate of change) dataframe['roc'] = ta.ROC(dataframe, timeperiod=5) dataframe['roc_strong'] = dataframe['roc'] > 2 # 2% price velocity return dataframe @informative('4h') def populate_indicators_4h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ 4h timeframe indicators for overall trend context """ # Trend context from 4h dataframe['ema_trend_4h'] = ta.EMA(dataframe, timeperiod=20) > ta.EMA(dataframe, timeperiod=50) dataframe['rsi_4h'] = ta.RSI(dataframe, timeperiod=14) dataframe['macd_4h'] = ta.MACD(dataframe)['macd'] dataframe['macdsignal_4h'] = ta.MACD(dataframe)['macdsignal'] dataframe['trend_strength_4h'] = dataframe['macd_4h'] > dataframe['macdsignal_4h'] # 4h volume context dataframe['volume_trend_4h'] = dataframe['volume'] > ta.SMA(dataframe['volume'], timeperiod=10) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ ETH-optimized entry conditions for momentum breakouts (5-8% moves) Entry Logic: 1. Bollinger Band breakout with volume surge 2. RSI momentum confirmation 3. MACD bullish momentum 4. 4h trend alignment 5. High volatility environment (ATR) 6. Strong price velocity """ conditions = [] # Primary momentum signal: BB breakout with volume conditions.append( (dataframe['bb_breakout_up'] | (dataframe['bb_percent'] > 0.8)) & dataframe['volume_surge'] ) # RSI momentum confirmation (not oversold, but building momentum) conditions.append( dataframe['rsi_momentum'] & (dataframe['rsi'] < 80) # Not extremely overbought ) # MACD momentum confirmation conditions.append(dataframe['macd_momentum']) # 4h trend context (don't fight the trend) conditions.append(dataframe['ema_trend_4h']) conditions.append(dataframe['trend_strength_4h']) # High volatility environment (momentum needs volatility) conditions.append(dataframe['atr_high']) # EMA trend alignment on 1h conditions.append(dataframe['ema_trend']) # Strong momentum indicators conditions.append(dataframe['momentum_positive']) conditions.append(dataframe['roc_strong']) # Volume confirmation (institutional/whale activity) conditions.append(dataframe['volume_ratio'] > 1.5) # Not in a BB squeeze (need volatility expansion) conditions.append(~dataframe['bb_squeeze']) if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), 'enter_long'] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ ETH-optimized exit conditions for momentum exhaustion Exit Logic: 1. RSI extremely overbought 2. MACD momentum declining 3. Volume declining (momentum fading) 4. Bollinger Band extreme stretch 5. Momentum indicators weakening """ conditions = [] # RSI extremely overbought (ETH-specific high threshold) conditions.append(dataframe['rsi'] > self.rsi_exit_threshold) # MACD momentum declining conditions.append( (dataframe['macd'] < dataframe['macdsignal']) | (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) # Declining histogram ) # Volume declining (momentum fading) conditions.append(dataframe['volume_ratio'] < 0.7) # Bollinger Band extreme stretch (overextended) conditions.append(dataframe['bb_percent'] > 1.1) # Above upper band # Momentum weakening conditions.append(~dataframe['momentum_positive']) # Price velocity declining conditions.append(dataframe['roc'] < 0) if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1 return dataframe def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: """ ETH-optimized stoploss logic (wider for volatility) """ return self.stoploss def custom_sell(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[Union[str, bool]]: """ ETH-optimized profit-taking logic Scale-out approach: 5%, 7%, 9% targets (higher for ETH volatility) """ # Scale out at 9% (aggressive target for ETH momentum) if current_profit >= 0.09: return 'eth_profit_9pct' # Scale out at 7% (momentum target) if current_profit >= 0.07: return 'eth_profit_7pct' # Consider exit at 5% minimum if momentum weakening if current_profit >= 0.05: return None # Let normal exit signals handle this return None def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float: """ No leverage for spot trading on Kraken """ return 1.0 def reduce(function, iterable, initializer=None): """Python reduce function for combining conditions""" it = iter(iterable) if initializer is None: value = next(it) else: value = initializer for element in it: value = function(value, element) return value