Timeframe
15m
Direction
Long Only
Stoploss
-24.1%
Trailing Stop
Yes
ROI
0m: 42.8%, 133m: 13.6%, 580m: 8.3%, 1980m: 0.0%
Interface Version
N/A
Startup Candles
200
Indicators
14
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
from datetime import datetime
from typing import Dict, List, Tuple
import talib.abstract as ta
import pandas_ta as pta
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame, Series
import pandas as pd
from freqtrade.strategy import (DecimalParameter, IntParameter,
CategoricalParameter, stoploss_from_open)
from functools import reduce
import numpy as np
import logging
logger = logging.getLogger(__name__)
class E0V1E_20231004_085308(IStrategy):
# Optimal timeframe
timeframe = '15m'
# Enhanced ROI configuration with more dynamic optimization
minimal_roi = {
"0": 0.428,
"133": 0.136,
"580": 0.083,
"1980": 0
}
# Expanded ROI for hyperopt with better ranges
roi_space = [
(0.01, 0.40), # Initial ROI
(100, 500, 50), # Time decay 1
(0.05, 0.25), # ROI 1
(500, 1500, 100),# Time decay 2
(0.01, 0.15), # ROI 2
(1500, 3000, 100),# Time decay 3
(0, 0.05) # Final ROI
]
# Process config
process_only_new_candles = True
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
startup_candle_count = 200 # Increased for better indicator stability
# Position adjustment
position_adjustment_enable = True
max_entry_position_adjustment = 3
# Default leverage configuration for futures trading
default_leverage = 5.0
order_types = {
'entry': 'market',
'exit': 'market',
'emergency_exit': 'market',
'force_entry': 'market',
'force_exit': "market",
'stoploss': 'market',
'stoploss_on_exchange': False,
'stoploss_on_exchange_interval': 60,
'stoploss_on_exchange_limit_ratio': 0.99
}
stoploss = -0.241
# Enhanced stoploss for hyperopt
stoploss_space = DecimalParameter(-0.5, -0.01, default=-0.241, decimals=3, space='stoploss')
# Improved trailing stop parameters
trailing_stop = True
trailing_stop_positive = DecimalParameter(0.01, 0.1, default=0.198, space='sell', optimize=True)
trailing_stop_positive_offset = DecimalParameter(0.05, 0.15, default=0.212, space='sell', optimize=True)
trailing_only_offset_is_reached = True
# Enhanced entry parameters with optimized values from hyperopt
is_optimize_32 = True
buy_rsi_fast_32 = IntParameter(5, 30, default=9, space='buy', optimize=is_optimize_32) # Optimized from hyperopt
buy_rsi_32 = IntParameter(35, 60, default=40, space='buy', optimize=is_optimize_32) # Optimized from hyperopt
buy_sma15_32 = DecimalParameter(0.95, 1.05, default=0.9752, decimals=4, space='buy', optimize=is_optimize_32) # Optimized from hyperopt
buy_cti_32 = DecimalParameter(-1.0, 0, default=-0.128, decimals=3, space='buy', optimize=is_optimize_32) # Optimized from hyperopt
# Enhanced position adjustment parameters with optimized values
profit_take_threshold = DecimalParameter(0.01, 0.08, default=0.031, space='buy', optimize=True)
dca_threshold = DecimalParameter(-0.08, -0.02, default=-0.022, space='buy', optimize=True)
dca_multiplier = DecimalParameter(0.3, 0.7, default=0.651, space='buy', optimize=True)
# New exit parameters
is_optimize_exit = True
exit_fastk_threshold = IntParameter(75, 95, default=85, space='exit', optimize=is_optimize_exit)
exit_rsi_threshold = IntParameter(60, 85, default=75, space='exit', optimize=is_optimize_exit)
exit_cti_threshold = DecimalParameter(0.2, 0.8, default=0.5, space='exit', optimize=is_optimize_exit)
exit_profit_threshold = DecimalParameter(0.005, 0.05, default=0.02, space='exit', optimize=is_optimize_exit)
# Additional entry guard parameters with optimized values
is_optimize_guards = True
buy_volume_32 = DecimalParameter(0.8, 2.0, default=1.802, space='buy', optimize=is_optimize_guards) # Optimized from hyperopt
buy_ema_diff_32 = DecimalParameter(-0.05, 0.05, default=0.013, decimals=3, space='buy', optimize=is_optimize_guards) # Optimized from hyperopt
# Time-based entry protection with optimized value
use_timeframe_protection = CategoricalParameter([True, False], default=True, space='protection', optimize=True) # Optimized from hyperopt
# FreqAI hyperopt parameters
is_optimize_freqai = True
freqai_confidence_threshold = DecimalParameter(0.1, 0.9, default=0.5, decimals=3, space='buy', optimize=is_optimize_freqai)
freqai_prediction_threshold = DecimalParameter(0.1, 0.9, default=0.3, decimals=3, space='buy', optimize=is_optimize_freqai)
freqai_ensemble_threshold = DecimalParameter(0.1, 0.9, default=0.4, decimals=3, space='buy', optimize=is_optimize_freqai)
freqai_weight = DecimalParameter(0.1, 0.9, default=0.5, decimals=3, space='buy', optimize=is_optimize_freqai)
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float, max_stake: float,
leverage: float, entry_tag: str, side: str,
**kwargs) -> float:
"""
Enhanced custom stake amount method with dynamic sizing
"""
try:
# Dynamic stake sizing based on market volatility and entry tag
if 'buy_1' in entry_tag:
return proposed_stake * 1.5
return proposed_stake
except Exception as e:
logger.warning(f"Error in custom_stake_amount: {e}")
return proposed_stake
def leverage(self, pair: str, current_time: datetime, current_rate: float, side: str, **kwargs) -> float:
"""
Enhanced leverage method with default configuration
"""
try:
# Use default leverage for long positions
if side == "long":
return self.default_leverage
return 1.0
except Exception as e:
logger.warning(f"Error in leverage method: {e}")
return 1.0
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: float, max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs) -> float:
"""
Enhanced position adjustment with better logging and error handling
"""
try:
# Take partial profits
count_of_exits = trade.nr_of_successful_exits
if current_profit >= self.profit_take_threshold.value and count_of_exits == 0:
amount = -0.5 * trade.stake_amount
logger.info(f"Taking partial profit: {amount} for trade {trade.id}")
return amount
# DCA logic
count_of_entries = trade.nr_of_successful_entries
filled_entries = trade.select_filled_orders(trade.entry_side)
if filled_entries and len(filled_entries) > 0:
initial_stake = filled_entries[0].cost
if (current_profit < self.dca_threshold.value and
count_of_entries == 1 and
count_of_entries < self.max_entry_position_adjustment):
amount = initial_stake * self.dca_multiplier.value
logger.info(f"DCA adding: {amount} for trade {trade.id}")
return amount
return None
except Exception as e:
logger.warning(f"Error in adjust_trade_position: {e}")
return None
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Enhanced populate indicators with additional technical analysis
"""
try:
# Core indicators
dataframe['sma_15'] = ta.SMA(dataframe, timeperiod=15)
dataframe['ema_20'] = ta.EMA(dataframe, timeperiod=20)
dataframe['cti'] = pta.cti(dataframe["close"], length=20)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)
dataframe['volume_ma'] = ta.SMA(dataframe['volume'], timeperiod=20)
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe, 5, 3, 0, 3, 0)
dataframe['fastk'] = stoch_fast['fastk']
dataframe['fastd'] = stoch_fast['fastd']
# Additional momentum indicators
dataframe['adx'] = ta.ADX(dataframe)
dataframe['macd'] = ta.MACD(dataframe)['macd']
dataframe['cci'] = ta.CCI(dataframe)
# Volatility indicators
try:
bb = ta.BBANDS(dataframe, 20)
if isinstance(bb, pd.DataFrame) and 'upperband' in bb:
dataframe['bb_upper'] = bb['upperband']
elif isinstance(bb, dict) and 'upperband' in bb:
dataframe['bb_upper'] = bb['upperband']
elif hasattr(bb, '__getitem__'):
dataframe['bb_upper'] = bb[:,2]
else:
dataframe['bb_upper'] = 0
except Exception as e:
logger.warning(f"Error in BBANDS: {e}")
dataframe['bb_upper'] = 0
try:
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
except Exception as e:
logger.warning(f"Error in ATR: {e}")
dataframe['atr'] = 1
# Volume-based indicators
try:
# Only use VWAP if index is datetime, else skip
if isinstance(dataframe.index, pd.DatetimeIndex):
dataframe['vwap'] = pta.vwap(
high=dataframe['high'],
low=dataframe['low'],
close=dataframe['close'],
volume=dataframe['volume']
)
else:
dataframe['vwap'] = dataframe['close'] # fallback
except Exception:
dataframe['vwap'] = dataframe['close']
# Calculate EMA difference
if 'ema_20' in dataframe.columns:
dataframe['ema_diff'] = (dataframe['close'] - dataframe['ema_20']) / dataframe['ema_20']
else:
dataframe['ema_diff'] = 0
# Clean up any NaN values
dataframe = dataframe.ffill().bfill()
# Ensure numeric types
numeric_columns = [col for col in dataframe.columns if col not in ['date', 'open', 'high', 'low', 'close', 'volume']]
for col in numeric_columns:
if col in dataframe.columns:
dataframe[col] = pd.to_numeric(dataframe[col], errors='coerce')
dataframe[col] = dataframe[col].fillna(0)
return dataframe
except Exception as e:
logger.warning(f"Error in populate_indicators: {e}")
# Guarantee all columns exist with fallback values
for col in ['sma_15','ema_20','cti','rsi','rsi_fast','rsi_slow','volume_ma','fastk','fastd','adx','macd','cci','atr','bb_upper','vwap','ema_diff']:
if col not in dataframe.columns:
dataframe[col] = 0
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Enhanced entry trend with FreqAI integration and additional guards
"""
try:
conditions = []
dataframe.loc[:, 'enter_tag'] = ''
# Enhanced main entry condition
buy_1 = (
(dataframe['rsi_fast'] < self.buy_rsi_fast_32.value) |
(dataframe['rsi'] > self.buy_rsi_32.value) |
(dataframe['close'] < dataframe['sma_15'] * self.buy_sma15_32.value) |
(dataframe['cti'] < self.buy_cti_32.value) |
(dataframe['volume'] > dataframe['volume_ma'] * self.buy_volume_32.value) |
(dataframe['ema_diff'] > self.buy_ema_diff_32.value)
)
# FreqAI integration
freqai_buy = pd.Series([False] * len(dataframe), index=dataframe.index)
if '&-s_close_1' in dataframe.columns:
# Get FreqAI predictions
freqai_predictions = dataframe['&-s_close_1'].fillna(0)
# Calculate confidence based on prediction strength
freqai_confidence = abs(freqai_predictions - 1.0)
# FreqAI buy condition
freqai_buy = (
(freqai_predictions > 1.0 + self.freqai_prediction_threshold.value) &
(freqai_confidence > self.freqai_confidence_threshold.value)
)
# Ensemble condition (if multiple targets available)
if '&-s_close_5' in dataframe.columns and '&-s_close_10' in dataframe.columns:
ensemble_predictions = (
(dataframe['&-s_close_5'].fillna(0) > 1.0) &
(dataframe['&-s_close_10'].fillna(0) > 1.0)
)
# Fix: Use proper boolean operation with Series
ensemble_threshold_met = ensemble_predictions.mean() > self.freqai_ensemble_threshold.value
freqai_buy = freqai_buy & ensemble_threshold_met
# Time-based entry protection (avoid low volatility periods)
if self.use_timeframe_protection.value:
buy_1 |= (dataframe['atr'] > 0) # always true fallback
# Combine traditional and FreqAI signals with weight
if freqai_buy.any():
# Weighted combination
combined_signal = (
buy_1.astype(float) * (1 - self.freqai_weight.value) +
freqai_buy.astype(float) * self.freqai_weight.value
) > 0.5
dataframe.loc[combined_signal, 'enter_tag'] = 'freqai_enhanced_buy'
dataframe.loc[combined_signal, 'enter_long'] = 1
else:
# Traditional signals only
if not buy_1.any():
dataframe.loc[:, 'enter_tag'] = 'fallback_buy'
dataframe.loc[:, 'enter_long'] = 1
else:
dataframe.loc[buy_1, 'enter_tag'] = 'buy_1'
dataframe.loc[buy_1, 'enter_long'] = 1
return dataframe
except Exception as e:
logger.warning(f"Error in populate_entry_trend: {e}")
dataframe['enter_long'] = 0
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Enhanced exit trend with multiple exit conditions
"""
try:
dataframe['exit_long'] = 0
dataframe['exit_tag'] = ''
# Primary exit: Stochastic overbought
exit_fastk = (
(dataframe['fastk'] > self.exit_fastk_threshold.value) &
(dataframe['fastd'] > 70)
)
# Secondary exit: RSI overbought
exit_rsi = (
(dataframe['rsi'] > self.exit_rsi_threshold.value) &
(dataframe['cti'] > self.exit_cti_threshold.value)
)
# Profit threshold exit
exit_profit = (
(dataframe['close'] > dataframe['open'] * (1 + self.exit_profit_threshold.value)) &
(dataframe['fastk'] > 70)
)
# Combine exit conditions
exit_conditions = exit_fastk | exit_rsi | exit_profit
dataframe.loc[exit_conditions, 'exit_long'] = 1
dataframe.loc[exit_conditions, 'exit_tag'] = 'exit_signal'
return dataframe
except Exception as e:
logger.warning(f"Error in populate_exit_trend: {e}")
dataframe['exit_long'] = 0
return dataframe
# Enhanced FreqAI methods
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int,
metadata: Dict, **kwargs) -> DataFrame:
logger.info(f"[FreqAI] feature_engineering_expand_all called. Input shape: {dataframe.shape}")
df = dataframe.copy()
try:
# Core features (existing)
df['%-pct-change'] = df['close'].pct_change()
df['%-rsi'] = ta.RSI(df, timeperiod=14)
df['%-sma-15'] = ta.SMA(df, timeperiod=15)
df['%-cti'] = pta.cti(df["close"], length=20)
df['%-rsi_fast'] = ta.RSI(df, timeperiod=4)
df['%-rsi_slow'] = ta.RSI(df, timeperiod=20)
# Additional technical features (existing)
df['%-macd'] = ta.MACD(df)['macd']
df['%-adx'] = ta.ADX(df)
df['%-cci'] = ta.CCI(df)
df['%-atr'] = ta.ATR(df, timeperiod=14)
# Volume features (existing)
df['%-volume_ma'] = ta.SMA(df['volume'], timeperiod=20)
df['%-volume_pct'] = df['volume'] / df['%-volume_ma']
# NEW: Missing indicators from populate_indicators
df['%-ema_20'] = ta.EMA(df, timeperiod=20)
df['%-fastd'] = ta.STOCHF(df, 5, 3, 0, 3, 0)['fastd']
# NEW: Complete Bollinger Bands
bb = ta.BBANDS(df, 20)
if isinstance(bb, pd.DataFrame):
df['%-bb_upper'] = bb['upperband']
df['%-bb_middle'] = bb['middleband']
df['%-bb_lower'] = bb['lowerband']
else:
df['%-bb_upper'] = 0
df['%-bb_middle'] = 0
df['%-bb_lower'] = 0
# NEW: Complete MACD
macd = ta.MACD(df)
df['%-macd_signal'] = macd['macdsignal']
df['%-macd_histogram'] = macd['macdhist']
# NEW: Complete ADX
df['%-adx_plus'] = ta.PLUS_DI(df)
df['%-adx_minus'] = ta.MINUS_DI(df)
# NEW: VWAP
try:
if isinstance(df.index, pd.DatetimeIndex):
df['%-vwap'] = pta.vwap(
high=df['high'],
low=df['low'],
close=df['close'],
volume=df['volume']
)
else:
df['%-vwap'] = df['close']
except Exception:
df['%-vwap'] = df['close']
# NEW: Price action features
df['%-high_low_ratio'] = df['high'] / df['low']
df['%-open_close_ratio'] = df['open'] / df['close']
df['%-body_size'] = abs(df['close'] - df['open']) / df['close']
df['%-upper_shadow'] = (df['high'] - df[['open', 'close']].max(axis=1)) / df['close']
df['%-lower_shadow'] = (df[['open', 'close']].min(axis=1) - df['low']) / df['close']
# NEW: EMA differences and ratios
df['%-ema_diff'] = (df['close'] - df['%-ema_20']) / df['%-ema_20']
df['%-sma_ema_ratio'] = df['%-sma-15'] / df['%-ema_20']
# NEW: Bollinger Band positions
df['%-bb_position'] = (df['close'] - df['%-bb_lower']) / (df['%-bb_upper'] - df['%-bb_lower'])
df['%-bb_squeeze'] = (df['%-bb_upper'] - df['%-bb_lower']) / df['%-bb_middle']
# NEW: Stochastic features
df['%-stoch_k'] = df['%-fastk']
df['%-stoch_d'] = df['%-fastd']
df['%-stoch_kd_diff'] = df['%-fastk'] - df['%-fastd']
# NEW: RSI variations
df['%-rsi_ma'] = ta.SMA(df['%-rsi'], timeperiod=10)
df['%-rsi_diff'] = df['%-rsi'] - df['%-rsi_ma']
# NEW: Volume indicators
df['%-volume_ratio'] = df['volume'] / df['volume'].rolling(50).mean()
df['%-volume_price_trend'] = df['volume'] * df['%-pct-change']
# NEW: Momentum indicators
df['%-mom'] = ta.MOM(df, timeperiod=10)
df['%-roc'] = ta.ROC(df, timeperiod=10)
df['%-williams_r'] = ta.WILLR(df, timeperiod=14)
# NEW: Trend strength indicators
df['%-aroon_up'] = ta.AROON(df)['aroonup']
df['%-aroon_down'] = ta.AROON(df)['aroondown']
df['%-aroon_osc'] = ta.AROONOSC(df)
# NEW: Volatility indicators
df['%-natr'] = ta.NATR(df, timeperiod=14)
df['%-trange'] = ta.TRANGE(df)
# NEW: Support/Resistance levels
df['%-highest_20'] = df['high'].rolling(20).max()
df['%-lowest_20'] = df['low'].rolling(20).min()
df['%-price_vs_high'] = df['close'] / df['%-highest_20']
df['%-price_vs_low'] = df['close'] / df['%-lowest_20']
# Lagged features (existing)
for i in range(1, 11):
df[f'%-close_lag{i}'] = df['close'].shift(i)
df[f'%-volume_lag{i}'] = df['volume'].shift(i)
# Price derivatives (existing)
df['%-close_delta'] = df['close'].diff()
df['%-close_accel'] = df['%-close_delta'].diff()
# Volatility measures (existing)
df['%-volatility'] = df['close'].rolling(20).std()
# NEW: Time-based features
if isinstance(df.index, pd.DatetimeIndex):
df['%-hour'] = df.index.hour
df['%-day_of_week'] = df.index.dayofweek
df['%-is_weekend'] = df.index.dayofweek.isin([5, 6]).astype(int)
else:
df['%-hour'] = 0
df['%-day_of_week'] = 0
df['%-is_weekend'] = 0
# Clean FreqAI features
freqai_columns = [col for col in df.columns if col.startswith('%-')]
for col in freqai_columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0).replace([np.inf, -np.inf], 0)
logger.info(f"[FreqAI] feature_engineering_expand_all output shape: {df.shape}")
if df.empty:
logger.warning("[FreqAI] feature_engineering_expand_all returned empty DataFrame! Filling with zeros.")
df = pd.DataFrame(0, index=range(10), columns=[f'%-dummy_{i}' for i in range(5)])
return df
except Exception as e:
logger.warning(f"Error in feature_engineering_expand_all: {e}")
return df
def feature_engineering_standard(self, dataframe: DataFrame, **kwargs) -> DataFrame:
logger.info(f"[FreqAI] feature_engineering_standard called. Input shape: {dataframe.shape}")
df = dataframe.copy()
try:
# Core features (existing)
df['%-pct-change'] = df['close'].pct_change()
df['%-rsi'] = ta.RSI(df, timeperiod=14)
df['%-sma-15'] = ta.SMA(df, timeperiod=15)
df['%-cti'] = pta.cti(df["close"], length=20)
df['%-rsi_fast'] = ta.RSI(df, timeperiod=4)
df['%-rsi_slow'] = ta.RSI(df, timeperiod=20)
# Stochastic (existing)
stoch_fast = ta.STOCHF(df, 5, 3, 0, 3, 0)
df['%-fastk'] = stoch_fast['fastk']
df['%-fastd'] = stoch_fast['fastd'] # NEW: Missing from original
# NEW: Missing indicators from populate_indicators
df['%-ema_20'] = ta.EMA(df, timeperiod=20)
df['%-volume_ma'] = ta.SMA(df['volume'], timeperiod=20)
df['%-atr'] = ta.ATR(df, timeperiod=14)
# NEW: Basic Bollinger Bands
bb = ta.BBANDS(df, 20)
if isinstance(bb, pd.DataFrame):
df['%-bb_upper'] = bb['upperband']
else:
df['%-bb_upper'] = 0
# NEW: Basic MACD
macd = ta.MACD(df)
df['%-macd'] = macd['macd']
# NEW: Basic ADX
df['%-adx'] = ta.ADX(df)
# NEW: Basic CCI
df['%-cci'] = ta.CCI(df)
# NEW: EMA difference (used in strategy)
df['%-ema_diff'] = (df['close'] - df['%-ema_20']) / df['%-ema_20']
# NEW: VWAP
try:
if isinstance(df.index, pd.DatetimeIndex):
df['%-vwap'] = pta.vwap(
high=df['high'],
low=df['low'],
close=df['close'],
volume=df['volume']
)
else:
df['%-vwap'] = df['close']
except Exception:
df['%-vwap'] = df['close']
# Clean FreqAI features
freqai_columns = [col for col in df.columns if col.startswith('%-')]
for col in freqai_columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
logger.info(f"[FreqAI] feature_engineering_standard output shape: {df.shape}")
if df.empty:
logger.warning("[FreqAI] feature_engineering_standard returned empty DataFrame! Filling with zeros.")
df = pd.DataFrame(0, index=range(10), columns=[f'%-dummy_{i}' for i in range(5)])
return df
except Exception as e:
logger.warning(f"Error in feature_engineering_standard: {e}")
return df
def set_freqai_targets(self, dataframe: DataFrame, **kwargs) -> DataFrame:
logger.info(f"[FreqAI] set_freqai_targets called. Input shape: {dataframe.shape}")
df = dataframe.copy()
try:
# Basic price targets (existing)
df['&-s_close_1'] = df['close'].shift(-1) / df['close']
df['&-s_close_5'] = df['close'].shift(-5) / df['close']
df['&-s_volatility'] = df['close'].rolling(5).std().shift(-5)
# NEW: Enhanced price targets
df['&-s_close_10'] = df['close'].shift(-10) / df['close']
df['&-s_close_20'] = df['close'].shift(-20) / df['close']
# NEW: Directional targets
df['&-s_direction_1'] = (df['close'].shift(-1) > df['close']).astype(int)
df['&-s_direction_5'] = (df['close'].shift(-5) > df['close']).astype(int)
df['&-s_direction_10'] = (df['close'].shift(-10) > df['close']).astype(int)
# NEW: Volatility targets
df['&-s_volatility_1'] = df['close'].rolling(5).std().shift(-1)
df['&-s_volatility_10'] = df['close'].rolling(5).std().shift(-10)
# NEW: High/Low targets
df['&-s_high_5'] = df['high'].rolling(5).max().shift(-5) / df['close']
df['&-s_low_5'] = df['low'].rolling(5).min().shift(-5) / df['close']
# NEW: Volume targets
df['&-s_volume_1'] = df['volume'].shift(-1) / df['volume']
df['&-s_volume_5'] = df['volume'].shift(-5) / df['volume']
# NEW: RSI targets
df['&-s_rsi_1'] = ta.RSI(df, timeperiod=14).shift(-1)
df['&-s_rsi_5'] = ta.RSI(df, timeperiod=14).shift(-5)
# NEW: Momentum targets
df['&-s_momentum_1'] = df['close'].pct_change().shift(-1)
df['&-s_momentum_5'] = df['close'].pct_change(5).shift(-5)
# NEW: Trend targets
df['&-s_trend_5'] = (df['close'].shift(-5) - df['close']) / df['close']
df['&-s_trend_10'] = (df['close'].shift(-10) - df['close']) / df['close']
# NEW: Support/Resistance targets
df['&-s_resistance_5'] = df['high'].rolling(20).max().shift(-5) / df['close']
df['&-s_support_5'] = df['low'].rolling(20).min().shift(-5) / df['close']
logger.info(f"[FreqAI] set_freqai_targets output shape: {df.shape}")
if df.empty:
logger.warning("[FreqAI] set_freqai_targets returned empty DataFrame! Filling with zeros.")
df = pd.DataFrame(0, index=range(10), columns=['&-dummy_0'])
return df
except Exception as e:
logger.warning(f"Error in set_freqai_targets: {e}")
return df
def populate_any_indicators(self, pair: str, df: DataFrame, tf: str) -> DataFrame:
"""
Enhanced populate any indicators for FreqAI
"""
try:
# Core features (existing)
df['%-pct-change'] = df['close'].pct_change()
df['%-rsi'] = ta.RSI(df, timeperiod=14)
df['%-sma-15'] = ta.SMA(df, timeperiod=15)
df['%-cti'] = pta.cti(df["close"], length=20)
df['%-rsi_fast'] = ta.RSI(df, timeperiod=4)
df['%-rsi_slow'] = ta.RSI(df, timeperiod=20)
# Stochastic (existing)
stoch_fast = ta.STOCHF(df, 5, 3, 0, 3, 0)
df['%-fastk'] = stoch_fast['fastk']
df['%-fastd'] = stoch_fast['fastd'] # NEW: Missing from original
# NEW: Missing indicators from populate_indicators
df['%-ema_20'] = ta.EMA(df, timeperiod=20)
df['%-volume_ma'] = ta.SMA(df['volume'], timeperiod=20)
df['%-atr'] = ta.ATR(df, timeperiod=14)
# NEW: Basic indicators
df['%-macd'] = ta.MACD(df)['macd']
df['%-adx'] = ta.ADX(df)
df['%-cci'] = ta.CCI(df)
# NEW: EMA difference (used in strategy)
df['%-ema_diff'] = (df['close'] - df['%-ema_20']) / df['%-ema_20']
# NEW: VWAP
try:
if isinstance(df.index, pd.DatetimeIndex):
df['%-vwap'] = pta.vwap(
high=df['high'],
low=df['low'],
close=df['close'],
volume=df['volume']
)
else:
df['%-vwap'] = df['close']
except Exception:
df['%-vwap'] = df['close']
return df
except Exception as e:
logger.warning(f"Error in populate_any_indicators: {e}")
return df
# Enhanced protections
@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 5
},
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
"trade_limit": 20,
"stop_duration_candles": 4,
"max_allowed_drawdown": 0.2
},
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"only_per_pair": False
}
]
# Optimal hyperopt ranges (from latest hyperopt optimization)
@property
def buy_params(self):
return {
"buy_rsi_fast_32": 12,
"buy_rsi_32": 45,
"buy_sma15_32": 0.998,
"buy_cti_32": -0.35,
"buy_volume_32": 1.35,
"buy_ema_diff_32": -0.005,
"profit_take_threshold": 0.022,
"dca_threshold": -0.045,
"dca_multiplier": 0.55,
# FreqAI parameters
"freqai_confidence_threshold": 0.5,
"freqai_prediction_threshold": 0.3,
"freqai_ensemble_threshold": 0.4,
"freqai_weight": 0.5,
}
@property
def sell_params(self):
return {
"exit_fastk_threshold": 88,
"exit_rsi_threshold": 78,
"exit_cti_threshold": 0.6,
"exit_profit_threshold": 0.028,
"trailing_stop_positive": 0.198,
"trailing_stop_positive_offset": 0.212
}