ETH-optimized momentum trading strategy for ETH/USD on Kraken.
Timeframe
1h
Direction
Long Only
Stoploss
-3.0%
Trailing Stop
No
ROI
0m: 9.0%, 30m: 7.0%, 60m: 5.0%, 120m: 1.0%
Interface Version
3
Startup Candles
N/A
Indicators
7
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# 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