Enhanced EMA RSI Multi-Timeframe Strategy with FreqAI Integration
Timeframe
5m
Direction
Long Only
Stoploss
N/A
Trailing Stop
Yes
ROI
0m: 10.0%, 30m: 5.0%, 60m: 2.0%, 120m: 1.0%
Interface Version
N/A
Startup Candles
200
Indicators
3
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
"""
Enhanced EMA RSI Multi-Timeframe Strategy with FreqAI Integration
================================================================
This strategy combines EMA and RSI indicators across multiple timeframes
with FreqAI model predictions for enhanced entry/exit signals.
Features:
- Multi-timeframe analysis (5m, 15m, 1h, 4h)
- FreqAI model integration for prediction-based signals
- Comprehensive hyperopt spaces for all parameters
- Advanced risk management with dynamic stoploss
- Ensemble model support for improved accuracy
"""
import logging
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
from freqtrade.strategy import IStrategy, merge_informative_pair, IntParameter, DecimalParameter, CategoricalParameter
from functools import reduce
from freqtrade.persistence import Trade
from pandas import DataFrame
import talib.abstract as ta
from scipy import stats
logger = logging.getLogger(__name__)
class EMARSI_MTF_Enhanced(IStrategy):
"""
Enhanced EMA RSI Multi-Timeframe Strategy with FreqAI Integration
This strategy uses EMA and RSI indicators across multiple timeframes
combined with FreqAI model predictions for optimal entry and exit signals.
FreqAI Integration:
- Uses model predictions to enhance entry/exit decisions
- Supports ensemble models for improved accuracy
- Dynamic weight adjustment based on model performance
- Real-time model retraining and validation
"""
# Strategy Configuration
timeframe = '5m'
informative_timeframes = ['15m', '1h', '4h']
startup_candle_count = 200
process_only_new_candles = True
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
can_short = False
# Risk Management
use_custom_stoploss = True
trailing_stop = True
position_adjustment_enable = True
# ROI Table - Dynamic based on hyperopt parameters
minimal_roi = {
"0": 0.10,
"30": 0.05,
"60": 0.02,
"120": 0.01,
"240": 0.005
}
# FreqAI Configuration
freqai_enabled = True # Re-enabled for FreqAI integration
# FreqAI Model Selection Parameters
is_optimize_freqai_model = True
freqai_model_type = CategoricalParameter(
["AdvancedEnsembleRegressor", "BlendingRegressor", "StackingRegressor", "VotingRegressor"],
default="AdvancedEnsembleRegressor",
space="buy",
optimize=is_optimize_freqai_model
)
# Hyperopt Parameters - Entry
is_optimize_entry = True
ema_fast = IntParameter(5, 20, default=20, space="buy", optimize=is_optimize_entry)
ema_slow = IntParameter(20, 50, default=42, space="buy", optimize=is_optimize_entry)
rsi_buy = IntParameter(20, 50, default=25, space="buy", optimize=is_optimize_entry)
rsi_buy_15m = IntParameter(30, 60, default=39, space="buy", optimize=is_optimize_entry)
rsi_buy_1h = IntParameter(35, 65, default=51, space="buy", optimize=is_optimize_entry)
rsi_buy_4h = IntParameter(40, 70, default=53, space="buy", optimize=is_optimize_entry)
# Volume and Momentum Parameters
volume_threshold = DecimalParameter(0.8, 2.0, default=0.967, space="buy", optimize=is_optimize_entry)
momentum_threshold = DecimalParameter(0.1, 0.5, default=0.172, space="buy", optimize=is_optimize_entry)
# FreqAI Prediction Parameters
is_optimize_freqai = True
freqai_prediction_threshold = DecimalParameter(0.3, 0.8, default=0.31, space="buy", optimize=is_optimize_freqai)
freqai_weight = DecimalParameter(0.1, 0.9, default=0.876, space="buy", optimize=is_optimize_freqai)
freqai_ensemble_threshold = DecimalParameter(0.4, 0.9, default=0.478, space="buy", optimize=is_optimize_freqai)
freqai_confidence_threshold = DecimalParameter(0.5, 0.95, default=0.769, space="buy", optimize=is_optimize_freqai)
# Advanced Model Parameters
smart_money_weight = DecimalParameter(0.1, 0.5, default=0.3, space="buy", optimize=is_optimize_freqai)
ensemble_weight = DecimalParameter(0.2, 0.6, default=0.4, space="buy", optimize=is_optimize_freqai)
mtf_weight = DecimalParameter(0.1, 0.5, default=0.3, space="buy", optimize=is_optimize_freqai)
use_adaptive_weights = CategoricalParameter([True, False], default=True, space="buy", optimize=is_optimize_freqai)
weight_update_frequency = IntParameter(12, 48, default=24, space="buy", optimize=is_optimize_freqai)
min_confidence_threshold = DecimalParameter(0.4, 0.8, default=0.6, space="buy", optimize=is_optimize_freqai)
# Hyperopt Parameters - Exit
is_optimize_exit = True
rsi_sell = IntParameter(50, 80, default=64, space="sell", optimize=is_optimize_exit)
rsi_sell_15m = IntParameter(60, 85, default=80, space="sell", optimize=is_optimize_exit)
rsi_sell_1h = IntParameter(65, 90, default=70, space="sell", optimize=is_optimize_exit)
rsi_sell_4h = IntParameter(70, 95, default=83, space="sell", optimize=is_optimize_exit)
# Profit Taking Parameters
profit_take_threshold = DecimalParameter(0.01, 0.08, default=0.036, space="sell", optimize=is_optimize_exit)
profit_take_ratio = DecimalParameter(0.3, 0.8, default=0.446, space="sell", optimize=is_optimize_exit)
# Stoploss Parameters
is_optimize_stoploss = True
_stoploss = DecimalParameter(-0.5, -0.01, default=-0.318, space="stoploss", optimize=is_optimize_stoploss)
@property
def stoploss(self):
return self._stoploss.value
@stoploss.setter
def stoploss(self, value):
self._stoploss.value = value
# Trailing Stop Parameters
is_optimize_trailing = True
_trailing_stop_positive = DecimalParameter(0.005, 0.05, default=0.243, space="trailing", optimize=is_optimize_trailing)
_trailing_stop_positive_offset = DecimalParameter(0.01, 0.1, default=0.288, space="trailing", optimize=is_optimize_trailing)
_trailing_only_offset_is_reached = CategoricalParameter([True, False], default=False, space="trailing", optimize=is_optimize_trailing)
@property
def trailing_stop_positive(self):
return self._trailing_stop_positive.value
@trailing_stop_positive.setter
def trailing_stop_positive(self, value):
self._trailing_stop_positive.value = value
@property
def trailing_stop_positive_offset(self):
return self._trailing_stop_positive_offset.value
@trailing_stop_positive_offset.setter
def trailing_stop_positive_offset(self, value):
self._trailing_stop_positive_offset.value = value
@property
def trailing_only_offset_is_reached(self):
return self._trailing_only_offset_is_reached.value
@trailing_only_offset_is_reached.setter
def trailing_only_offset_is_reached(self, value):
self._trailing_only_offset_is_reached.value = value
# ROI Parameters
is_optimize_roi = True
roi_0 = DecimalParameter(0.01, 0.40, default=0.255, space="roi", optimize=is_optimize_roi)
roi_1 = DecimalParameter(0.05, 0.25, default=0.151, space="roi", optimize=is_optimize_roi)
roi_2 = DecimalParameter(0.01, 0.15, default=0.061, space="roi", optimize=is_optimize_roi)
roi_3 = DecimalParameter(0, 0.05, default=0.0, space="roi", optimize=is_optimize_roi)
roi_t1 = IntParameter(100, 500, default=116, space="roi", optimize=is_optimize_roi)
roi_t2 = IntParameter(500, 1500, default=155, space="roi", optimize=is_optimize_roi)
roi_t3 = IntParameter(1500, 3000, default=241, space="roi", optimize=is_optimize_roi)
def __init__(self, config: dict) -> None:
super().__init__(config)
self._last_stoploss_times = {}
self._last_candle_times = {}
self.freqai_predictions = {}
# Convert hyperopt parameters to actual values for backtesting
self._convert_hyperopt_params()
logger.info("✅ EMARSI_MTF_Enhanced initialized with FreqAI integration")
def _convert_hyperopt_params(self):
"""Convert hyperopt parameters to their actual values for backtesting"""
# Get all attributes of the strategy
for attr_name in dir(self):
attr = getattr(self, attr_name)
# Check if it's a hyperopt parameter (has 'value' and 'default' attributes)
if hasattr(attr, 'value') and hasattr(attr, 'default'):
# Use the current value if available, otherwise use default
if hasattr(attr, 'value') and attr.value is not None:
setattr(self, attr_name, attr.value)
else:
setattr(self, attr_name, attr.default)
# Protection Parameters
is_optimize_protection = True
use_timeframe_protection = CategoricalParameter([True, False], default=False, space="protection", optimize=is_optimize_protection)
cooldown_lookback = IntParameter(2, 48, default=29, space="protection", optimize=is_optimize_protection)
# Technical Indicator Parameters
is_optimize_indicators = True
rsi_period = IntParameter(10, 21, default=13, space="buy", optimize=is_optimize_indicators)
atr_period = IntParameter(10, 21, default=14, space="buy", optimize=is_optimize_indicators)
volume_ma_period = IntParameter(10, 50, default=35, space="buy", optimize=is_optimize_indicators)
momentum_period = IntParameter(3, 10, default=4, space="buy", optimize=is_optimize_indicators)
momentum_ma_period = IntParameter(5, 20, default=18, space="buy", optimize=is_optimize_indicators)
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
Set FreqAI targets for prediction
"""
# Create target based on future price movement
dataframe['&-s_target'] = self.create_target_T(dataframe)
return dataframe
def create_target_T(self, dataframe: DataFrame) -> pd.Series:
"""
Create target for FreqAI prediction
"""
# Target: Future price movement (24 candles ahead)
target = (dataframe['close'].shift(-24) - dataframe['close']) / dataframe['close']
return target
def informative_pairs(self):
"""Define additional informative pair/interval combinations"""
pairs = self.dp.current_whitelist()
informative_pairs = [(pair, tf) for pair in pairs for tf in self.informative_timeframes]
return informative_pairs
def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
Populate indicators for the given dataframe
"""
# Get parameter values safely
ema_fast_val = self.ema_fast.value if hasattr(self.ema_fast, 'value') else self.ema_fast
ema_slow_val = self.ema_slow.value if hasattr(self.ema_slow, 'value') else self.ema_slow
rsi_period_val = self.rsi_period.value if hasattr(self.rsi_period, 'value') else self.rsi_period
volume_ma_period_val = self.volume_ma_period.value if hasattr(self.volume_ma_period, 'value') else self.volume_ma_period
momentum_period_val = self.momentum_period.value if hasattr(self.momentum_period, 'value') else self.momentum_period
momentum_ma_period_val = self.momentum_ma_period.value if hasattr(self.momentum_ma_period, 'value') else self.momentum_ma_period
atr_period_val = self.atr_period.value if hasattr(self.atr_period, 'value') else self.atr_period
# Use parameter values
df['ema_fast'] = df['close'].ewm(span=ema_fast_val, adjust=False).mean()
df['ema_slow'] = df['close'].ewm(span=ema_slow_val, adjust=False).mean()
df['rsi'] = ta.RSI(df, timeperiod=rsi_period_val)
# Volume indicators
df['volume_mean'] = df['volume'].rolling(window=volume_ma_period_val).mean()
df['volume_ratio'] = df['volume'] / df['volume_mean']
# Momentum indicators
df['momentum'] = df['close'].pct_change(periods=momentum_period_val)
df['momentum_ma'] = df['momentum'].rolling(window=momentum_ma_period_val).mean()
# Volatility indicators
df['atr'] = ta.ATR(df, timeperiod=atr_period_val)
df['volatility'] = df['atr'] / df['close']
# Trend indicators
df['trend_strength'] = abs(df['ema_fast'] - df['ema_slow']) / df['close']
# Multi-timeframe indicators
for tf in self.informative_timeframes:
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=tf)
# EMA indicators
informative[f'ema_fast_{tf}'] = informative['close'].ewm(span=ema_fast_val, adjust=False).mean()
informative[f'ema_slow_{tf}'] = informative['close'].ewm(span=ema_slow_val, adjust=False).mean()
# RSI indicators
informative[f'rsi_{tf}'] = ta.RSI(informative, timeperiod=rsi_period_val)
# Volume indicators
informative[f'volume_mean_{tf}'] = informative['volume'].rolling(window=volume_ma_period_val).mean()
informative[f'volume_ratio_{tf}'] = informative['volume'] / informative[f'volume_mean_{tf}']
# Momentum indicators
informative[f'momentum_{tf}'] = informative['close'].pct_change(periods=momentum_period_val)
informative[f'momentum_ma_{tf}'] = informative[f'momentum_{tf}'].rolling(window=momentum_ma_period_val).mean()
# Merge informative dataframe
df = merge_informative_pair(df, informative, self.timeframe, tf, ffill=True)
# FreqAI feature engineering
if self.freqai_enabled:
df = self.add_freqai_features(df)
return df
def add_freqai_features(self, df: DataFrame) -> DataFrame:
"""
Add advanced features for FreqAI model training
"""
# Price-based features
df['price_change'] = df['close'].pct_change()
df['price_change_2'] = df['close'].pct_change(periods=2)
df['price_change_5'] = df['close'].pct_change(periods=5)
df['price_change_10'] = df['close'].pct_change(periods=10)
df['price_change_20'] = df['close'].pct_change(periods=20)
# Log returns for better statistical properties
df['log_return'] = np.log(df['close'] / df['close'].shift(1))
df['log_return_5'] = np.log(df['close'] / df['close'].shift(5))
df['log_return_20'] = np.log(df['close'] / df['close'].shift(20))
# Technical indicator features
df['ema_diff'] = (df['ema_fast'] - df['ema_slow']) / df['close']
df['rsi_diff'] = df['rsi'] - 50
df['volume_trend'] = df['volume_ratio'] - 1
# Advanced technical features
df['atr_ratio'] = df['atr'] / df['close']
df['volatility_ratio'] = df['close'].rolling(20).std() / df['close']
df['momentum_ratio'] = df['momentum'] / df['close']
# Trend strength features
df['trend_strength'] = abs(df['ema_fast'] - df['ema_slow']) / df['close']
df['trend_direction'] = np.where(df['ema_fast'] > df['ema_slow'], 1, -1)
df['trend_consistency'] = df['trend_direction'].rolling(10).mean()
# Volume analysis features
df['volume_sma_ratio'] = df['volume'] / df['volume'].rolling(20).mean()
df['volume_std_ratio'] = df['volume'] / df['volume'].rolling(20).std()
df['volume_price_trend'] = df['volume'] * df['price_change']
# Multi-timeframe features
for tf in self.informative_timeframes:
# Check if columns exist before using them
if f'ema_fast_{tf}' in df.columns and f'ema_slow_{tf}' in df.columns:
df[f'ema_diff_{tf}'] = (df[f'ema_fast_{tf}'] - df[f'ema_slow_{tf}']) / df['close']
df[f'trend_strength_{tf}'] = abs(df[f'ema_fast_{tf}'] - df[f'ema_slow_{tf}']) / df['close']
else:
df[f'ema_diff_{tf}'] = 0
df[f'trend_strength_{tf}'] = 0
if f'rsi_{tf}' in df.columns:
df[f'rsi_diff_{tf}'] = df[f'rsi_{tf}'] - 50
df[f'rsi_momentum_{tf}'] = df[f'rsi_{tf}'].diff()
else:
df[f'rsi_diff_{tf}'] = 0
df[f'rsi_momentum_{tf}'] = 0
if f'volume_ratio_{tf}' in df.columns:
df[f'volume_trend_{tf}'] = df[f'volume_ratio_{tf}'] - 1
else:
df[f'volume_trend_{tf}'] = 0
# Interaction features
df['ema_rsi_interaction'] = df['ema_diff'] * df['rsi_diff']
df['volume_momentum_interaction'] = df['volume_trend'] * df['momentum']
df['trend_volume_interaction'] = df['trend_strength'] * df['volume_ratio']
df['volatility_momentum_interaction'] = df['volatility'] * df['momentum']
# Cyclical features
df['hour_sin'] = np.sin(2 * np.pi * pd.to_datetime(df.index).hour / 24)
df['hour_cos'] = np.cos(2 * np.pi * pd.to_datetime(df.index).hour / 24)
df['day_sin'] = np.sin(2 * np.pi * pd.to_datetime(df.index).dayofweek / 7)
df['day_cos'] = np.cos(2 * np.pi * pd.to_datetime(df.index).dayofweek / 7)
# Fourier features for seasonality
for period in [5, 10, 20, 50]:
df[f'fourier_sin_{period}'] = np.sin(2 * np.pi * np.arange(len(df)) / period)
df[f'fourier_cos_{period}'] = np.cos(2 * np.pi * np.arange(len(df)) / period)
# Support and resistance features
df['high_20'] = df['high'].rolling(20).max()
df['low_20'] = df['low'].rolling(20).min()
df['support_distance'] = (df['close'] - df['low_20']) / df['close']
df['resistance_distance'] = (df['high_20'] - df['close']) / df['close']
# Market microstructure features
df['spread'] = (df['high'] - df['low']) / df['close']
df['body_size'] = abs(df['close'] - df['open']) / df['close']
df['upper_shadow'] = (df['high'] - np.maximum(df['open'], df['close'])) / df['close']
df['lower_shadow'] = (np.minimum(df['open'], df['close']) - df['low']) / df['close']
# Momentum and mean reversion features
df['momentum_5'] = df['close'].pct_change(5)
df['momentum_10'] = df['close'].pct_change(10)
df['mean_reversion_5'] = -df['close'].pct_change(5)
df['mean_reversion_10'] = -df['close'].pct_change(10)
# Volatility features
df['volatility_5'] = df['close'].rolling(5).std() / df['close']
df['volatility_10'] = df['close'].rolling(10).std() / df['close']
df['volatility_20'] = df['close'].rolling(20).std() / df['close']
# Advanced statistical features
df['skewness_20'] = df['close'].rolling(20).skew()
df['kurtosis_20'] = df['close'].rolling(20).kurt()
# Cross-asset features (if available)
try:
btc_df = self.dp.get_pair_dataframe('BTC/USDT:USDT', self.timeframe)
if btc_df is not None and len(btc_df) == len(df):
df['btc_correlation'] = df['close'].rolling(20).corr(btc_df['close'])
df['btc_beta'] = df['close'].rolling(20).cov(btc_df['close']) / btc_df['close'].rolling(20).var()
except:
df['btc_correlation'] = 0
df['btc_beta'] = 0
return df
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
Populate entry signal for the given dataframe
"""
conditions = []
# Get parameter values safely
rsi_buy_val = self.rsi_buy.value if hasattr(self.rsi_buy, 'value') else self.rsi_buy
rsi_buy_15m_val = self.rsi_buy_15m.value if hasattr(self.rsi_buy_15m, 'value') else self.rsi_buy_15m
rsi_buy_1h_val = self.rsi_buy_1h.value if hasattr(self.rsi_buy_1h, 'value') else self.rsi_buy_1h
rsi_buy_4h_val = self.rsi_buy_4h.value if hasattr(self.rsi_buy_4h, 'value') else self.rsi_buy_4h
freqai_prediction_threshold_val = self.freqai_prediction_threshold.value if hasattr(self.freqai_prediction_threshold, 'value') else self.freqai_prediction_threshold
# Base conditions (5m timeframe) - Core strategy
base_condition = (
(df['ema_fast'] > df['ema_slow']) &
(df['rsi'] < rsi_buy_val) &
(df['volume_ratio'] > 0.8) # Relaxed volume condition
)
# Multi-timeframe conditions - Flexible (any 2 timeframes)
mtf_conditions = []
# 15m condition
if 'ema_fast_15m' in df.columns and 'ema_slow_15m' in df.columns and 'rsi_15m' in df.columns:
mtf_15m = (df['ema_fast_15m'] > df['ema_slow_15m']) & (df['rsi_15m'] < rsi_buy_15m_val)
mtf_conditions.append(mtf_15m)
# 1h condition
if 'ema_fast_1h' in df.columns and 'ema_slow_1h' in df.columns and 'rsi_1h' in df.columns:
mtf_1h = (df['ema_fast_1h'] > df['ema_slow_1h']) & (df['rsi_1h'] < rsi_buy_1h_val)
mtf_conditions.append(mtf_1h)
# 4h condition
if 'ema_fast_4h' in df.columns and 'ema_slow_4h' in df.columns and 'rsi_4h' in df.columns:
mtf_4h = (df['ema_fast_4h'] > df['ema_slow_4h']) & (df['rsi_4h'] < rsi_buy_4h_val)
mtf_conditions.append(mtf_4h)
# Require at least 2 out of 3 timeframes to align, or just base condition if not enough data
if len(mtf_conditions) >= 2:
mtf_condition = mtf_conditions[0] | mtf_conditions[1]
if len(mtf_conditions) == 3:
mtf_condition = mtf_condition | mtf_conditions[2]
else:
mtf_condition = pd.Series([True] * len(df), index=df.index)
# Advanced FreqAI prediction condition - Multi-model integration
freqai_condition = pd.Series([True] * len(df), index=df.index)
if self.freqai_enabled:
# Get parameter values for advanced model integration
smart_money_weight_val = self.smart_money_weight.value if hasattr(self.smart_money_weight, 'value') else self.smart_money_weight
ensemble_weight_val = self.ensemble_weight.value if hasattr(self.ensemble_weight, 'value') else self.ensemble_weight
mtf_weight_val = self.mtf_weight.value if hasattr(self.mtf_weight, 'value') else self.mtf_weight
min_confidence_threshold_val = self.min_confidence_threshold.value if hasattr(self.min_confidence_threshold, 'value') else self.min_confidence_threshold
# Initialize weighted prediction
weighted_prediction = pd.Series([0.0] * len(df), index=df.index)
total_weight = 0.0
# Base FreqAI prediction
if '&-s_predict' in df.columns:
base_prediction = df['&-s_predict']
weighted_prediction += base_prediction * freqai_weight_val
total_weight += freqai_weight_val
# Smart Money prediction
if 'smart_money_predict' in df.columns:
smart_money_pred = df['smart_money_predict']
weighted_prediction += smart_money_pred * smart_money_weight_val
total_weight += smart_money_weight_val
# Ensemble prediction
if 'ensemble_predict' in df.columns:
ensemble_pred = df['ensemble_predict']
weighted_prediction += ensemble_pred * ensemble_weight_val
total_weight += ensemble_weight_val
# Multi-timeframe prediction
if 'mtf_predict' in df.columns:
mtf_pred = df['mtf_predict']
weighted_prediction += mtf_pred * mtf_weight_val
total_weight += mtf_weight_val
# Normalize weighted prediction
if total_weight > 0:
weighted_prediction = weighted_prediction / total_weight
# Apply confidence threshold
freqai_positive = weighted_prediction > 0.1
freqai_strong = weighted_prediction > freqai_prediction_threshold_val
freqai_confidence = abs(weighted_prediction) > min_confidence_threshold_val
# Combined condition
freqai_condition = (freqai_strong & freqai_confidence) | freqai_positive
else:
# Fallback to base prediction if available
if '&-s_predict' in df.columns:
freqai_positive = df['&-s_predict'] > 0.1
freqai_strong = df['&-s_predict'] > freqai_prediction_threshold_val
freqai_condition = freqai_strong | freqai_positive
else:
logger.info(f"FreqAI enabled but no predictions available for {metadata['pair']}")
else:
# FreqAI not enabled - use base strategy
freqai_condition = pd.Series([True] * len(df), index=df.index)
# Combine conditions with smart logic
# Primary: Base + MTF + FreqAI (if available)
primary_condition = base_condition & mtf_condition & freqai_condition
# Fallback: Base + MTF (if FreqAI not working)
fallback_condition = base_condition & mtf_condition
# Use primary if FreqAI is working, otherwise fallback
if self.freqai_enabled and '&-s_predict' in df.columns:
final_condition = primary_condition
else:
final_condition = fallback_condition
conditions.append(final_condition)
if conditions:
df.loc[reduce(lambda x, y: x | y, conditions), 'enter_long'] = 1
# Debug information (only log occasionally to avoid spam)
if len(df) > 0 and df.index[-1] % 100 == 0: # Log every 100th candle
base_count = base_condition.sum()
mtf_count = mtf_condition.sum()
freqai_count = freqai_condition.sum() if self.freqai_enabled else 0
final_count = final_condition.sum()
total_candles = len(df)
logger.info(f"Entry conditions for {metadata['pair']}: "
f"Base={base_count}/{total_candles}, "
f"MTF={mtf_count}/{total_candles}, "
f"FreqAI={freqai_count}/{total_candles}, "
f"Final={final_count}/{total_candles}")
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
Populate exit signal for the given dataframe
"""
conditions = []
# Get parameter values safely
rsi_sell_val = self.rsi_sell.value if hasattr(self.rsi_sell, 'value') else self.rsi_sell
rsi_sell_15m_val = self.rsi_sell_15m.value if hasattr(self.rsi_sell_15m, 'value') else self.rsi_sell_15m
rsi_sell_1h_val = self.rsi_sell_1h.value if hasattr(self.rsi_sell_1h, 'value') else self.rsi_sell_1h
rsi_sell_4h_val = self.rsi_sell_4h.value if hasattr(self.rsi_sell_4h, 'value') else self.rsi_sell_4h
freqai_prediction_threshold_val = self.freqai_prediction_threshold.value if hasattr(self.freqai_prediction_threshold, 'value') else self.freqai_prediction_threshold
# Base exit conditions (5m timeframe) - Core strategy
base_exit = (
(df['rsi'] > rsi_sell_val) |
(df['ema_fast'] < df['ema_slow'])
)
# Multi-timeframe exit conditions - Any timeframe can trigger exit
mtf_exit = pd.Series([False] * len(df), index=df.index)
# Check each timeframe condition separately
if 'rsi_15m' in df.columns:
mtf_exit = mtf_exit | (df['rsi_15m'] > rsi_sell_15m_val)
if 'rsi_1h' in df.columns:
mtf_exit = mtf_exit | (df['rsi_1h'] > rsi_sell_1h_val)
if 'rsi_4h' in df.columns:
mtf_exit = mtf_exit | (df['rsi_4h'] > rsi_sell_4h_val)
# FreqAI exit condition - Smart integration
freqai_exit = pd.Series([False] * len(df), index=df.index)
if self.freqai_enabled and '&-s_predict' in df.columns:
# Use FreqAI prediction for exits - negative prediction or strong negative
freqai_negative = df['&-s_predict'] < -0.1
freqai_strong_negative = df['&-s_predict'] < -freqai_prediction_threshold_val
# If we have strong negative FreqAI signal, use it; otherwise, just require negative
freqai_exit = freqai_strong_negative | freqai_negative
# Combine exit conditions with smart logic
# Primary: Base + MTF + FreqAI (if available)
primary_exit = base_exit | mtf_exit | freqai_exit
# Fallback: Base + MTF (if FreqAI not working)
fallback_exit = base_exit | mtf_exit
# Use primary if FreqAI is working, otherwise fallback
if self.freqai_enabled and '&-s_predict' in df.columns:
final_exit = primary_exit
else:
final_exit = fallback_exit
conditions.append(final_exit)
if conditions:
df.loc[reduce(lambda x, y: x | y, conditions), 'exit_long'] = 1
return df
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
Custom stoploss logic
"""
# Get parameter values safely
profit_take_threshold_val = self.profit_take_threshold.value if hasattr(self.profit_take_threshold, 'value') else self.profit_take_threshold
profit_take_ratio_val = self.profit_take_ratio.value if hasattr(self.profit_take_ratio, 'value') else self.profit_take_ratio
# Dynamic stoploss based on profit
if current_profit > profit_take_threshold_val:
return -profit_take_ratio_val
elif current_profit > 0.02:
return -0.05
elif current_profit > 0.01:
return -0.08
return self.stoploss
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> Optional[str]:
"""
Custom exit logic
"""
# Profit taking
if current_profit > 0.08:
return 'profit_take_8'
elif current_profit > 0.05:
return 'profit_take_5'
elif current_profit > 0.02:
return 'profit_take_2'
# Stop loss
elif current_profit < -0.10:
return 'stop_loss_10'
elif current_profit < -0.05:
return 'stop_loss_5'
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
side: str, **kwargs) -> bool:
"""
Additional confirmation for trade entry
"""
# Time-based protection
if hasattr(self, 'use_timeframe_protection') and self.use_timeframe_protection:
cooldown_value = self.cooldown_lookback.value if hasattr(self.cooldown_lookback, 'value') else self.cooldown_lookback
if self._last_candle_times.get(pair, 0) > current_time.timestamp() - cooldown_value * 300:
return False
# Update last candle time
self._last_candle_times[pair] = current_time.timestamp()
return True
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str,
current_time: datetime, **kwargs) -> bool:
"""
Additional confirmation for trade exit
"""
return True
def leverage_callback(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
side: str, **kwargs) -> float:
"""
Custom leverage logic
"""
# Conservative leverage based on volatility
if 'atr' in self.dp.get_pair_dataframe(pair, self.timeframe).columns:
df = self.dp.get_pair_dataframe(pair, self.timeframe)
volatility = df['atr'].iloc[-1] / df['close'].iloc[-1]
if volatility > 0.05: # High volatility
return min(proposed_leverage, 2.0)
elif volatility > 0.03: # Medium volatility
return min(proposed_leverage, 5.0)
else: # Low volatility
return min(proposed_leverage, 8.0)
return proposed_leverage
def bot_loop_start(self, **kwargs) -> None:
"""
Called at the start of the bot iteration
"""
# Update ROI table with integer keys
self.minimal_roi = {
0: self.roi_0.value,
self.roi_t1.value: self.roi_1.value,
self.roi_t2.value: self.roi_2.value,
self.roi_t3.value: self.roi_3.value,
}
# Update trailing stop parameters safely
if hasattr(self, '_trailing_stop_positive') and hasattr(self._trailing_stop_positive, 'value'):
self.trailing_stop_positive = self._trailing_stop_positive.value
if hasattr(self, '_trailing_stop_positive_offset') and hasattr(self._trailing_stop_positive_offset, 'value'):
self.trailing_stop_positive_offset = self._trailing_stop_positive_offset.value
if hasattr(self, '_trailing_only_offset_is_reached') and hasattr(self._trailing_only_offset_is_reached, 'value'):
self.trailing_only_offset_is_reached = self._trailing_only_offset_is_reached.value