# --- Do not remove these libs --- from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from pathlib import Path import logging import time current_time = time.time() import datetime from datetime import datetime, timedelta, timezone import numpy as np import pandas as pd pd.options.mode.chained_assignment = None from pandas import DataFrame, Series from technical.util import resample_to_interval, resampled_merge from freqtrade.strategy import (IStrategy, BooleanParameter, CategoricalParameter, DecimalParameter, IntParameter, RealParameter, merge_informative_pair, stoploss_from_open, stoploss_from_absolute, merge_informative_pair) from freqtrade.persistence import Trade from typing import List, Tuple, Optional, Dict, Any from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal import json import os # -------------------------------- # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from collections import deque import optuna from optuna.samplers import TPESampler from optuna.exceptions import OptunaError import warnings warnings.simplefilter(action="ignore", category=pd.errors.PerformanceWarning) logger = logging.getLogger(__name__) ASCII_ART = ''' ╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ ██████╗ ██╗ ███████╗██╗ ██╗ ██████╗██████╗ ██╗ ██╗██████╗ ████████╗ ██████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗ ║ ║ ██╔══██╗██║ ██╔════╝╚██╗██╔╝██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝██╔═══██╗██║ ██╔╝██║████╗ ██║██╔════╝ ║ ║ ███████║██║ █████╗ ╚███╔╝ ██║ ██████╔╝ ╚████╔╝ ██████╔╝ ██║ ██║ ██║█████╔╝ ██║██╔██╗ ██║██║ ███╗ ║ ║ ██╔══██║██║ ██╔══╝ ██╔██╗ ██║ ██╔══██╗ ╚██╔╝ ██╔═══╝ ██║ ██║ ██║██╔═██╗ ██║██║╚██╗██║██║ ██║ ║ ║ ██║ ██║███████╗███████╗██╔╝ ██╗╚██████╗██║ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██╗██║██║ ╚████║╚██████╔╝ ║ ║ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ║ ║ ║ ║ PURE OPTUNA OPTIMIZING STRATEGY ║ ║ Enhanced Multi-Feature Optimazion ║ ║ Machine Learning Powered ║ ║ ║ ╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ ''' # FIX: Remove the problematic relative import and create a simple inline replacement # from .optuna_manager import OptunaManager class RealOptunaManager: """Real Optuna-based optimization manager""" def __init__(self, strategy_name: str): self.strategy_name = strategy_name self.studies = {} # Store studies per pair self.best_params_cache = {} self.performance_history = {} self.optimization_trigger_count = {} self.pretraining_enabled = True self.pretraining_days = 14 self.pretraining_done = {} logger.info(f"📊 [OPTUNA] Initializing RealOptunaManager for strategy: {strategy_name}") logger.info(f"🎯 [OPTUNA] Real Optuna optimization enabled") def get_best_params(self, pair: str) -> Dict[str, Any]: """Get best parameters for a pair""" if pair in self.best_params_cache: return self.best_params_cache[pair] # Try to load from existing study try: study_name = f"{self.strategy_name}_{pair.replace('/', '_').replace(':', '_')}" study = optuna.load_study( study_name=study_name, storage=f"sqlite:///user_data/strategies/optuna_studies/optuna_{study_name}.db" ) if study.trials: self.studies[pair] = study self.best_params_cache[pair] = study.best_params return study.best_params except: pass return None def should_optimize(self, pair: str) -> bool: """Check if optimization should be triggered""" if pair not in self.best_params_cache: return True if pair in self.performance_history: recent_performance = self.performance_history[pair][-10:] if len(recent_performance) >= 5: avg_performance = sum(recent_performance) / len(recent_performance) if avg_performance < -0.02: return True return False def should_optimize_based_on_performance(self, pair: str) -> bool: """Check if performance-based optimization is needed""" if pair not in self.performance_history: return False recent_trades = self.performance_history[pair][-10:] if len(recent_trades) >= 5: avg_performance = sum(recent_trades) / len(recent_trades) return avg_performance < -0.015 return False def update_performance(self, pair: str, profit_ratio: float): """Update performance tracking""" if pair not in self.performance_history: self.performance_history[pair] = [] self.performance_history[pair].append(profit_ratio) if len(self.performance_history[pair]) > 50: self.performance_history[pair] = self.performance_history[pair][-50:] logger.debug(f"📈 [OPTUNA] Updated performance for {pair}: {profit_ratio:.4f}") def optimize_coin(self, pair: str, objective_func, n_trials: int = 10): """Optimize parameters for a specific coin using real Optuna""" logger.info(f"🚀 [OPTUNA] Starting REAL optimization for {pair} with {n_trials} trials") try: optuna.logging.set_verbosity(optuna.logging.WARNING) from pathlib import Path study_name = f"{self.strategy_name}_{pair.replace('/', '_').replace(':', '_')}" Path("user_data/strategies/optuna_studies").mkdir(parents=True, exist_ok=True) study = optuna.create_study( study_name=study_name, direction='maximize', sampler=TPESampler(), storage=f"sqlite:///user_data/strategies/optuna_studies/optuna_{study_name}.db", load_if_exists=True ) study.optimize(objective_func, n_trials=n_trials) self.studies[pair] = study self.best_params_cache[pair] = study.best_params logger.info(f"✅ [OPTUNA] REAL optimization completed for {pair}") logger.info(f"🏆 [OPTUNA] Best value: {study.best_value:.4f}") self._log_formatted_parameters(pair, study.best_params) return True except Exception as e: logger.error(f"❌ [OPTUNA] Real optimization failed for {pair}: {e}") return False def _log_formatted_parameters(self, pair: str, params: Dict[str, Any]): """Log parameters in a nicely formatted way - Enhanced with all optimized parameters""" logger.info(f"📈 [OPTUNA] Optimized parameters for {pair}:") # === INDICATOR PARAMETERS === logger.info(f"┌─ 📊 TECHNICAL INDICATOR PARAMETERS") logger.info(f"│ • rsi_period: {params.get('rsi_period', 'N/A')}") logger.info(f"│ • willr_period: {params.get('willr_period', 'N/A')}") logger.info(f"│ • cci_period: {params.get('cci_period', 'N/A')}") logger.info(f"│ • bb_period: {params.get('bb_period', 'N/A')}") logger.info(f"│ • bb_std: {params.get('bb_std', 'N/A')}") logger.info(f"│ • macd_fast/slow/signal: {params.get('macd_fast', 'N/A')}/{params.get('macd_slow', 'N/A')}/{params.get('macd_signal', 'N/A')}") logger.info(f"│ • ema_short/long: {params.get('ema_short_period', 'N/A')}/{params.get('ema_long_period', 'N/A')}") logger.info(f"│ • volume_sma_period: {params.get('volume_sma_period', 'N/A')}") logger.info(f"│ • atr_period: {params.get('atr_period', 'N/A')}") logger.info(f"│ • pivot_window: {params.get('pivot_window', 'N/A')}") logger.info(f"│ • swing_period: {params.get('swing_period', 'N/A')}") # === CORE ENTRY PARAMETERS === logger.info(f"├─ 🎯 CORE ENTRY PARAMETERS") logger.info(f"│ • min_divergence_count: {params.get('min_divergence_count', 'N/A')}") logger.info(f"│ • min_signal_strength: {params.get('min_signal_strength', 'N/A')}") logger.info(f"│ • volume_threshold: {params.get('volume_threshold', 'N/A')}") logger.info(f"│ • volume_factor: {params.get('volume_factor', 'N/A')}") logger.info(f"│ • atr_multiplier: {params.get('atr_multiplier', 'N/A')}") logger.info(f"│ • adx_threshold: {params.get('adx_threshold', 'N/A')}") logger.info(f"│ • max_volatility: {params.get('max_volatility', 'N/A')}") logger.info(f"│ • min_volatility: {params.get('min_volatility', 'N/A')}") # === RSI PARAMETERS === logger.info(f"├─ 📈 RSI TIMING PARAMETERS") logger.info(f"│ • rsi_overbought: {params.get('rsi_overbought', 'N/A')}") logger.info(f"│ • rsi_oversold: {params.get('rsi_oversold', 'N/A')}") logger.info(f"│ • rsi_long_upper_tight: {params.get('rsi_long_upper_tight', 'N/A')}") logger.info(f"│ • rsi_short_lower_tight: {params.get('rsi_short_lower_tight', 'N/A')}") logger.info(f"│ • rsi_recovery_periods: {params.get('rsi_recovery_periods', 'N/A')}") # === VOLUME PARAMETERS === logger.info(f"├─ 📊 VOLUME PARAMETERS") logger.info(f"│ • volume_ratio_min: {params.get('volume_ratio_min', 'N/A')}") logger.info(f"│ • volume_trend_multiplier: {params.get('volume_trend_multiplier', 'N/A')}") logger.info(f"│ • volume_momentum_multiplier: {params.get('volume_momentum_multiplier', 'N/A')}") logger.info(f"│ • volume_breakout_multiplier: {params.get('volume_breakout_multiplier', 'N/A')}") logger.info(f"│ • volume_breakout_rsi_min/max: {params.get('volume_breakout_rsi_min', 'N/A')}/{params.get('volume_breakout_rsi_max', 'N/A')}") # === POSITION TIMING === logger.info(f"├─ ⏰ POSITION TIMING") logger.info(f"│ • bb_percent_long_max: {params.get('bb_percent_long_max', 'N/A')}") logger.info(f"│ • bb_percent_short_min: {params.get('bb_percent_short_min', 'N/A')}") logger.info(f"│ • price_position_long_max: {params.get('price_position_long_max', 'N/A')}") logger.info(f"│ • price_position_short_min: {params.get('price_position_short_min', 'N/A')}") # === MARKET STRUCTURE === logger.info(f"├─ 🏗️ MARKET STRUCTURE") logger.info(f"│ • adx_trending_min: {params.get('adx_trending_min', 'N/A')}") logger.info(f"│ • chop_threshold: {params.get('chop_threshold', 'N/A')}") logger.info(f"│ • min_oversold_conditions: {params.get('min_oversold_conditions', 'N/A')}") logger.info(f"│ • min_overbought_conditions: {params.get('min_overbought_conditions', 'N/A')}") logger.info(f"│ • min_reversal_signals: {params.get('min_reversal_signals', 'N/A')}") logger.info(f"│ • min_trend_filters: {params.get('min_trend_filters', 'N/A')}") # === MEAN REVERSION PARAMETERS === logger.info(f"├─ 🔄 MEAN REVERSION PARAMETERS") logger.info(f"│ • bb_oversold_threshold: {params.get('bb_oversold_threshold', 'N/A')}") logger.info(f"│ • bb_overbought_threshold: {params.get('bb_overbought_threshold', 'N/A')}") logger.info(f"│ • mean_reversion_rsi_oversold: {params.get('mean_reversion_rsi_oversold', 'N/A')}") logger.info(f"│ • mean_reversion_rsi_overbought: {params.get('mean_reversion_rsi_overbought', 'N/A')}") # === MOMENTUM CONTINUATION PARAMETERS === logger.info(f"├─ 🚀 MOMENTUM CONTINUATION PARAMETERS") logger.info(f"│ • momentum_ema_periods: {params.get('momentum_ema_periods', 'N/A')}") logger.info(f"│ • momentum_strength_min: {params.get('momentum_strength_min', 'N/A')}") logger.info(f"│ • momentum_pullback_max: {params.get('momentum_pullback_max', 'N/A')}") logger.info(f"│ • momentum_pullback_min: {params.get('momentum_pullback_min', 'N/A')}") # === EXIT PARAMETERS === logger.info(f"├─ 🎯 GLOBAL EXIT SETTINGS") logger.info(f"│ • session_multiplier_overlap: {params.get('session_multiplier_overlap', 'N/A')}") logger.info(f"│ • session_multiplier_major: {params.get('session_multiplier_major', 'N/A')}") logger.info(f"│ • volatility_sensitivity: {params.get('volatility_sensitivity', 'N/A')}") logger.info(f"│ • high_signal_multiplier: {params.get('high_signal_multiplier', 'N/A')}") logger.info(f"│ • low_signal_multiplier: {params.get('low_signal_multiplier', 'N/A')}") logger.info(f"├─ 🔄 MEAN REVERSION (MR1) EXITS") logger.info(f"│ • mr1_max_hold_minutes: {params.get('mr1_max_hold_minutes', 'N/A')} min") logger.info(f"│ • mr1_rsi_exit_long/short: {params.get('mr1_rsi_exit_long', 'N/A')}/{params.get('mr1_rsi_exit_short', 'N/A')}") logger.info(f"│ • mr1_quick_profit_target: {params.get('mr1_quick_profit_target', 'N/A')}") logger.info(f"│ • mr1_timeout_min_profit: {params.get('mr1_timeout_min_profit', 'N/A')}") logger.info(f"├─ 🚀 MOMENTUM CONTINUATION (MC1) EXITS") logger.info(f"│ • mc1_profit_target: {params.get('mc1_profit_target', 'N/A')}") logger.info(f"│ • mc1_max_hold_minutes: {params.get('mc1_max_hold_minutes', 'N/A')} min") logger.info(f"│ • mc1_adx_exit_threshold: {params.get('mc1_adx_exit_threshold', 'N/A')}") logger.info(f"│ • mc1_timeout_min_profit: {params.get('mc1_timeout_min_profit', 'N/A')}") logger.info(f"├─ 📊 VOLUME BREAKOUT (VB1) EXITS") logger.info(f"│ • vb1_volume_fade_threshold: {params.get('vb1_volume_fade_threshold', 'N/A')}") logger.info(f"│ • vb1_profit_target: {params.get('vb1_profit_target', 'N/A')}") logger.info(f"│ • vb1_quick_profit: {params.get('vb1_quick_profit', 'N/A')}") logger.info(f"├─ 🔄 REVERSAL (RSV1) EXITS") logger.info(f"│ • rsv1_rsi_recovery_threshold: {params.get('rsv1_rsi_recovery_threshold', 'N/A')}") logger.info(f"│ • rsv1_profit_target: {params.get('rsv1_profit_target', 'N/A')}") logger.info(f"│ • rsv1_max_hold_minutes: {params.get('rsv1_max_hold_minutes', 'N/A')} min") logger.info(f"│ • rsv1_min_timeout_profit: {params.get('rsv1_min_timeout_profit', 'N/A')}") logger.info(f"├─ 📈 TREND FOLLOWING EXITS") logger.info(f"│ • trend_profit_target: {params.get('trend_profit_target', 'N/A')}") logger.info(f"│ • trend_ema_break_periods: {params.get('trend_ema_break_periods', 'N/A')}") logger.info(f"├─ ⏰ UNIVERSAL EXIT PARAMETERS") logger.info(f"│ • medium_term_base_target: {params.get('medium_term_base_target', 'N/A')}") logger.info(f"│ • rsi_exit_overbought/oversold: {params.get('rsi_exit_overbought', 'N/A')}/{params.get('rsi_exit_oversold', 'N/A')}") logger.info(f"│ • momentum_fade_threshold: {params.get('momentum_fade_threshold', 'N/A')}") logger.info(f"├─ 🕐 EXTENDED DURATION MANAGEMENT") logger.info(f"│ • extended_2hr_target: {params.get('extended_2hr_target', 'N/A')}") logger.info(f"│ • extended_3hr_target: {params.get('extended_3hr_target', 'N/A')}") logger.info(f"│ • extended_5hr_target: {params.get('extended_5hr_target', 'N/A')}") logger.info(f"├─ 🛡️ MARKET PROTECTION") logger.info(f"│ • friday_close_min_profit: {params.get('friday_close_min_profit', 'N/A')}") logger.info(f"│ • overnight_min_profit: {params.get('overnight_min_profit', 'N/A')}") logger.info(f"│ • low_liquidity_min_profit: {params.get('low_liquidity_min_profit', 'N/A')}") # === RSV1 SPECIFIC PARAMETERS === logger.info(f"├─ 🔄 REVERSAL (RSV1) ENTRY PARAMETERS") logger.info(f"│ • rsv1_rsi_oversold/overbought: {params.get('rsv1_rsi_oversold', 'N/A')}/{params.get('rsv1_rsi_overbought', 'N/A')}") logger.info(f"│ • rsv1_willr_oversold/overbought: {params.get('rsv1_willr_oversold', 'N/A')}/{params.get('rsv1_willr_overbought', 'N/A')}") logger.info(f"│ • rsv1_cci_oversold/overbought: {params.get('rsv1_cci_oversold', 'N/A')}/{params.get('rsv1_cci_overbought', 'N/A')}") logger.info(f"│ • rsv1_volume_factor: {params.get('rsv1_volume_factor', 'N/A')}") logger.info(f"│ • rsv1_min_conditions: {params.get('rsv1_min_oversold_conditions', 'N/A')}/{params.get('rsv1_min_overbought_conditions', 'N/A')}") # === ADVANCED ENTRY PARAMETERS === logger.info(f"├─ 🎯 E1 PRIMARY ENTRY PARAMETERS") logger.info(f"│ • e1_rsi_thresholds: {params.get('e1_rsi_oversold_threshold', 'N/A')}-{params.get('e1_rsi_overbought_threshold', 'N/A')}") logger.info(f"│ • e1_rsi_buffers: {params.get('e1_rsi_oversold_buffer', 'N/A')}/{params.get('e1_rsi_overbought_buffer', 'N/A')}") logger.info(f"│ • volatility_multipliers: {params.get('volatility_min_multiplier', 'N/A')}-{params.get('volatility_max_multiplier', 'N/A')}") logger.info(f"├─ 📈 TREND BREAKOUT PARAMETERS") logger.info(f"│ • trend_volume_window/multiplier: {params.get('trend_volume_window', 'N/A')}/{params.get('trend_volume_multiplier', 'N/A')}") logger.info(f"│ • trend_rsi_range: {params.get('trend_rsi_min', 'N/A')}-{params.get('trend_rsi_max', 'N/A')}") logger.info(f"│ • trend_adx_min: {params.get('trend_adx_min', 'N/A')}") logger.info(f"├─ 🚀 MOMENTUM ENTRY PARAMETERS") logger.info(f"│ • momentum_rsi_pivot/trigger: {params.get('momentum_rsi_pivot', 'N/A')}/{params.get('momentum_rsi_trigger', 'N/A')}") logger.info(f"│ • momentum_volume_multiplier: {params.get('momentum_volume_multiplier', 'N/A')}") logger.info(f"│ • momentum_adx_min: {params.get('momentum_adx_min', 'N/A')}") # === SUPPORT/RESISTANCE & BREAKDOWN === logger.info(f"├─ 📉 BREAKDOWN/BREAKOUT PARAMETERS") logger.info(f"│ • breakdown_volume_threshold: {params.get('breakdown_volume_threshold', 'N/A')}") logger.info(f"│ • breakdown_adx_threshold: {params.get('breakdown_adx_threshold', 'N/A')}") logger.info(f"│ • breakdown_rsi_range: {params.get('breakdown_rsi_lower', 'N/A')}-{params.get('breakdown_rsi_upper', 'N/A')}") logger.info(f"│ • support_tolerance: {params.get('support_tolerance', 'N/A')}") logger.info(f"│ • resistance_tolerance: {params.get('resistance_tolerance', 'N/A')}") # === SECONDARY/TERTIARY ENTRIES === logger.info(f"├─ 📊 SECONDARY/TERTIARY ENTRIES") logger.info(f"│ • secondary_rsi_range: {params.get('secondary_rsi_min', 'N/A')}-{params.get('secondary_rsi_max', 'N/A')}") logger.info(f"│ • tertiary_volume_multiplier: {params.get('tertiary_volume_multiplier', 'N/A')}") logger.info(f"│ • sixth_min_div_count: {params.get('sixth_min_div_count', 'N/A')}") logger.info(f"│ • seventh_atr_multiplier: {params.get('seventh_atr_multiplier', 'N/A')}") # === SUMMARY === logger.info(f"├─ 📋 OPTIMIZATION SUMMARY") entry_params = len([k for k in params.keys() if any(x in k for x in ['e1_', 'trend_', 'momentum_', 'secondary_', 'tertiary_', 'rsv1_rsi_', 'breakdown_', 'div_'])]) exit_params = len([k for k in params.keys() if any(x in k for x in ['mr1_', 'mc1_', 'vb1_', 'rsv1_profit', 'extended_', 'session_'])]) indicator_params = len([k for k in params.keys() if any(x in k for x in ['rsi_period', 'bb_period', 'macd_', 'ema_', 'volume_sma', 'atr_period'])]) logger.info(f"│ • Entry parameters optimized: {entry_params}") logger.info(f"│ • Exit parameters optimized: {exit_params}") logger.info(f"│ • Indicator parameters optimized: {indicator_params}") logger.info(f"│ • Total parameters in study: {len(params)}") logger.info(f"└─ 🎯 Strategy fully optimized with {len(params)} parameters") logger.info(f"📊 [OPTUNA] Comprehensive optimization completed for {pair}") def pretrain_with_historical_data(self, pair: str, strategy_instance) -> bool: """Stub for compatibility - real Optuna doesn't need pre-training""" return False class PlotConfig(): def __init__(self): self.config = { 'main_plot': { # Try direct column names first to test 'bollinger_upperband': {'color': 'rgba(4,137,122,0.7)'}, 'kc_upperband': {'color': 'rgba(4,146,250,0.7)'}, 'kc_middleband': {'color': 'rgba(4,146,250,0.7)'}, 'kc_lowerband': {'color': 'rgba(4,146,250,0.7)'}, 'bollinger_lowerband': { 'color': 'rgba(4,137,122,0.7)', 'fill_to': 'bollinger_upperband', 'fill_color': 'rgba(4,137,122,0.07)' }, 'ema9': {'color': 'purple'}, 'ema20': {'color': 'yellow'}, 'ema50': {'color': 'red'}, 'ema200': {'color': 'white'}, 'trend_1h_1h': {'color': 'orange'}, }, 'subplots': { "RSI": { 'rsi': {'color': 'green'} }, "ATR": { 'atr': {'color': 'firebrick'} }, "Signal Strength": { 'signal_strength': {'color': 'blue'} } } } def add_total_divergences_in_config(self, dataframe): # Test if columns exist before adding them if 'total_bullish_divergences' in dataframe.columns: self.config['main_plot']['total_bullish_divergences'] = { "plotly": { 'mode': 'markers', 'marker': { 'symbol': 'diamond', 'size': 11, 'color': 'green' } } } if 'total_bearish_divergences' in dataframe.columns: self.config['main_plot']['total_bearish_divergences'] = { "plotly": { 'mode': 'markers', 'marker': { 'symbol': 'diamond', 'size': 11, 'color': 'crimson' } } } return self class AlexBandSniperV100CO(IStrategy): """ ╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ ██████╗ ██╗ ███████╗██╗ ██╗ ██████╗██████╗ ██╗ ██╗██████╗ ████████╗ ██████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗ ║ ║ ██╔══██╗██║ ██╔════╝╚██╗██╔╝██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝██╔═══██╗██║ ██╔╝██║████╗ ██║██╔════╝ ║ ║ ███████║██║ █████╗ ╚███╔╝ ██║ ██████╔╝ ╚████╔╝ ██████╔╝ ██║ ██║ ██║█████╔╝ ██║██╔██╗ ██║██║ ███╗ ║ ║ ██╔══██║██║ ██╔══╝ ██╔██╗ ██║ ██╔══██╗ ╚██╔╝ ██╔═══╝ ██║ ██║ ██║██╔═██╗ ██║██║╚██╗██║██║ ██║ ║ ║ ██║ ██║███████╗███████╗██╔╝ ██╗╚██████╗██║ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██╗██║██║ ╚████║╚██████╔╝ ║ ║ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ║ ║ ║ ║ Advanced RL + Optuna Trading Strategy ║ ║ Enhanced Multi-Pair Optimization ║ ║ Machine Learning Powered ║ ║ ║ ╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ Alex BandSniper on 15m Timeframe - OPTIMIZED VERSION WITH OPTUNA INTEGRATION Version 106CO-Optuna - Claude optimized Entry & Exit with Optuna Management # CHANGED: Versionshinweis angepasst Key improvements: - Fixed Entry Tags Assignment - Removed - Bull Pullbak Momentum & Bear Momentum Breakout - Bull Pullbak Momentum & Bear Momentum Breakout - Removed RSV1 Signals because of worse results - Added better Custom_Exit - Added Signals from V52C - Enabled ROI and TRAILING - Dynamic Trailing & New Custom Exits - Integrated OPTUNA Fully - Opimizing Daily Optimizing - Enhanced Parameters for Optuna - Optuna integration - Fixed ROI and Trailing adjusted Custom Exits - Fixed Entry Signals - Included 1h Informative Timeframe - Multi-timeframe analysis (1h trend confirmation) - Enhanced signal filtering with minimum divergence counts - Volume and volatility filters - Adaptive position sizing based on signal strength - Improved risk management - ADDED: Optuna-based parameter optimization per coin # ADDED: Neuer Kommentar - ADDED: Historical pre-training for better startup performance # ADDED: Neuer Kommentar - ADDED: Dynamic parameter adjustment based on performance # ADDED: Neuer Kommentar """ INTERFACE_VERSION = 3 def version(self) -> str: return "v5-Optuna" class HyperOpt: # Define a custom stoploss space. def stoploss_space(): return [SKDecimal(-0.15, -0.03, decimals=2, name='stoploss')] # Define a custom max_open_trades space def max_open_trades_space() -> List[Dimension]: return [ Integer(3, 8, name='max_open_trades'), ] def trailing_space() -> List[Dimension]: return [ Categorical([True], name='trailing_stop'), SKDecimal(0.02, 0.3, decimals=2, name='trailing_stop_positive'), SKDecimal(0.03, 0.1, decimals=2, name='trailing_stop_positive_offset_p1'), Categorical([True, False], name='trailing_only_offset_is_reached'), ] # Minimal ROI designed for the strategy. minimal_roi = { "0": 0.15, # 15% initial target (let winners run!) "20": 0.08, # After 20 min, 8% "45": 0.05, # After 45 min, 5% "90": 0.03, # After 1.5 hours, 3% "180": 0.02, # After 3 hours, 2% "360": 0.01 # After 6 hours, 1% } # Optimal stoploss designed for the strategy. stoploss = -0.20 can_short = True use_custom_stoploss = False leverage_value = 10.0 # Reduced leverage for better risk management #trailing_stop = True #trailing_stop_positive = 0.025 # Start trailing at 2.5% profit #trailing_stop_positive_offset = 0.035 # Trail 0.8% behind peak #trailing_stop = False #trailing_stop_positive = 0.40 # Only trail after 40% profit (very high) #trailing_stop_positive_offset = 0.45 # Start trailing at 45% profit #trailing_only_offset_is_reached = True # Optimal timeframe for the strategy. timeframe = '15m' timeframe_minutes = timeframe_to_minutes(timeframe) # Run "populate_indicators()" only for new candle. process_only_new_candles = True # These values can be overridden in the "exit_pricing" section in the config. use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = False use_custom_exits = True use_entry_signal = True # In your hyperopt parameters, consider these more permissive defaults: min_divergence_count = IntParameter(1, 3, default=1, space='buy', optimize=True, load=True) # Reduced from 2-5 min_signal_strength = IntParameter(1, 5, default=1, space='buy', optimize=True, load=True) # Reduced from 3-10 volume_threshold = DecimalParameter(1.0, 1.5, default=1.0, decimals=1, space='buy', optimize=True, load=True) # Reduced from 1.1-2.5 # Make ADX less restrictive adx_threshold = IntParameter(15, 30, default=15, space='buy', optimize=True, load=True) # Reduced from 25-45 # Market Condition Filters rsi_overbought = DecimalParameter(65.0, 85.0, default=80.0, decimals=1, space='buy', optimize=True, load=True) rsi_oversold = DecimalParameter(15.0, 35.0, default=15.0, decimals=1, space='buy', optimize=True, load=True) # Volatility Filters max_volatility = DecimalParameter(0.015, 0.035, default=0.025, decimals=3, space='buy', optimize=True, load=True) min_volatility = DecimalParameter(0.003, 0.008, default=0.005, decimals=3, space='buy', optimize=True, load=True) # Exit Parameters rsi_exit_overbought = DecimalParameter(70.0, 90.0, default=80.0, decimals=1, space='sell', optimize=True, load=True) rsi_exit_oversold = DecimalParameter(10.0, 30.0, default=20.0, decimals=1, space='sell', optimize=True, load=True) adx_exit_threshold = IntParameter(15, 30, default=20, space='sell', optimize=True, load=True) # Trend Confirmation Parameters trend_strength_threshold = IntParameter(20, 40, default=25, space='buy', optimize=True, load=True) # Technical Parameters window = IntParameter(3, 6, default=4, space="buy", optimize=True, load=True) index_range = IntParameter(20, 50, default=30, space='buy', optimize=True, load=True) # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 10 # Protection parameters cooldown_lookback = IntParameter(2, 48, default=1, space="protection", optimize=True) stop_duration = IntParameter(12, 200, default=20, space="protection", optimize=True) use_stop_protection = BooleanParameter(default=False, space="protection", optimize=True) use_cooldown_protection = BooleanParameter(default=False, space="protection", optimize=True) # Enhanced protection parameters use_max_drawdown_protection = BooleanParameter(default=False, space="protection", optimize=True) max_drawdown_lookback = IntParameter(100, 300, default=200, space="protection", optimize=True) max_drawdown_trade_limit = IntParameter(5, 15, default=10, space="protection", optimize=True) max_drawdown_stop_duration = IntParameter(1, 5, default=1, space="protection", optimize=True) max_allowed_drawdown = DecimalParameter(0.08, 0.25, default=0.15, decimals=2, space="protection", optimize=True) stoploss_guard_lookback = IntParameter(30, 80, default=50, space="protection", optimize=True) stoploss_guard_trade_limit = IntParameter(2, 6, default=3, space="protection", optimize=True) stoploss_guard_only_per_pair = BooleanParameter(default=True, space="protection", optimize=True) # Optional order type mapping. order_types = { 'entry': 'market', 'exit': 'market', 'stoploss': 'limit', 'stoploss_on_exchange': True } # Optional order time in force. order_time_in_force = { 'entry': 'gtc', 'exit': 'gtc' } plot_config = None def __init__(self, config: dict): # Signal Performance Tracking self.signal_performance_file = Path("user_data/strategies/AlexBandSniper_Signal_Performance.json") self.signal_performance = self.load_signal_performance() self.signal_locks = {} # Configurable parameters - add these to your get_default_params() self.performance_window = 15 # Trades to evaluate over self.lock_duration_hours = 12 # Hours to lock signal self.min_win_rate = 0.35 # Minimum win rate (35%) self.min_avg_profit = -0.015 # Minimum avg profit (-1.5%) self.min_trades_for_eval = 8 # Minimum trades before evaluation super().__init__(config) print(ASCII_ART, flush=True) logger.info(f"🚀 [STRATEGY] Initializing {self.__class__.__name__}") # AUTO-UPDATED: Klassenname wird automatisch angepasst # Initialize simple Optuna manager replacement try: self.optuna_manager = RealOptunaManager("AlexBandSniperV96CO") # CHANGED: Strategy name angepasst logger.info(f"✅ [OPTUNA] Successfully initialized RealOptunaManager") except Exception as e: logger.error(f"❌ [OPTUNA] Failed to initialize RealOptunaManager: {e}") self.optuna_manager = None # Per-coin optimized parameters self.coin_params: Dict[str, Dict] = {} # ADDED: Dictionary für coin-spezifische Parameter # Track performance for optimization self.coin_performance: Dict[str, float] = {} # ADDED: Performance-Tracking pro Coin # Optimization settings - CHANGED: Enable proactive optimization self.enable_dynamic_optimization = True # ADDED: Dynamische Optimierung aktiviert (deaktivieren für Backtest) self.optimization_min_trades = 0 # CHANGED: No minimum trades required for startup optimization logger.info(f"⚙️ [STRATEGY] Dynamic optimization: {'ENABLED' if self.enable_dynamic_optimization else 'DISABLED'}") logger.info(f"⚙️ [STRATEGY] Minimum trades for optimization: {self.optimization_min_trades}") logger.info(f"✅ [STRATEGY] {self.__class__.__name__} initialization completed") # ...existing code... def get_coin_params(self, pair: str) -> Dict[str, Any]: """Get optimized parameters for specific coin using real Optuna""" if pair not in self.coin_params: logger.debug(f"🔍 [OPTUNA] Loading parameters for new pair: {pair}") # Check if optimization is disabled first (for backtesting) if not self.enable_dynamic_optimization: logger.info(f"⏸️ [OPTUNA] Dynamic optimization DISABLED for {pair} - using default parameters") self.coin_params[pair] = self.get_default_params() return self.coin_params[pair] # Real Optuna optimization - only if enabled if self.enable_dynamic_optimization and self.optuna_manager: logger.info(f"🚀 [OPTUNA] Real Optuna will optimize {pair} based on actual trade results") if self.optuna_manager: best_params = self.optuna_manager.get_best_params(pair) if best_params: self.coin_params[pair] = best_params logger.info(f"✅ [OPTUNA] Loaded optimized parameters for {pair}: {len(best_params)} params") logger.debug(f"📊 [OPTUNA] Parameters from study with {len(self.optuna_manager.studies.get(pair, {}).trials) if pair in self.optuna_manager.studies else 0} trials") else: self.coin_params[pair] = self.get_default_params() logger.info(f"🔧 [OPTUNA] Using default parameters for {pair} (no study found - will optimize after trades)") # ADD THIS SECTION: Force startup optimization for new pairs if self.enable_dynamic_optimization and self.optuna_manager: logger.info(f"🎯 [OPTUNA] Triggering STARTUP OPTIMIZATION for new pair: {pair}") try: # Call maybe_optimize_coin with force_startup=True self.maybe_optimize_coin(pair, force_startup=True) # After optimization, try to reload the optimized parameters optimized_params = self.optuna_manager.get_best_params(pair) if optimized_params: self.coin_params[pair] = optimized_params logger.info(f"🎉 [OPTUNA] Successfully loaded STARTUP optimized parameters for {pair}") else: logger.warning(f"⚠️ [OPTUNA] Startup optimization completed but no parameters returned for {pair}") except Exception as e: logger.error(f"❌ [OPTUNA] Startup optimization failed for {pair}: {e}") # Keep using default parameters if optimization fails else: self.coin_params[pair] = self.get_default_params() logger.warning(f"⚠️ [OPTUNA] RealOptunaManager not available, using default parameters for {pair}") # ENSURE ALL PARAMETERS ARE PRESENT default_params = self.get_default_params() current_params = self.coin_params.get(pair, {}) # Merge defaults with current params (current params override defaults) complete_params = {**default_params, **current_params} self.coin_params[pair] = complete_params logger.debug(f"♻️ [OPTUNA] Using complete parameter set for {pair}") return self.coin_params[pair] def get_default_params(self) -> Dict[str, Any]: """Get enhanced default parameters including exit optimization""" return { # === EXISTING ENTRY PARAMETERS === 'min_divergence_count': 1, # Default fallback only 'min_signal_strength': 1, # Default fallback only 'volume_threshold': 1.0, # Default fallback only 'adx_threshold': 15, # Default fallback only 'rsi_overbought': 80.0, # Default fallback only 'rsi_oversold': 15.0, # Default fallback only 'max_volatility': 0.025, # Default fallback only 'min_volatility': 0.005, # Default fallback only # === ADD THESE MISSING INDICATOR PARAMETERS === # Technical Indicator Periods 'volume_sma_period': 20, # Volume SMA calculation 'atr_period': 14, # ATR calculation 'rsi_period': 14, # RSI period (if not already defined) 'willr_period': 14, # Williams %R period 'cci_period': 20, # CCI period 'mom_period': 10, # Momentum period # Bollinger Bands Parameters 'bb_period': 20, # Bollinger Bands period 'bb_std': 2.0, # Bollinger Bands standard deviation # EMA Parameters 'ema_short_period': 8, # Short EMA period 'ema_long_period': 21, # Long EMA period # MACD Parameters 'macd_fast': 12, # MACD fast period 'macd_slow': 26, # MACD slow period 'macd_signal': 9, # MACD signal period # Stochastic Parameters 'stoch_k': 14, # Stochastic K period 'stoch_d': 3, # Stochastic D period # Market Structure Parameters 'pivot_window': 10, # Pivot points window 'swing_period': 50, # Support/Resistance swing period 'recent_high_low_period': 5, # Recent highs/lows period 'rolling_20_period': 20, # 20-period rolling calculations 'rolling_10_period': 10, # 10-period rolling calculations # Market Analysis Parameters 'chop_period': 14, # Choppiness index period 'natr_period': 14, # NATR period 'cmf_period': 20, # Chaikin Money Flow period # === EXISTING ENTRY TIMING PARAMETERS === 'rsi_recovery_periods': 3, 'rsi_long_upper_tight': 65.0, 'rsi_short_lower_tight': 35.0, 'volume_ratio_min': 1.1, 'volume_trend_multiplier': 1.5, 'volume_momentum_multiplier': 1.2, 'bb_percent_long_max': 0.25, 'bb_percent_short_min': 0.75, 'price_position_long_max': 0.3, 'price_position_short_min': 0.7, 'divergence_freshness_periods': 5, 'min_strong_divergence_count': 2, 'min_oversold_conditions': 3, 'min_overbought_conditions': 3, 'min_reversal_signals': 2, 'min_trend_filters': 1, 'chop_threshold': 61.8, 'adx_trending_min': 25, 'volume_breakout_multiplier': 2.5, 'volume_breakout_rsi_min': 45.0, 'volume_breakout_rsi_max': 60.0, 'bb_oversold_threshold': 0.15, 'bb_overbought_threshold': 0.85, 'mean_reversion_rsi_oversold': 28.0, 'mean_reversion_rsi_overbought': 72.0, 'momentum_ema_periods': 9, 'momentum_strength_min': 0.025, 'momentum_pullback_max': 0.35, 'momentum_pullback_min': 0.65, # === NEW EXIT PARAMETERS === # Global Exit Settings 'emergency_profit_limit': 0.25, 'session_multiplier_overlap': 1.3, 'session_multiplier_major': 1.15, 'session_multiplier_quiet': 0.85, 'volatility_sensitivity': 40, 'high_signal_multiplier': 1.2, 'low_signal_multiplier': 0.8, # Mean Reversion (MR1) Exit Parameters 'mr1_max_hold_minutes': 35, 'mr1_rsi_exit_long': 68.0, 'mr1_rsi_exit_short': 32.0, 'mr1_quick_profit_target': 0.025, 'mr1_timeout_min_profit': 0.008, 'mr1_bb_exit_long': 0.7, 'mr1_bb_exit_short': 0.3, # Momentum Continuation (MC1) Exit Parameters 'mc1_profit_target': 0.04, 'mc1_momentum_break_threshold': 0.015, 'mc1_max_hold_minutes': 45, 'mc1_adx_exit_threshold': 22, 'mc1_timeout_min_profit': 0.01, # Volume Breakout (VB1) Exit Parameters 'vb1_volume_fade_threshold': 1.15, 'vb1_profit_target': 0.03, 'vb1_quick_profit': 0.02, 'vb1_min_volatility': 0.012, # Reversal (RSV1) Exit Parameters 'rsv1_rsi_recovery_threshold': 58.0, 'rsv1_profit_target': 0.035, 'rsv1_max_hold_minutes': 60, 'rsv1_min_timeout_profit': 0.01, # Trend Following Exit Parameters 'trend_profit_target': 0.045, 'trend_ema_break_periods': 3, # Universal Exit Parameters 'medium_term_base_target': 0.05, 'reversal_min_profit_long': 0.008, 'reversal_min_profit_short': 0.008, 'rsi_exit_overbought': 78, 'rsi_exit_oversold': 22, 'rsi_extreme_min_profit': 0.015, 'momentum_fade_min_profit': 0.012, 'momentum_fade_threshold': 0.02, # Extended Duration Management 'extended_2hr_target': 0.025, 'extended_3hr_target': 0.015, 'extended_5hr_target': 0.008, # Market Protection 'friday_close_min_profit': 0.012, 'overnight_min_profit': 0.008, 'low_liquidity_min_profit': 0.01, 'rsv1_rsi_oversold': 15, 'rsv1_rsi_overbought': 80, 'rsv1_willr_oversold': -80, 'rsv1_willr_overbought': -20, 'rsv1_cci_oversold': -100, 'rsv1_cci_overbought': 100, 'rsv1_bb_oversold': 0.1, 'rsv1_bb_overbought': 0.9, 'rsv1_volume_factor': 1.5, 'rsv1_price_position_low': 0.2, 'rsv1_price_position_high': 0.8, 'rsv1_min_oversold_conditions': 3, 'rsv1_min_overbought_conditions': 3, 'rsv1_min_reversal_signals': 2, 'rsv1_min_trend_filters': 1, # Add these to the existing get_default_params() method: # E1 (Primary) Parameters 'e1_rsi_overbought_threshold': 75, 'e1_rsi_overbought_buffer': 5, 'e1_rsi_oversold_min': 25, 'e1_rsi_oversold_threshold': 25, 'e1_rsi_oversold_buffer': 5, 'e1_rsi_overbought_max': 75, 'volatility_min_multiplier': 0.5, 'volatility_max_multiplier': 2.0, # Trend Parameters 'trend_volume_window': 20, 'trend_volume_multiplier': 1.5, 'trend_rsi_min': 40, 'trend_rsi_max': 60, 'trend_close_lookback': 2, 'trend_atr_window': 20, 'trend_atr_multiplier': 1.0, 'trend_adx_min': 25, # Momentum Parameters 'momentum_rsi_lookback': 3, 'momentum_rsi_pivot': 50, 'momentum_rsi_trigger': 55, 'momentum_rsi_trigger_short': 45, 'momentum_high_lookback': 2, 'momentum_low_lookback': 2, 'momentum_volume_window': 10, 'momentum_volume_multiplier': 1.2, 'momentum_kc_upper_factor': 0.995, 'momentum_kc_lower_factor': 1.005, 'momentum_adx_min': 25, # Secondary Parameters 'secondary_close_lookback': 2, 'secondary_rsi_min': 35, 'secondary_rsi_max': 70, 'secondary_rsi_min_short': 30, 'secondary_rsi_max_short': 80, # Tertiary Parameters 'tertiary_rsi_min': 40, 'tertiary_rsi_max': 65, 'tertiary_rsi_min_short': 35, 'tertiary_rsi_max_short': 75, 'tertiary_volume_window': 10, 'tertiary_volume_multiplier': 1.0, # Quaternary Parameters 'quaternary_rsi_min': 45, 'quaternary_rsi_max': 55, 'quaternary_volume_window': 5, 'quaternary_volume_multiplier': 1.2, # Fifth Parameters 'fifth_rsi_min': 35, 'fifth_rsi_max': 70, 'fifth_rsi_min_short': 30, 'fifth_rsi_max_short': 65, # Sixth Parameters 'sixth_min_div_count': 2, 'sixth_rsi_min': 30, 'sixth_rsi_max': 75, 'sixth_rsi_min_short': 25, 'sixth_rsi_max_short': 70, # Seventh Parameters 'seventh_volume_window': 20, 'seventh_volume_multiplier': 1.2, 'seventh_rsi_min': 30, 'seventh_rsi_max': 70, 'seventh_rsi_min_short': 30, 'seventh_rsi_max_short': 70, 'seventh_close_lookback': 2, 'seventh_atr_window': 20, 'seventh_atr_multiplier': 0.5, 'seventh_adx_min': 15, # Divergence Parameters 'div_rsi_min': 25, 'div_rsi_max': 80, 'div_rsi_min_short': 20, 'div_rsi_max_short': 75, #SUPPORT 'support_tolerance': 0.005, # 0.5% tolerance for support touch 'support_bounce_min': 0.002, # 0.2% minimum bounce 'resistance_tolerance': 0.005, # 0.5% tolerance for resistance touch 'resistance_bounce_min': 0.002, # 0.2% minimum rejection 'sr_volume_window': 20, # Volume average window 'sr_volume_multiplier': 1.3, # Volume surge multiplier 'sr_rsi_min_long': 25, # RSI minimum for long S/R 'sr_rsi_max_long': 70, # RSI maximum for long S/R 'sr_rsi_min_short': 30, # RSI minimum for short S/R 'sr_rsi_max_short': 75, # RSI maximum for short S/R 'sr_price_position_max_long': 0.6, # Max price position for long 'sr_price_position_min_short': 0.4, # Min price position for short 'sr_bb_max_long': 0.7, # Max BB% for long entries 'sr_bb_min_short': 0.3, # Min BB% for short entries 'sr_momentum_lookback': 2, # Momentum confirmation lookback 'sr_adx_min': 15, # Minimum ADX for S/R # Strong S/R Parameters 'bb_support_tolerance': 0.01, # BB support tolerance 'bb_resistance_tolerance': 0.01, # BB resistance tolerance 'kc_support_tolerance': 0.01, # KC support tolerance 'kc_resistance_tolerance': 0.01, # KC resistance tolerance 'ema_support_tolerance': 0.008, # EMA support tolerance 'ema_resistance_tolerance': 0.008, # EMA resistance tolerance 'strong_sr_volume_multiplier': 1.8, # Strong S/R volume multiplier 'strong_sr_rsi_min_long': 20, # Strong S/R RSI min long 'strong_sr_rsi_max_long': 50, # Strong S/R RSI max long 'strong_sr_rsi_min_short': 50, # Strong S/R RSI min short 'strong_sr_rsi_max_short': 80, # Strong S/R RSI max short 'min_distance_to_resistance': 0.02, # Minimum distance to resistance 'min_distance_to_support': 0.02, # Minimum distance to support 'strong_sr_adx_min': 18, # Minimum ADX for strong S/R 'breakdown_volume_threshold': 1.5, 'breakdown_adx_threshold': 25, 'breakdown_rsi_upper': 60, 'breakdown_rsi_lower': 40, 'breakdown_price_deviation': 1.0, 'breakdown_volume_confirmation': 1.2, # Add to get_default_params(): 'breakdown_volume_multiplier': 1.3, 'breakout_volume_multiplier': 1.3, 'breakdown_rsi_min': 35, 'breakdown_rsi_max': 60, 'breakout_rsi_min': 40, 'breakout_rsi_max': 75, # Signal Locking Parameters 'signal_lock_enabled': True, # Enable/disable signal locking 'signal_performance_window': 15, # Trades to evaluate over 'signal_lock_duration_hours': 12, # Hours to lock signal 'signal_min_win_rate': 0.35, # Minimum win rate (35%) 'signal_min_avg_profit': -0.015, # Minimum avg profit (-1.5%) 'signal_min_trades_for_eval': 3, # Minimum trades before evaluation 'ema_support_tolerance': 0.008, # 0.8% tolerance for EMA support touch 'ema_resistance_tolerance': 0.008, # 0.8% tolerance for EMA resistance touch 'bb_support_tolerance': 0.01, # 1.0% tolerance for BB support touch 'bb_resistance_tolerance': 0.01, # 1.0% tolerance for BB resistance touch 'kc_support_tolerance': 0.01, # 1.0% tolerance for KC support touch 'kc_resistance_tolerance': 0.01, # 1.0% tolerance for KC resistance touch } def create_objective_function(self, pair: str): """Create enhanced objective function with exit parameter optimization""" def objective(trial): params = { # === EXISTING ENTRY PARAMETERS === 'min_divergence_count': trial.suggest_int('min_divergence_count', 1, 3), 'min_signal_strength': trial.suggest_int('min_signal_strength', 1, 5), 'volume_threshold': trial.suggest_float('volume_threshold', 1.0, 1.5), 'adx_threshold': trial.suggest_int('adx_threshold', 15, 30), 'rsi_overbought': trial.suggest_float('rsi_overbought', 65.0, 85.0), 'rsi_oversold': trial.suggest_float('rsi_oversold', 15.0, 35.0), 'max_volatility': trial.suggest_float('max_volatility', 0.015, 0.035), 'min_volatility': trial.suggest_float('min_volatility', 0.003, 0.008), 'volume_factor': trial.suggest_float('volume_factor', 1.2, 2.0), 'atr_multiplier': trial.suggest_float('atr_multiplier', 2.0, 4.0), # === MISSING INDICATOR PARAMETERS === 'volume_sma_period': trial.suggest_int('volume_sma_period', 15, 25), 'atr_period': trial.suggest_int('atr_period', 10, 20), 'ema_short_period': trial.suggest_int('ema_short_period', 6, 12), 'ema_long_period': trial.suggest_int('ema_long_period', 18, 25), 'macd_fast': trial.suggest_int('macd_fast', 8, 16), 'macd_slow': trial.suggest_int('macd_slow', 22, 30), 'macd_signal': trial.suggest_int('macd_signal', 6, 12), 'stoch_k': trial.suggest_int('stoch_k', 10, 18), 'stoch_d': trial.suggest_int('stoch_d', 2, 5), 'mom_period': trial.suggest_int('mom_period', 8, 15), 'pivot_window': trial.suggest_int('pivot_window', 8, 15), 'swing_period': trial.suggest_int('swing_period', 40, 60), 'recent_high_low_period': trial.suggest_int('recent_high_low_period', 3, 8), 'rolling_20_period': trial.suggest_int('rolling_20_period', 15, 25), 'rolling_10_period': trial.suggest_int('rolling_10_period', 8, 15), 'chop_period': trial.suggest_int('chop_period', 10, 18), 'natr_period': trial.suggest_int('natr_period', 10, 18), 'cmf_period': trial.suggest_int('cmf_period', 15, 25), 'rsi_period': trial.suggest_int('rsi_period', 10, 20), 'willr_period': trial.suggest_int('willr_period', 10, 20), 'cci_period': trial.suggest_int('cci_period', 15, 25), 'bb_period': trial.suggest_int('bb_period', 15, 25), 'bb_std': trial.suggest_float('bb_std', 1.8, 2.5), # === EXISTING ENTRY TIMING PARAMETERS === 'rsi_recovery_periods': trial.suggest_int('rsi_recovery_periods', 2, 5), 'rsi_long_upper_tight': trial.suggest_float('rsi_long_upper_tight', 60.0, 70.0), 'rsi_short_lower_tight': trial.suggest_float('rsi_short_lower_tight', 30.0, 40.0), 'volume_ratio_min': trial.suggest_float('volume_ratio_min', 1.0, 1.4), 'volume_trend_multiplier': trial.suggest_float('volume_trend_multiplier', 1.2, 2.0), 'volume_momentum_multiplier': trial.suggest_float('volume_momentum_multiplier', 1.1, 1.5), 'bb_percent_long_max': trial.suggest_float('bb_percent_long_max', 0.15, 0.35), 'bb_percent_short_min': trial.suggest_float('bb_percent_short_min', 0.65, 0.85), 'price_position_long_max': trial.suggest_float('price_position_long_max', 0.2, 0.4), 'price_position_short_min': trial.suggest_float('price_position_short_min', 0.6, 0.8), 'divergence_freshness_periods': trial.suggest_int('divergence_freshness_periods', 3, 8), 'min_strong_divergence_count': trial.suggest_int('min_strong_divergence_count', 1, 3), 'min_oversold_conditions': trial.suggest_int('min_oversold_conditions', 2, 4), 'min_overbought_conditions': trial.suggest_int('min_overbought_conditions', 2, 4), 'min_reversal_signals': trial.suggest_int('min_reversal_signals', 1, 3), 'min_trend_filters': trial.suggest_int('min_trend_filters', 1, 2), 'chop_threshold': trial.suggest_float('chop_threshold', 55.0, 65.0), 'adx_trending_min': trial.suggest_int('adx_trending_min', 20, 30), 'volume_breakout_multiplier': trial.suggest_float('volume_breakout_multiplier', 2.0, 4.0), 'volume_breakout_rsi_min': trial.suggest_float('volume_breakout_rsi_min', 35.0, 55.0), 'volume_breakout_rsi_max': trial.suggest_float('volume_breakout_rsi_max', 45.0, 65.0), 'bb_oversold_threshold': trial.suggest_float('bb_oversold_threshold', 0.05, 0.25), 'bb_overbought_threshold': trial.suggest_float('bb_overbought_threshold', 0.75, 0.95), 'mean_reversion_rsi_oversold': trial.suggest_float('mean_reversion_rsi_oversold', 20.0, 35.0), 'mean_reversion_rsi_overbought': trial.suggest_float('mean_reversion_rsi_overbought', 65.0, 80.0), 'momentum_ema_periods': trial.suggest_int('momentum_ema_periods', 8, 21), 'momentum_strength_min': trial.suggest_float('momentum_strength_min', 0.015, 0.035), 'momentum_pullback_max': trial.suggest_float('momentum_pullback_max', 0.25, 0.45), 'momentum_pullback_min': trial.suggest_float('momentum_pullback_min', 0.55, 0.75), # === NEW EXIT PARAMETER OPTIMIZATION === # Global Exit Settings 'session_multiplier_overlap': trial.suggest_float('session_multiplier_overlap', 1.1, 1.5), 'session_multiplier_major': trial.suggest_float('session_multiplier_major', 1.0, 1.3), 'volatility_sensitivity': trial.suggest_int('volatility_sensitivity', 25, 60), 'high_signal_multiplier': trial.suggest_float('high_signal_multiplier', 1.0, 1.4), 'low_signal_multiplier': trial.suggest_float('low_signal_multiplier', 0.6, 1.0), # Mean Reversion (MR1) Exit Optimization 'mr1_max_hold_minutes': trial.suggest_int('mr1_max_hold_minutes', 25, 50), 'mr1_rsi_exit_long': trial.suggest_float('mr1_rsi_exit_long', 62.0, 75.0), 'mr1_rsi_exit_short': trial.suggest_float('mr1_rsi_exit_short', 25.0, 38.0), 'mr1_quick_profit_target': trial.suggest_float('mr1_quick_profit_target', 0.015, 0.035), 'mr1_timeout_min_profit': trial.suggest_float('mr1_timeout_min_profit', 0.005, 0.015), 'mr1_bb_exit_long': trial.suggest_float('mr1_bb_exit_long', 0.6, 0.8), 'mr1_bb_exit_short': trial.suggest_float('mr1_bb_exit_short', 0.2, 0.4), # Momentum Continuation (MC1) Exit Optimization 'mc1_profit_target': trial.suggest_float('mc1_profit_target', 0.025, 0.055), 'mc1_momentum_break_threshold': trial.suggest_float('mc1_momentum_break_threshold', 0.01, 0.025), 'mc1_max_hold_minutes': trial.suggest_int('mc1_max_hold_minutes', 30, 60), 'mc1_adx_exit_threshold': trial.suggest_int('mc1_adx_exit_threshold', 18, 28), 'mc1_timeout_min_profit': trial.suggest_float('mc1_timeout_min_profit', 0.005, 0.02), # Volume Breakout (VB1) Exit Optimization 'vb1_volume_fade_threshold': trial.suggest_float('vb1_volume_fade_threshold', 1.0, 1.3), 'vb1_profit_target': trial.suggest_float('vb1_profit_target', 0.02, 0.045), 'vb1_quick_profit': trial.suggest_float('vb1_quick_profit', 0.012, 0.03), 'vb1_min_volatility': trial.suggest_float('vb1_min_volatility', 0.008, 0.018), # Reversal (RSV1) Exit Optimization 'rsv1_rsi_recovery_threshold': trial.suggest_float('rsv1_rsi_recovery_threshold', 52.0, 65.0), 'rsv1_profit_target': trial.suggest_float('rsv1_profit_target', 0.025, 0.05), 'rsv1_max_hold_minutes': trial.suggest_int('rsv1_max_hold_minutes', 40, 80), 'rsv1_min_timeout_profit': trial.suggest_float('rsv1_min_timeout_profit', 0.005, 0.02), # Trend Following Exit Optimization 'trend_profit_target': trial.suggest_float('trend_profit_target', 0.03, 0.06), 'trend_ema_break_periods': trial.suggest_int('trend_ema_break_periods', 2, 5), # Universal Exit Optimization 'medium_term_base_target': trial.suggest_float('medium_term_base_target', 0.035, 0.065), 'reversal_min_profit_long': trial.suggest_float('reversal_min_profit_long', 0.005, 0.015), 'reversal_min_profit_short': trial.suggest_float('reversal_min_profit_short', 0.005, 0.015), 'rsi_exit_overbought': trial.suggest_float('rsi_exit_overbought', 72, 85), 'rsi_exit_oversold': trial.suggest_float('rsi_exit_oversold', 15, 28), 'rsi_extreme_min_profit': trial.suggest_float('rsi_extreme_min_profit', 0.01, 0.025), 'momentum_fade_min_profit': trial.suggest_float('momentum_fade_min_profit', 0.008, 0.02), 'momentum_fade_threshold': trial.suggest_float('momentum_fade_threshold', 0.015, 0.03), # Extended Duration Management 'extended_2hr_target': trial.suggest_float('extended_2hr_target', 0.015, 0.035), 'extended_3hr_target': trial.suggest_float('extended_3hr_target', 0.008, 0.025), 'extended_5hr_target': trial.suggest_float('extended_5hr_target', 0.005, 0.015), # Market Protection Optimization 'friday_close_min_profit': trial.suggest_float('friday_close_min_profit', 0.008, 0.02), 'overnight_min_profit': trial.suggest_float('overnight_min_profit', 0.005, 0.015), 'low_liquidity_min_profit': trial.suggest_float('low_liquidity_min_profit', 0.006, 0.018), # === RSV1 REVERSAL OPTIMIZATION === # RSI thresholds 'rsv1_rsi_oversold': trial.suggest_float('rsv1_rsi_oversold', 20.0, 35.0), 'rsv1_rsi_overbought': trial.suggest_float('rsv1_rsi_overbought', 65.0, 80.0), # Williams %R thresholds 'rsv1_willr_oversold': trial.suggest_float('rsv1_willr_oversold', -90.0, -70.0), 'rsv1_willr_overbought': trial.suggest_float('rsv1_willr_overbought', -30.0, -10.0), # CCI thresholds 'rsv1_cci_oversold': trial.suggest_float('rsv1_cci_oversold', -120.0, -80.0), 'rsv1_cci_overbought': trial.suggest_float('rsv1_cci_overbought', 80.0, 120.0), # Bollinger Band position 'rsv1_bb_oversold': trial.suggest_float('rsv1_bb_oversold', 0.05, 0.15), 'rsv1_bb_overbought': trial.suggest_float('rsv1_bb_overbought', 0.85, 0.95), # Volume and position filters 'rsv1_volume_factor': trial.suggest_float('rsv1_volume_factor', 1.2, 2.0), 'rsv1_price_position_low': trial.suggest_float('rsv1_price_position_low', 0.1, 0.3), 'rsv1_price_position_high': trial.suggest_float('rsv1_price_position_high', 0.7, 0.9), # Condition count requirements 'rsv1_min_oversold_conditions': trial.suggest_int('rsv1_min_oversold_conditions', 2, 4), 'rsv1_min_overbought_conditions': trial.suggest_int('rsv1_min_overbought_conditions', 2, 4), 'rsv1_min_reversal_signals': trial.suggest_int('rsv1_min_reversal_signals', 1, 3), 'rsv1_min_trend_filters': trial.suggest_int('rsv1_min_trend_filters', 1, 2), # === E1 PARAMETERS === 'e1_rsi_overbought_threshold': trial.suggest_float('e1_rsi_overbought_threshold', 70.0, 80.0), 'e1_rsi_overbought_buffer': trial.suggest_int('e1_rsi_overbought_buffer', 3, 8), 'e1_rsi_oversold_min': trial.suggest_float('e1_rsi_oversold_min', 20.0, 30.0), 'e1_rsi_oversold_threshold': trial.suggest_float('e1_rsi_oversold_threshold', 20.0, 30.0), 'e1_rsi_oversold_buffer': trial.suggest_int('e1_rsi_oversold_buffer', 3, 8), 'e1_rsi_overbought_max': trial.suggest_float('e1_rsi_overbought_max', 70.0, 80.0), 'volatility_min_multiplier': trial.suggest_float('volatility_min_multiplier', 0.3, 1.0), 'volatility_max_multiplier': trial.suggest_float('volatility_max_multiplier', 1.5, 3.0), # === TREND PARAMETERS === 'trend_volume_window': trial.suggest_int('trend_volume_window', 15, 30), 'trend_volume_multiplier': trial.suggest_float('trend_volume_multiplier', 1.2, 2.0), 'trend_rsi_min': trial.suggest_float('trend_rsi_min', 35.0, 45.0), 'trend_rsi_max': trial.suggest_float('trend_rsi_max', 55.0, 65.0), 'trend_close_lookback': trial.suggest_int('trend_close_lookback', 1, 4), 'trend_atr_window': trial.suggest_int('trend_atr_window', 15, 25), 'trend_atr_multiplier': trial.suggest_float('trend_atr_multiplier', 0.8, 1.3), 'trend_adx_min': trial.suggest_int('trend_adx_min', 20, 30), # === MOMENTUM PARAMETERS === 'momentum_rsi_lookback': trial.suggest_int('momentum_rsi_lookback', 2, 5), 'momentum_rsi_pivot': trial.suggest_float('momentum_rsi_pivot', 45.0, 55.0), 'momentum_rsi_trigger': trial.suggest_float('momentum_rsi_trigger', 50.0, 60.0), 'momentum_rsi_trigger_short': trial.suggest_float('momentum_rsi_trigger_short', 40.0, 50.0), 'momentum_high_lookback': trial.suggest_int('momentum_high_lookback', 1, 4), 'momentum_low_lookback': trial.suggest_int('momentum_low_lookback', 1, 4), 'momentum_volume_window': trial.suggest_int('momentum_volume_window', 8, 15), 'momentum_volume_multiplier': trial.suggest_float('momentum_volume_multiplier', 1.0, 1.5), 'momentum_kc_upper_factor': trial.suggest_float('momentum_kc_upper_factor', 0.99, 1.0), 'momentum_kc_lower_factor': trial.suggest_float('momentum_kc_lower_factor', 1.0, 1.01), 'momentum_adx_min': trial.suggest_int('momentum_adx_min', 20, 30), # === SECONDARY PARAMETERS === 'secondary_close_lookback': trial.suggest_int('secondary_close_lookback', 1, 4), 'secondary_rsi_min': trial.suggest_float('secondary_rsi_min', 30.0, 40.0), 'secondary_rsi_max': trial.suggest_float('secondary_rsi_max', 65.0, 75.0), 'secondary_rsi_min_short': trial.suggest_float('secondary_rsi_min_short', 25.0, 35.0), 'secondary_rsi_max_short': trial.suggest_float('secondary_rsi_max_short', 75.0, 85.0), # === TERTIARY PARAMETERS === 'tertiary_rsi_min': trial.suggest_float('tertiary_rsi_min', 35.0, 45.0), 'tertiary_rsi_max': trial.suggest_float('tertiary_rsi_max', 60.0, 70.0), 'tertiary_rsi_min_short': trial.suggest_float('tertiary_rsi_min_short', 30.0, 40.0), 'tertiary_rsi_max_short': trial.suggest_float('tertiary_rsi_max_short', 70.0, 80.0), 'tertiary_volume_window': trial.suggest_int('tertiary_volume_window', 8, 15), 'tertiary_volume_multiplier': trial.suggest_float('tertiary_volume_multiplier', 0.8, 1.3), # === QUATERNARY PARAMETERS === 'quaternary_rsi_min': trial.suggest_float('quaternary_rsi_min', 40.0, 50.0), 'quaternary_rsi_max': trial.suggest_float('quaternary_rsi_max', 50.0, 60.0), 'quaternary_volume_window': trial.suggest_int('quaternary_volume_window', 3, 8), 'quaternary_volume_multiplier': trial.suggest_float('quaternary_volume_multiplier', 1.0, 1.5), # === FIFTH PARAMETERS === 'fifth_rsi_min': trial.suggest_float('fifth_rsi_min', 30.0, 40.0), 'fifth_rsi_max': trial.suggest_float('fifth_rsi_max', 65.0, 75.0), 'fifth_rsi_min_short': trial.suggest_float('fifth_rsi_min_short', 25.0, 35.0), 'fifth_rsi_max_short': trial.suggest_float('fifth_rsi_max_short', 60.0, 70.0), # === SIXTH PARAMETERS === 'sixth_min_div_count': trial.suggest_int('sixth_min_div_count', 1, 3), 'sixth_rsi_min': trial.suggest_float('sixth_rsi_min', 25.0, 35.0), 'sixth_rsi_max': trial.suggest_float('sixth_rsi_max', 70.0, 80.0), 'sixth_rsi_min_short': trial.suggest_float('sixth_rsi_min_short', 20.0, 30.0), 'sixth_rsi_max_short': trial.suggest_float('sixth_rsi_max_short', 65.0, 75.0), # === SEVENTH PARAMETERS === 'seventh_volume_window': trial.suggest_int('seventh_volume_window', 15, 25), 'seventh_volume_multiplier': trial.suggest_float('seventh_volume_multiplier', 1.0, 1.5), 'seventh_rsi_min': trial.suggest_float('seventh_rsi_min', 25.0, 35.0), 'seventh_rsi_max': trial.suggest_float('seventh_rsi_max', 65.0, 75.0), 'seventh_rsi_min_short': trial.suggest_float('seventh_rsi_min_short', 25.0, 35.0), 'seventh_rsi_max_short': trial.suggest_float('seventh_rsi_max_short', 65.0, 75.0), 'seventh_close_lookback': trial.suggest_int('seventh_close_lookback', 1, 4), 'seventh_atr_window': trial.suggest_int('seventh_atr_window', 15, 25), 'seventh_atr_multiplier': trial.suggest_float('seventh_atr_multiplier', 0.3, 0.8), 'seventh_adx_min': trial.suggest_int('seventh_adx_min', 10, 20), # === DIVERGENCE PARAMETERS === 'div_rsi_min': trial.suggest_float('div_rsi_min', 20.0, 30.0), 'div_rsi_max': trial.suggest_float('div_rsi_max', 75.0, 85.0), 'div_rsi_min_short': trial.suggest_float('div_rsi_min_short', 15.0, 25.0), 'div_rsi_max_short': trial.suggest_float('div_rsi_max_short', 70.0, 80.0), # === SUPPORT/RESISTANCE PARAMETERS === 'support_tolerance': trial.suggest_float('support_tolerance', 0.003, 0.008), 'support_bounce_min': trial.suggest_float('support_bounce_min', 0.001, 0.004), 'resistance_tolerance': trial.suggest_float('resistance_tolerance', 0.003, 0.008), 'resistance_bounce_min': trial.suggest_float('resistance_bounce_min', 0.001, 0.004), 'sr_volume_multiplier': trial.suggest_float('sr_volume_multiplier', 1.1, 1.8), 'sr_rsi_min_long': trial.suggest_float('sr_rsi_min_long', 20.0, 35.0), 'sr_rsi_max_long': trial.suggest_float('sr_rsi_max_long', 60.0, 75.0), 'sr_rsi_min_short': trial.suggest_float('sr_rsi_min_short', 25.0, 40.0), 'sr_rsi_max_short': trial.suggest_float('sr_rsi_max_short', 65.0, 80.0), 'strong_sr_volume_multiplier': trial.suggest_float('strong_sr_volume_multiplier', 1.5, 2.5), # === BREAKDOWN/BREAKOUT PARAMETERS === 'breakdown_volume_threshold': trial.suggest_float('breakdown_volume_threshold', 1.1, 3.0), 'breakdown_adx_threshold': trial.suggest_int('breakdown_adx_threshold', 15, 45), 'breakdown_rsi_upper': trial.suggest_int('breakdown_rsi_upper', 55, 70), 'breakdown_rsi_lower': trial.suggest_int('breakdown_rsi_lower', 30, 45), 'breakdown_price_deviation': trial.suggest_float('breakdown_price_deviation', 0.5, 3.0), 'breakdown_volume_confirmation': trial.suggest_float('breakdown_volume_confirmation', 1.0, 2.5), 'breakdown_volume_multiplier': trial.suggest_float('breakdown_volume_multiplier', 1.1, 2.0), 'breakout_volume_multiplier': trial.suggest_float('breakout_volume_multiplier', 1.1, 2.0), 'breakdown_rsi_min': trial.suggest_float('breakdown_rsi_min', 30.0, 45.0), 'breakdown_rsi_max': trial.suggest_float('breakdown_rsi_max', 55.0, 70.0), 'breakout_rsi_min': trial.suggest_float('breakout_rsi_min', 35.0, 50.0), 'breakout_rsi_max': trial.suggest_float('breakout_rsi_max', 65.0, 80.0), # === ADDITIONAL TECHNICAL PARAMETERS === 'willr_oversold': trial.suggest_float('willr_oversold', -90, -70), 'willr_overbought': trial.suggest_float('willr_overbought', -30, -10), 'cci_oversold': trial.suggest_float('cci_oversold', -120, -80), 'cci_overbought': trial.suggest_float('cci_overbought', 80, 120), 'ema_support_tolerance': trial.suggest_float('ema_support_tolerance', 0.005, 0.015), 'ema_resistance_tolerance': trial.suggest_float('ema_resistance_tolerance', 0.005, 0.015), 'bb_support_tolerance': trial.suggest_float('bb_support_tolerance', 0.008, 0.020), 'bb_resistance_tolerance': trial.suggest_float('bb_resistance_tolerance', 0.008, 0.020), 'kc_support_tolerance': trial.suggest_float('kc_support_tolerance', 0.008, 0.020), 'kc_resistance_tolerance': trial.suggest_float('kc_resistance_tolerance', 0.008, 0.020), } # Return performance for this coin recent_trades = self.get_recent_trade_performance(pair) if len(recent_trades) >= 1: # Need at least 3 trades return sum(recent_trades) / len(recent_trades) else: return 0.0 # No optimization until we have trade data return objective def get_recent_trade_performance(self, pair: str) -> List[float]: """Get recent trade performance for optimization""" try: from freqtrade.persistence import Trade trades = Trade.get_trades_proxy(pair=pair) if not trades: return [] # Get last 5 trades for this pair recent_trades = trades[-5:] if len(trades) >= 5 else trades # Calculate profit ratios performance = [] for trade in recent_trades: if trade.close_date: # Only closed trades # FIX: Use the close_rate for calc_profit_ratio performance.append(trade.calc_profit_ratio(trade.close_rate)) logger.debug(f"📊 [OPTUNA] Found {len(performance)} completed trades for {pair}") return performance except Exception as e: logger.error(f"❌ [OPTUNA] Failed to get trade performance for {pair}: {e}") return [] def daily_optimization_check(self): """Enhanced optimization check with smarter scheduling""" try: if not self.optuna_manager: return current_time = time.time() all_pairs = list(self.coin_params.keys()) if not all_pairs: logger.info("No pairs available for optimization yet") return optimized_count = 0 for pair in all_pairs: try: if self.should_retrain_pair(pair, current_time): logger.info(f"🔄 Optimizing {pair}") self.optuna_manager.optimize_coin(pair, self.create_objective_function(pair), n_trials=15) # Track last optimization time setattr(self, f'last_optimization_{pair.replace("/", "_").replace(":", "_")}', current_time) optimized_count += 1 except Exception as e: logger.error(f"❌ Optimization failed for {pair}: {e}") logger.info(f"✅ Optimization check completed: {optimized_count}/{len(all_pairs)} pairs optimized") except Exception as e: logger.error(f"❌ Daily optimization check failed: {e}") def should_retrain_pair(self, pair: str, current_time: float) -> bool: """Simplified retraining logic""" last_opt_time = getattr(self, f'last_optimization_{pair.replace("/", "_").replace(":", "_")}', 0) trades_since_last = self.get_trades_since_optimization(pair, last_opt_time) # Simplified: Just check if enough time passed OR enough trades time_threshold = 21600 # 6 hours (reduced from complex scheduling) trade_threshold = 2 # Only 2 trades needed (reduced from 5-10) return (current_time - last_opt_time > time_threshold) or (trades_since_last >= trade_threshold) def get_trades_since_optimization(self, pair: str, last_opt_time: float) -> int: """Count trades since last optimization""" try: from freqtrade.persistence import Trade from datetime import datetime last_opt_datetime = datetime.fromtimestamp(last_opt_time) if last_opt_time > 0 else datetime.min trades = Trade.get_trades_proxy(pair=pair) if not trades: return 0 recent_trades = [t for t in trades if t.open_date_utc > last_opt_datetime] return len(recent_trades) except Exception as e: logger.debug(f"Failed to count recent trades for {pair}: {e}") return 0 def load_signal_performance(self): """Load signal performance history from file""" if os.path.exists(self.signal_performance_file): try: with open(self.signal_performance_file, 'r') as f: return json.load(f) except: return {} return {} def save_signal_performance(self): """Save signal performance to file - both formats""" try: os.makedirs(os.path.dirname(self.signal_performance_file), exist_ok=True) # Save both formats in one file data_to_save = { 'trades': getattr(self, 'all_signal_trades', []), # Excel-friendly flat format 'signal_locks': self.signal_performance # Nested format for locking } with open(self.signal_performance_file, 'w') as f: json.dump(data_to_save, f, indent=2) except Exception as e: print(f"Failed to save signal performance: {e}") def is_signal_locked(self, entry_tag, pair): """Check if a specific signal is locked for this pair""" lock_key = f"{pair}_{entry_tag}" if lock_key in self.signal_locks: if self.signal_locks[lock_key] > datetime.now(): return True else: # Remove expired lock del self.signal_locks[lock_key] print(f"Signal lock expired for {entry_tag} on {pair}") return False def update_signal_performance(self, entry_tag, pair, profit_ratio): """Update performance tracking for a signal - Excel-friendly format + locking support""" # Create trade record with all info in one line (Excel-friendly) trade_record = { 'pair': pair, 'entry_tag': entry_tag, 'profit': profit_ratio, 'timestamp': datetime.now().isoformat(), 'win': profit_ratio > 0 } # Store in flat list for Excel analysis if not hasattr(self, 'all_signal_trades'): self.all_signal_trades = [] self.all_signal_trades.append(trade_record) # Keep only recent window if len(self.all_signal_trades) > 500: self.all_signal_trades = self.all_signal_trades[-500:] # Keep the nested format for the locking logic to work perf_key = f"{pair}_{entry_tag}" if perf_key not in self.signal_performance: self.signal_performance[perf_key] = [] self.signal_performance[perf_key].append({ 'profit': profit_ratio, 'timestamp': datetime.now().isoformat(), 'win': profit_ratio > 0 }) if len(self.signal_performance[perf_key]) > self.performance_window: self.signal_performance[perf_key] = self.signal_performance[perf_key][-self.performance_window:] # Evaluate if signal should be locked self.evaluate_signal_lock(entry_tag, pair) # Save to file self.save_signal_performance() def evaluate_signal_lock(self, entry_tag, pair): """Evaluate if signal should be locked based on recent performance""" perf_key = f"{pair}_{entry_tag}" recent_trades = self.signal_performance.get(perf_key, []) # Need minimum trades for evaluation if len(recent_trades) < self.min_trades_for_eval: return # Calculate performance metrics profits = [trade['profit'] for trade in recent_trades] wins = sum(1 for profit in profits if profit > 0) win_rate = wins / len(profits) avg_profit = sum(profits) / len(profits) # Check if signal should be locked should_lock = ( win_rate < self.min_win_rate or avg_profit < self.min_avg_profit ) if should_lock: lock_until = datetime.now() + timedelta(hours=self.lock_duration_hours) lock_key = f"{pair}_{entry_tag}" self.signal_locks[lock_key] = lock_until print(f"LOCKED SIGNAL: {entry_tag} for {pair}") print(f" Win Rate: {win_rate:.1%} (min: {self.min_win_rate:.1%})") print(f" Avg Profit: {avg_profit:.2%} (min: {self.min_avg_profit:.2%})") print(f" Locked until: {lock_until.strftime('%Y-%m-%d %H:%M')}") def get_signal_status(self, pair): """Get status of all signals for a pair - for debugging""" status = {} # Complete list of entry tags from your trading script entry_tags = [ # Primary conditions 'Bull_E1', 'Bear_E1', # Support/Resistance bounce 'Bull_SR_Bounce', 'Bear_SR_Bounce', # Strong Support/Resistance 'Bull_Strong_Support', 'Bear_Strong_Resistance', # Trend breakout 'Bull_Trend', 'Bear_Trend', # Momentum 'Bull_Momentum', 'Bear_Momentum', # Momentum breakout 'Bull_Momentum_Breakout', 'Bear_Momentum_Breakout', # Pullback momentum 'Bull_Pullback_Momentum', 'Bear_Pullback_Momentum', # Secondary conditions 'Bull_E2', 'Bear_E2', # Breakdown conditions 'Bull_Breakdown', 'Bear_Breakdown', # Pure divergence 'Bull_Div', 'Bear_Div', # High quality divergence 'Bull_Div_HQ', 'Bear_Div_HQ', # Reversal (RSV1) 'Bull_RSV1', 'Bear_RSV1', # Mean reversion (MR1) 'Bull_MR1', 'Bear_MR1', # Momentum continuation (MC1) 'Bull_MC1', 'Bear_MC1', # Seventh condition 'Bull_E7', 'Bear_E7', # Simple breakout/breakdown (NEW) 'Bull_Simple_Breakout', 'Bear_Simple_Breakdown' ] for entry_tag in entry_tags: perf_key = f"{pair}_{entry_tag}" recent_trades = self.signal_performance.get(perf_key, []) is_locked = self.is_signal_locked(entry_tag, pair) if recent_trades: profits = [trade['profit'] for trade in recent_trades] wins = sum(1 for profit in profits if profit > 0) win_rate = wins / len(profits) if profits else 0 avg_profit = sum(profits) / len(profits) if profits else 0 status[entry_tag] = { 'locked': is_locked, 'trades_count': len(recent_trades), 'win_rate': win_rate, 'avg_profit': avg_profit, 'last_profit': profits[-1] if profits else None } else: status[entry_tag] = { 'locked': is_locked, 'trades_count': 0, 'win_rate': 0, 'avg_profit': 0, 'last_profit': None } return status def informative_pairs(self): """Define additional timeframes to download""" pairs = self.dp.current_whitelist() return [(pair, '1h') for pair in pairs] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Enhanced indicator population with multi-timeframe analysis and Optuna optimization ENHANCED: Now includes proactive Optuna optimization with parameterized indicators """ pair = metadata['pair'] logger.debug(f"📊 [INDICATORS] Processing {pair} with {len(dataframe)} candles") # ===== PROACTIVE OPTUNA OPTIMIZATION FIRST ===== # ENHANCED: Get optimized parameters EARLY (triggers proactive optimization) try: coin_params = self.get_coin_params(pair) # Get coin-specific parameters logger.debug(f"📈 [OPTUNA] Applied parameters for {pair}: min_div={coin_params.get('min_divergence_count')}, min_signal={coin_params.get('min_signal_strength')}") except Exception as e: logger.error(f"❌ [OPTUNA] Failed to get coin parameters for {pair}: {e}") coin_params = self.get_default_params() # ===== ENHANCED PARAMETER EXTRACTION ===== # Extract all coin-specific parameters at once for better performance # Volume analysis parameters volume_sma_period = coin_params.get('volume_sma_period', 20) volume_threshold = coin_params.get('volume_threshold', 1.0) # ATR and volatility parameters atr_period = coin_params.get('atr_period', 14) atr_multiplier = coin_params.get('atr_multiplier', 3.0) # Bollinger Bands parameters bb_period = coin_params.get('bb_period', getattr(self, 'bb_period', type('obj', (), {'value': 20})).value) bb_std = coin_params.get('bb_std', getattr(self, 'bb_std', type('obj', (), {'value': 2.0})).value) # EMA parameters ema_short_period = coin_params.get('ema_short_period', 8) ema_long_period = coin_params.get('ema_long_period', 21) # MACD parameters macd_fast = coin_params.get('macd_fast', 12) macd_slow = coin_params.get('macd_slow', 26) macd_signal = coin_params.get('macd_signal', 9) # RSI and momentum parameters rsi_period = coin_params.get('rsi_period', 14) willr_period = coin_params.get('willr_period', 14) cci_period = coin_params.get('cci_period', 20) mom_period = coin_params.get('mom_period', 10) stoch_k = coin_params.get('stoch_k', 14) stoch_d = coin_params.get('stoch_d', 3) # Pivot points window pivot_window = coin_params.get('pivot_window', getattr(self, 'window', type('obj', (), {'value': 10})).value) # Support/Resistance parameters swing_period = coin_params.get('swing_period', 50) recent_high_low_period = coin_params.get('recent_high_low_period', 5) # Rolling window parameters rolling_20_period = coin_params.get('rolling_20_period', 20) rolling_10_period = coin_params.get('rolling_10_period', 10) # Choppiness and other analysis parameters chop_period = coin_params.get('chop_period', 14) natr_period = coin_params.get('natr_period', 14) cmf_period = coin_params.get('cmf_period', 20) # Validate parameters to ensure they're within reasonable ranges rsi_period = max(2, min(50, rsi_period)) willr_period = max(2, min(50, willr_period)) cci_period = max(2, min(50, cci_period)) # === MULTI-TIMEFRAME ANALYSIS === # Get 1h timeframe for trend confirmation with improved error handling try: # Check if we're in backtesting mode or if pair supports 1h data if hasattr(self.dp, 'runmode') and self.dp.runmode.value in ['backtest', 'hyperopt']: # In backtesting, try to get 1h data but don't fail if unavailable informative_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1h') else: # In live/dry run, be more cautious about data availability try: informative_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1h') except Exception: informative_1h = None # Enhanced data validation if (informative_1h is not None and len(informative_1h) > 50 and # Reduced minimum requirement not informative_1h.empty and 'close' in informative_1h.columns): try: # 1h Trend indicators with additional error checking informative_1h['ema50_1h'] = ta.EMA(informative_1h, timeperiod=50) informative_1h['ema200_1h'] = ta.EMA(informative_1h, timeperiod=200) informative_1h['trend_1h'] = ta.EMA(informative_1h, timeperiod=21) informative_1h['trend_strength_1h'] = ta.ADX(informative_1h) informative_1h['rsi_1h'] = ta.RSI(informative_1h) # Fill NaN values before merging informative_1h = informative_1h.bfill().ffill() # Safe merge with additional error handling dataframe = merge_informative_pair(dataframe, informative_1h, self.timeframe, '1h', ffill=True) except Exception as merge_error: logger.warning(f"Failed to merge 1h data for {metadata['pair']}: {merge_error}") self._add_dummy_1h_columns(dataframe) else: logger.info(f"Using fallback 1h indicators for {metadata['pair']} (insufficient data)") self._add_dummy_1h_columns(dataframe) except Exception as e: logger.warning(f"Error accessing 1h data for {metadata['pair']}: {e}") self._add_dummy_1h_columns(dataframe) # === 15M TIMEFRAME INDICATORS === informative = dataframe.copy() # === VOLUME ANALYSIS WITH PARAMETERIZED VALUES === try: informative['volume_sma'] = ta.SMA(informative['volume'], timeperiod=volume_sma_period) informative['volume_ratio'] = informative['volume'] / informative['volume_sma'] informative['volume_ratio'] = informative['volume_ratio'].fillna(1.0) except: informative['volume_sma'] = informative['volume'] informative['volume_ratio'] = 1.0 # === VOLATILITY ANALYSIS WITH PARAMETERIZED VALUES === try: informative['atr'] = ta.ATR(informative, timeperiod=atr_period) informative['volatility'] = informative['atr'] / informative['close'] informative['volatility'] = informative['volatility'].fillna(0.01) except: informative['atr'] = informative['close'] * 0.02 informative['volatility'] = 0.01 # === MOMENTUM INDICATORS WITH PARAMETERIZED VALUES === try: # Parameterized momentum indicators informative['rsi'] = ta.RSI(informative, timeperiod=rsi_period) informative['willr'] = ta.WILLR(informative, timeperiod=willr_period) informative['cci'] = ta.CCI(informative, timeperiod=cci_period) informative['mom'] = ta.MOM(informative, timeperiod=mom_period) # MACD with parameterized values macd = ta.MACD(informative, fastperiod=macd_fast, slowperiod=macd_slow, signalperiod=macd_signal) informative['macd'] = macd['macd'] informative['macdsignal'] = macd['macdsignal'] informative['macdhist'] = macd['macdhist'] # Additional momentum indicators with parameters stoch = ta.STOCH(informative, fastk_period=stoch_k, slowk_period=stoch_d, slowd_period=stoch_d) informative['stoch'] = stoch['slowk'] informative['roc'] = ta.ROC(informative) informative['uo'] = ta.ULTOSC(informative) informative['ao'] = qtpylib.awesome_oscillator(informative) informative['cmf'] = chaikin_money_flow(informative, cmf_period) informative['obv'] = ta.OBV(informative) informative['mfi'] = ta.MFI(informative) informative['adx'] = ta.ADX(informative) # Fill NaN values for all indicators indicator_columns = ['rsi', 'stoch', 'roc', 'uo', 'ao', 'macd', 'macdsignal', 'macdhist', 'cci', 'cmf', 'obv', 'mfi', 'adx', 'willr', 'mom'] for col in indicator_columns: if col in informative.columns: informative[col] = informative[col].bfill().fillna(50 if col in ['rsi', 'mfi'] else 0) except Exception as e: logger.warning(f"Error calculating momentum indicators: {e}") # Provide fallback values informative['rsi'] = 50 informative['stoch'] = 50 informative['roc'] = 0 informative['uo'] = 50 informative['ao'] = 0 informative['macd'] = 0 informative['macdsignal'] = 0 informative['macdhist'] = 0 informative['cci'] = 0 informative['cmf'] = 0 informative['obv'] = informative['volume'].cumsum() informative['mfi'] = 50 informative['adx'] = 25 informative['willr'] = -50 informative['mom'] = 0 # === KELTNER CHANNEL === try: keltner = emaKeltner(informative) informative["kc_upperband"] = keltner["upper"] informative["kc_middleband"] = keltner["mid"] informative["kc_lowerband"] = keltner["lower"] except: informative["kc_upperband"] = informative['close'] * 1.02 informative["kc_middleband"] = informative['close'] informative["kc_lowerband"] = informative['close'] * 0.98 # === BOLLINGER BANDS WITH PARAMETERIZED VALUES === try: # Parameterized Bollinger Bands bollinger = qtpylib.bollinger_bands(informative['close'], window=bb_period, stds=bb_std) informative['bb_lower'] = bollinger['lower'] informative['bb_middle'] = bollinger['mid'] informative['bb_upper'] = bollinger['upper'] informative['bb_percent'] = (informative['close'] - informative['bb_lower']) / (informative['bb_upper'] - informative['bb_lower']) # Keep the original naming for compatibility informative['bollinger_upperband'] = bollinger['upper'] informative['bollinger_lowerband'] = bollinger['lower'] except: informative['bb_lower'] = informative['close'] * 0.98 informative['bb_middle'] = informative['close'] informative['bb_upper'] = informative['close'] * 1.02 informative['bb_percent'] = 0.5 informative['bollinger_upperband'] = informative['close'] * 1.02 informative['bollinger_lowerband'] = informative['close'] * 0.98 # === EMA WITH PARAMETERIZED VALUES === try: # Parameterized EMAs informative['ema_short'] = ta.EMA(informative, timeperiod=ema_short_period) informative['ema_long'] = ta.EMA(informative, timeperiod=ema_long_period) # Standard EMAs informative['ema9'] = ta.EMA(informative, timeperiod=9) informative['ema20'] = ta.EMA(informative, timeperiod=20) informative['ema50'] = ta.EMA(informative, timeperiod=50) informative['ema200'] = ta.EMA(informative, timeperiod=200) informative['ema21'] = ta.EMA(informative, timeperiod=21) # If not already exists informative['price_vs_ema21'] = (informative['close'] - informative['ema21']) / informative['ema21'] * 100 informative['price_vs_ema50'] = (informative['close'] - informative['ema50']) / informative['ema50'] * 100 informative['price_vs_ema200'] = (informative['close'] - informative['ema200']) / informative['ema200'] * 100 # Fill NaN values for EMAs ema_columns = ['ema_short', 'ema_long', 'ema9', 'ema20', 'ema21', 'ema50', 'ema200'] # Added ema21 for col in ema_columns: if col in informative.columns: informative[col] = informative[col].bfill().fillna(informative['close']) # ADD: Fill NaN values for price position indicators price_pos_columns = ['price_vs_ema21', 'price_vs_ema50', 'price_vs_ema200'] for col in price_pos_columns: if col in informative.columns: informative[col] = informative[col].bfill().fillna(0) except: informative['ema_short'] = informative['close'] informative['ema_long'] = informative['close'] informative['ema9'] = informative['close'] informative['ema20'] = informative['close'] informative['ema21'] = informative['close'] # ADD THIS informative['ema50'] = informative['close'] informative['ema200'] = informative['close'] # ADD THESE FALLBACK VALUES: informative['price_vs_ema21'] = 0 informative['price_vs_ema50'] = 0 informative['price_vs_ema200'] = 0 # === PRICE POSITION ANALYSIS WITH PARAMETERIZED VALUES === try: informative['high_20'] = informative['high'].rolling(window=rolling_20_period).max() informative['low_20'] = informative['low'].rolling(window=rolling_20_period).min() informative['price_position'] = (informative['close'] - informative['low_20']) / (informative['high_20'] - informative['low_20']) except: informative['high_20'] = informative['high'] informative['low_20'] = informative['low'] informative['price_position'] = 0.5 # === PIVOT POINTS WITH PARAMETERIZED VALUES === try: pivots = pivot_points(informative, pivot_window) informative['pivot_lows'] = pivots['pivot_lows'] informative['pivot_highs'] = pivots['pivot_highs'] except Exception as e: logger.warning(f"Error calculating pivot points: {e}") informative['pivot_lows'] = np.nan informative['pivot_highs'] = np.nan # === DIVERGENCE ANALYSIS === try: self.initialize_divergences_lists(informative) (high_iterator, low_iterator) = self.get_iterators(informative) # Add divergences for multiple indicators (expanded list from first file) indicators = ['rsi', 'stoch', 'roc', 'uo', 'ao', 'macd', 'cci', 'cmf', 'obv', 'mfi'] for indicator in indicators: try: if indicator in informative.columns: self.add_divergences(informative, indicator, high_iterator, low_iterator) except Exception as e: logger.warning(f"Error adding divergences for {indicator}: {e}") continue except Exception as e: logger.warning(f"Error in divergence analysis: {e}") # Initialize with empty divergence data informative["total_bullish_divergences"] = 0 informative["total_bullish_divergences_count"] = 0 informative["total_bullish_divergences_names"] = '' informative["total_bearish_divergences"] = 0 informative["total_bearish_divergences_count"] = 0 informative["total_bearish_divergences_names"] = '' # === SIGNAL STRENGTH CALCULATION === try: informative['signal_strength'] = self.calculate_signal_strength(informative) except: informative['signal_strength'] = 0 # === MERGE BACK TO DATAFRAME === for col in informative.columns: if col not in dataframe.columns: dataframe[col] = informative[col] else: dataframe[col] = informative[col] # ===== APPLY COIN-SPECIFIC OPTUNA ADJUSTMENTS ===== # Apply coin-specific adjustments AFTER all indicators are calculated dataframe['coin_min_divergence'] = coin_params.get('min_divergence_count', self.min_divergence_count.value) dataframe['coin_min_signal'] = coin_params.get('min_signal_strength', self.min_signal_strength.value) dataframe['coin_volume_threshold'] = coin_params.get('volume_threshold', self.volume_threshold.value) dataframe['coin_adx_threshold'] = coin_params.get('adx_threshold', self.adx_threshold.value) # === ADDITIONAL MARKET STRUCTURE ANALYSIS WITH PARAMETERIZED VALUES === try: dataframe['chop'] = choppiness_index(dataframe['high'], dataframe['low'], dataframe['close'], window=chop_period) dataframe['natr'] = ta.NATR(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=natr_period) dataframe['natr_diff'] = dataframe['natr'] - dataframe['natr'].shift(1) dataframe['natr_direction_change'] = (dataframe['natr_diff'] * dataframe['natr_diff'].shift(1) < 0) except: dataframe['chop'] = 50 dataframe['natr'] = 0.02 dataframe['natr_diff'] = 0 dataframe['natr_direction_change'] = False # === SUPPORT/RESISTANCE LEVELS WITH PARAMETERIZED VALUES === try: dataframe['swing_high'] = dataframe['high'].rolling(window=swing_period, min_periods=1).max() dataframe['swing_low'] = dataframe['low'].rolling(window=swing_period, min_periods=1).min() dataframe['distance_to_resistance'] = (dataframe['swing_high'] - dataframe['close']) / dataframe['close'] dataframe['distance_to_support'] = (dataframe['close'] - dataframe['swing_low']) / dataframe['close'] except: dataframe['swing_high'] = dataframe['high'] dataframe['swing_low'] = dataframe['low'] dataframe['distance_to_resistance'] = 0.02 dataframe['distance_to_support'] = 0.02 try: # Calculate recent highs/lows for pattern recognition WITH PARAMETERIZED VALUES dataframe['recent_high_5'] = dataframe['high'].rolling(window=recent_high_low_period).max() dataframe['recent_low_5'] = dataframe['low'].rolling(window=recent_high_low_period).min() dataframe['recent_high_10'] = dataframe['high'].rolling(window=rolling_10_period).max() dataframe['recent_low_10'] = dataframe['low'].rolling(window=rolling_10_period).min() # MA alignment indicators for trend confirmation dataframe['ma_alignment_bull'] = ( (dataframe['ema21'] > dataframe['ema50']) & (dataframe['ema50'] > dataframe['ema200']) ) dataframe['ma_alignment_bear'] = ( (dataframe['ema21'] < dataframe['ema50']) & (dataframe['ema50'] < dataframe['ema200']) ) # Rejection pattern detection dataframe['rejected_at_ema21'] = ( (dataframe['high'].shift(1) > dataframe['ema21'].shift(1)) & (dataframe['close'].shift(1) < dataframe['ema21'].shift(1)) ) dataframe['rejected_at_ema50'] = ( (dataframe['high'].shift(1) > dataframe['ema50'].shift(1)) & (dataframe['close'].shift(1) < dataframe['ema50'].shift(1)) ) dataframe['bounced_from_ema21'] = ( (dataframe['low'].shift(1) < dataframe['ema21'].shift(1)) & (dataframe['close'].shift(1) > dataframe['ema21'].shift(1)) ) dataframe['bounced_from_ema50'] = ( (dataframe['low'].shift(1) < dataframe['ema50'].shift(1)) & (dataframe['close'].shift(1) > dataframe['ema50'].shift(1)) ) # Candle pattern indicators dataframe['red_candle'] = dataframe['close'] < dataframe['close'].shift(1) dataframe['green_candle'] = dataframe['close'] > dataframe['close'].shift(1) dataframe['strong_red'] = (dataframe['close'] < dataframe['close'].shift(1)) & (dataframe['volume_ratio'] > 1.2) dataframe['strong_green'] = (dataframe['close'] > dataframe['close'].shift(1)) & (dataframe['volume_ratio'] > 1.2) # Fill NaN values pattern_columns = [ 'recent_high_5', 'recent_low_5', 'recent_high_10', 'recent_low_10', 'ma_alignment_bull', 'ma_alignment_bear', 'rejected_at_ema21', 'rejected_at_ema50', 'bounced_from_ema21', 'bounced_from_ema50', 'red_candle', 'green_candle', 'strong_red', 'strong_green' ] for col in pattern_columns: if col in dataframe.columns: if col.startswith('recent_'): dataframe[col] = dataframe[col].bfill().fillna(dataframe['high'] if 'high' in col else dataframe['low']) else: dataframe[col] = dataframe[col].fillna(False) except Exception as e: logger.warning(f"Error calculating breakdown patterns: {e}") # Fallback values dataframe['recent_high_5'] = dataframe['high'] dataframe['recent_low_5'] = dataframe['low'] dataframe['recent_high_10'] = dataframe['high'] dataframe['recent_low_10'] = dataframe['low'] dataframe['ma_alignment_bull'] = False dataframe['ma_alignment_bear'] = False dataframe['rejected_at_ema21'] = False dataframe['rejected_at_ema50'] = False dataframe['bounced_from_ema21'] = False dataframe['bounced_from_ema50'] = False dataframe['red_candle'] = False dataframe['green_candle'] = False dataframe['strong_red'] = False dataframe['strong_green'] = False # === PLOT CONFIGURATION === try: self.plot_config = ( PlotConfig() .add_total_divergences_in_config(dataframe) .config) except: self.plot_config = None logger.debug(f"✅ [INDICATORS] Completed processing for {pair}") # Run daily optimization check (only once per day) if not hasattr(self, 'last_daily_check'): self.last_daily_check = 0 current_time = time.time() if current_time - self.last_daily_check > 3600: # Check every hour instead of daily self.daily_optimization_check() self.last_daily_check = current_time return dataframe def _add_dummy_1h_columns(self, dataframe): """Add dummy 1h columns when higher timeframe data is unavailable""" # Use current 15m data to simulate 1h trend try: dataframe['ema50_1h_1h'] = ta.EMA(dataframe, timeperiod=200) # Use longer period on 15m dataframe['ema200_1h_1h'] = ta.EMA(dataframe, timeperiod=800) # Use much longer period dataframe['trend_1h_1h'] = ta.EMA(dataframe, timeperiod=84) # 21 * 4 (4x 15m = 1h) dataframe['trend_strength_1h_1h'] = ta.ADX(dataframe) dataframe['rsi_1h_1h'] = ta.RSI(dataframe, timeperiod=56) # Adjusted for timeframe # Fill NaN values columns_1h = ['ema50_1h_1h', 'ema200_1h_1h', 'trend_1h_1h', 'trend_strength_1h_1h', 'rsi_1h_1h'] for col in columns_1h: if col in dataframe.columns: dataframe[col] = dataframe[col].bfill().fillna( dataframe['close'] if 'ema' in col or 'trend' in col else 25 if 'strength' in col else 50 ) except Exception as e: logger.warning(f"Error creating dummy 1h columns: {e}") # Absolute fallback dataframe['ema50_1h_1h'] = dataframe['close'] dataframe['ema200_1h_1h'] = dataframe['close'] dataframe['trend_1h_1h'] = dataframe['close'] dataframe['trend_strength_1h_1h'] = 25 dataframe['rsi_1h_1h'] = 50 def calculate_signal_strength(self, dataframe: DataFrame) -> Series: """ Calculate overall signal strength based on multiple factors """ strength = pd.Series(0, index=dataframe.index) # Divergence strength strength += dataframe['total_bullish_divergences_count'] * 2 strength += dataframe['total_bearish_divergences_count'] * 2 # Volume strength volume_strength = np.where(dataframe['volume_ratio'] > 1.5, 2, np.where(dataframe['volume_ratio'] > 1.2, 1, 0)) strength += volume_strength # Trend alignment strength ema_bullish = (dataframe['ema20'] > dataframe['ema50']) & (dataframe['ema50'] > dataframe['ema200']) ema_bearish = (dataframe['ema20'] < dataframe['ema50']) & (dataframe['ema50'] < dataframe['ema200']) strength += np.where(ema_bullish | ema_bearish, 1, 0) # ADX strength strength += np.where(dataframe['adx'] > 30, 1, 0) return strength def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: print(f"Processing entry signals for {metadata['pair']}") # Debug line """ Enhanced entry strategy with RealOptuna optimization Based on better-performing original logic with dynamic parameter tuning """ if len(dataframe) > 0: last_row = dataframe.iloc[-1] print(f" RSI: {last_row.get('rsi', 0):.1f}") print(f" Volume: {last_row.get('volume', 0)}") print(f" Bull Div: {last_row.get('total_bullish_divergences', 0)}") print(f" Bear Div: {last_row.get('total_bearish_divergences', 0)}") # Initialize dataframe['enter_long'] = 0 dataframe['enter_short'] = 0 dataframe["enter_tag"] = "" dataframe['entry_type'] = 0 # Get optimized parameters for this pair coin_params = self.get_coin_params(metadata['pair']) pair = metadata['pair'] signal_lock_enabled = coin_params.get('signal_lock_enabled', True) # === BASIC FILTERS === has_volume = dataframe['volume'] > 0 # === PRIMARY DIVERGENCE CONDITIONS (E1) - HIGHEST PRIORITY === bullish_divergence = ( (dataframe['total_bullish_divergences'].shift(1) > 0) ) bearish_divergence = ( (dataframe['total_bearish_divergences'].shift(1) > 0) ) # Volatility filter with Optuna optimization volatility_ok = ( (dataframe['volatility'].shift(1) >= coin_params.get('min_volatility', 0.003) * coin_params.get('volatility_min_multiplier', 0.5)) & (dataframe['volatility'].shift(1) <= coin_params.get('max_volatility', 0.035) * coin_params.get('volatility_max_multiplier', 2.0)) ) # KC Bands filter with optimization bands_long = ( (dataframe['low'] <= dataframe['kc_lowerband']) | (dataframe['close'] <= dataframe['kc_lowerband']) ) bands_short = ( (dataframe['high'] >= dataframe['kc_upperband']) | (dataframe['close'] >= dataframe['kc_upperband']) ) # RSI filters with optimization rsi_long_ok = ( (dataframe['rsi'].shift(1) < coin_params.get('e1_rsi_overbought_threshold', 75) + coin_params.get('e1_rsi_overbought_buffer', 5)) & (dataframe['rsi'].shift(1) > coin_params.get('e1_rsi_oversold_min', 25)) ) rsi_short_ok = ( (dataframe['rsi'].shift(1) > coin_params.get('e1_rsi_oversold_threshold', 25) - coin_params.get('e1_rsi_oversold_buffer', 5)) & (dataframe['rsi'].shift(1) < coin_params.get('e1_rsi_overbought_max', 75)) ) long_condition_primary = ( bullish_divergence & volatility_ok & bands_long & rsi_long_ok & has_volume ) short_condition_primary = ( bearish_divergence & volatility_ok & bands_short & rsi_short_ok & has_volume ) # Enhanced Support Bounce (Long Entry) long_condition_support_bounce = ( # Multiple support level confluence - stronger than single swing level ( # Primary: Recent low near swing support (existing) (dataframe['low'] <= dataframe['swing_low'] * (1 + coin_params.get('support_tolerance', 0.005))) | # Secondary: EMA20/50 support confluence ( (dataframe['low'] <= dataframe['ema_short'] * (1 - coin_params.get('ema_support_tolerance', 0.008))) & (dataframe['ema_short'] > dataframe['ema_long']) # Bullish EMA alignment ) | # Tertiary: BB lower band support (dataframe['low'] <= dataframe['bb_lower'] * (1 + coin_params.get('bb_support_tolerance', 0.01))) ) & # Stronger bounce confirmation (dataframe['close'] > dataframe['swing_low'] * (1 + coin_params.get('support_bounce_min', 0.002))) & (dataframe['close'] > dataframe['open']) & # Green candle (dataframe['close'] > dataframe['close'].shift(1)) & # Higher close than previous # Enhanced volume confirmation (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('sr_volume_window', 20)).mean() * coin_params.get('strong_sr_volume_multiplier', 1.8)) & # Higher threshold # Better trend context (dataframe['adx'] > coin_params.get('strong_sr_adx_min', 18)) & # Stronger momentum requirement (dataframe['ema20'] >= dataframe['ema50']) & # Bullish bias # Improved RSI positioning (dataframe['rsi'] > coin_params.get('strong_sr_rsi_min_long', 20)) & (dataframe['rsi'] < coin_params.get('strong_sr_rsi_max_long', 50)) & (dataframe['rsi'] > dataframe['rsi'].shift(coin_params.get('sr_momentum_lookback', 2))) & # RSI improving # Enhanced position and structure filters (dataframe['price_position'] < coin_params.get('sr_price_position_max_long', 0.6)) & (dataframe['bb_percent'] < coin_params.get('sr_bb_max_long', 0.7)) & # Distance filter - ensure room to move up (dataframe['close'] < dataframe['swing_high'] * (1 - coin_params.get('min_distance_to_resistance', 0.02))) & # Volatility filters (dataframe['volatility'] >= coin_params.get('min_volatility', 0.005)) & (dataframe['volatility'] <= coin_params.get('max_volatility', 0.025)) & # Momentum confirmation over multiple periods (dataframe['close'] > dataframe['close'].shift(coin_params.get('sr_momentum_lookback', 2))) & has_volume ) # Enhanced Resistance Rejection (Short Entry) short_condition_resistance_bounce = ( # Multiple resistance level confluence - stronger than single swing level ( # Primary: Recent high near swing resistance (existing) (dataframe['high'] >= dataframe['swing_high'] * (1 - coin_params.get('resistance_tolerance', 0.005))) | # Secondary: EMA20/50 resistance confluence ( (dataframe['high'] >= dataframe['ema_short'] * (1 + coin_params.get('ema_resistance_tolerance', 0.008))) & (dataframe['ema_short'] < dataframe['ema_long']) # Bearish EMA alignment ) | # Tertiary: BB upper band resistance (dataframe['high'] >= dataframe['bb_upper'] * (1 - coin_params.get('bb_resistance_tolerance', 0.01))) ) & # Stronger rejection confirmation (dataframe['close'] < dataframe['swing_high'] * (1 - coin_params.get('resistance_bounce_min', 0.002))) & (dataframe['close'] < dataframe['open']) & # Red candle (dataframe['close'] < dataframe['close'].shift(1)) & # Lower close than previous # Enhanced volume confirmation (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('sr_volume_window', 20)).mean() * coin_params.get('strong_sr_volume_multiplier', 1.8)) & # Higher threshold # Better trend context (dataframe['adx'] > coin_params.get('strong_sr_adx_min', 18)) & # Stronger momentum requirement (dataframe['ema_short'] <= dataframe['ema_long']) & # Bearish bias # Improved RSI positioning (dataframe['rsi'] > coin_params.get('strong_sr_rsi_min_short', 50)) & (dataframe['rsi'] < coin_params.get('strong_sr_rsi_max_short', 80)) & (dataframe['rsi'] < dataframe['rsi'].shift(coin_params.get('sr_momentum_lookback', 2))) & # RSI declining # Enhanced position and structure filters (dataframe['price_position'] > coin_params.get('sr_price_position_min_short', 0.4)) & (dataframe['bb_percent'] > coin_params.get('sr_bb_min_short', 0.3)) & # Distance filter - ensure room to move down (dataframe['close'] > dataframe['swing_low'] * (1 + coin_params.get('min_distance_to_support', 0.02))) & # Volatility filters (dataframe['volatility'] >= coin_params.get('min_volatility', 0.005)) & (dataframe['volatility'] <= coin_params.get('max_volatility', 0.025)) & # Momentum confirmation over multiple periods (dataframe['close'] < dataframe['close'].shift(coin_params.get('sr_momentum_lookback', 2))) & has_volume ) # === ENHANCED S/R BOUNCE WITH CONFLUENCE === # Stronger version that requires multiple confirmations # Strong Support Bounce (Higher Quality) long_condition_strong_support = ( # Multiple support level confluence - at least one must be touched ( # EMA50 support (existing) (dataframe['low'] <= dataframe['ema_long'] * (1 - coin_params.get('support_tolerance', 0.005))) | # BB lower band support (dataframe['low'] <= dataframe['bb_lower'] * (1 + coin_params.get('bb_support_tolerance', 0.01))) | # KC lower band support (dataframe['low'] <= dataframe['kc_lowerband'] * (1 + coin_params.get('kc_support_tolerance', 0.01))) | # EMA20 support for shorter-term bounces (dataframe['low'] <= dataframe['ema_short'] * (1 - coin_params.get('ema_support_tolerance', 0.008))) ) & # Stronger bounce confirmation (dataframe['close'] > dataframe['low'] * (1 + coin_params.get('support_bounce_min', 0.002))) & (dataframe['close'] > dataframe['open']) & # Green candle confirmation # Trend and momentum context (dataframe['adx'] > coin_params.get('strong_sr_adx_min', 18)) & # Momentum strength # Enhanced volume requirement - more selective (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('sr_volume_window', 20)).mean() * coin_params.get('strong_sr_volume_multiplier', 1.8)) & # Tighter RSI positioning for quality entries (dataframe['rsi'] > coin_params.get('strong_sr_rsi_min_long', 20)) & (dataframe['rsi'] < coin_params.get('strong_sr_rsi_max_long', 50)) & # Distance filter - ensure room to move up (avoid resistance squeeze) (dataframe['close'] < dataframe['swing_high'] * (1 - coin_params.get('min_distance_to_resistance', 0.02))) & # Basic volatility filter (dataframe['volatility'] >= coin_params.get('min_volatility', 0.005)) & (dataframe['volatility'] <= coin_params.get('max_volatility', 0.025)) & has_volume ) # Enhanced Strong Resistance (Short Entry) short_condition_strong_resistance = ( # Multiple resistance level confluence - at least one must be touched ( # EMA50 resistance (existing) (dataframe['high'] >= dataframe['ema_long'] * (1 + coin_params.get('resistance_tolerance', 0.005))) | # BB upper band resistance (dataframe['high'] >= dataframe['bb_upper'] * (1 - coin_params.get('bb_resistance_tolerance', 0.01))) | # KC upper band resistance (dataframe['high'] >= dataframe['kc_upperband'] * (1 - coin_params.get('kc_resistance_tolerance', 0.01))) | # EMA20 resistance for shorter-term rejections (dataframe['high'] >= dataframe['ema_short'] * (1 + coin_params.get('ema_resistance_tolerance', 0.008))) ) & # Stronger rejection confirmation (dataframe['close'] < dataframe['high'] * (1 - coin_params.get('resistance_bounce_min', 0.002))) & (dataframe['close'] < dataframe['open']) & # Red candle confirmation # Trend and momentum context (dataframe['adx'] > coin_params.get('strong_sr_adx_min', 18)) & # Momentum strength # Enhanced volume requirement - more selective (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('sr_volume_window', 20)).mean() * coin_params.get('strong_sr_volume_multiplier', 1.8)) & # Tighter RSI positioning for quality entries (dataframe['rsi'] > coin_params.get('strong_sr_rsi_min_short', 50)) & (dataframe['rsi'] < coin_params.get('strong_sr_rsi_max_short', 80)) & # Distance filter - ensure room to move down (avoid support squeeze) (dataframe['close'] > dataframe['swing_low'] * (1 + coin_params.get('min_distance_to_support', 0.02))) & # Basic volatility filter (dataframe['volatility'] >= coin_params.get('min_volatility', 0.005)) & (dataframe['volatility'] <= coin_params.get('max_volatility', 0.025)) & has_volume ) # === TREND BREAKOUT CONDITIONS === long_condition_trend = ( # Core trend detection (more flexible) ( # Primary trend: EMA20 above EMA50 (main trend signal) (dataframe['ema_short'] > dataframe['ema_long']) & # Secondary: Recent bullish momentum (dataframe['close'] > dataframe['ema20'].shift(1)) & # Price action: Break above previous resistance or pullback entry ( # Breakout scenario (dataframe['close'] > dataframe['high'].shift(coin_params.get('trend_close_lookback', 2))) | # Pullback scenario - price pulling back to EMA20 after uptrend ( (dataframe['close'].shift(2) > dataframe['ema20'].shift(2)) & (dataframe['low'] <= dataframe['ema20']) & (dataframe['close'] > dataframe['ema_short']) ) ) ) & # Volume confirmation (less restrictive) (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('trend_volume_window', 20)).mean() * coin_params.get('trend_volume_multiplier', 1.2)) & # RSI not oversold (broader range) (dataframe['rsi'].shift(1) > coin_params.get('trend_rsi_min', 35)) & (dataframe['rsi'].shift(1) < coin_params.get('trend_rsi_max', 70)) & # Volatility filter (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('trend_atr_window', 20)).mean() * coin_params.get('trend_atr_multiplier', 0.8)) & # Momentum confirmation (dataframe['close'] > dataframe['close'].shift(3)) & # Optional: Weak ADX requirement (trend forming) (dataframe['adx'] > coin_params.get('trend_adx_min', 15)) & has_volume ) short_condition_trend = ( # Core trend detection (more flexible) ( # Primary trend: EMA20 below EMA50 (main trend signal) (dataframe['ema_short'] < dataframe['ema_long']) & # Secondary: Recent bearish momentum (dataframe['close'] < dataframe['ema20'].shift(1)) & # Price action: Break below previous support or pullback entry ( # Breakdown scenario (dataframe['close'] < dataframe['low'].shift(coin_params.get('trend_close_lookback', 2))) | # Pullback scenario - price pulling back to EMA20 after downtrend ( (dataframe['close'].shift(2) < dataframe['ema20'].shift(2)) & (dataframe['high'] >= dataframe['ema20']) & (dataframe['close'] < dataframe['ema_short']) ) ) ) & # Volume confirmation (less restrictive) (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('trend_volume_window', 20)).mean() * coin_params.get('trend_volume_multiplier', 1.2)) & # RSI not overbought (broader range) (dataframe['rsi'].shift(1) > coin_params.get('trend_rsi_min', 30)) & (dataframe['rsi'].shift(1) < coin_params.get('trend_rsi_max', 65)) & # Volatility filter (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('trend_atr_window', 20)).mean() * coin_params.get('trend_atr_multiplier', 0.8)) & # Momentum confirmation (dataframe['close'] < dataframe['close'].shift(3)) & # Optional: Weak ADX requirement (trend forming) (dataframe['adx'] > coin_params.get('trend_adx_min', 15)) & has_volume ) # === ENHANCED MOMENTUM CONTINUATION CONDITIONS === long_condition_momentum = ( # Momentum detection (multiple scenarios) ( # Scenario 1: Strong momentum continuation ( (dataframe['close'] > dataframe['ema_short']) & (dataframe['rsi'] > dataframe['rsi'].shift(1)) & (dataframe['rsi'].shift(1) > coin_params.get('momentum_rsi_trigger', 50)) & (dataframe['close'] > dataframe['high'].shift(coin_params.get('momentum_high_lookback', 2))) ) | # Scenario 2: Momentum recovery after pullback ( (dataframe['rsi'].shift(3) < coin_params.get('momentum_rsi_pivot', 45)) & (dataframe['rsi'] > coin_params.get('momentum_rsi_trigger', 52)) & (dataframe['close'] > dataframe['ema_short']) & (dataframe['volume'] > dataframe['volume'].shift(1)) ) | # Scenario 3: Breaking out of consolidation with momentum ( (dataframe['close'] > dataframe['high'].rolling(5).max().shift(1)) & (dataframe['rsi'] > 50) & (dataframe['volume'] > dataframe['volume'].rolling(10).mean() * 1.5) ) ) & # Basic trend filter (less restrictive) (dataframe['ema20'] >= dataframe['ema50']) & # Volume confirmation (flexible) (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('momentum_volume_window', 10)).mean() * coin_params.get('momentum_volume_multiplier', 1.0)) & # Not in extreme overbought (dataframe['close'] < dataframe['kc_upperband'] * coin_params.get('momentum_kc_upper_factor', 0.998)) & # Momentum strength (dataframe['adx'] > coin_params.get('momentum_adx_min', 20)) & has_volume ) short_condition_momentum = ( # Momentum detection (multiple scenarios) ( # Scenario 1: Strong momentum continuation ( (dataframe['close'] < dataframe['ema_short']) & (dataframe['rsi'] < dataframe['rsi'].shift(1)) & (dataframe['rsi'].shift(1) < coin_params.get('momentum_rsi_trigger_short', 50)) & (dataframe['close'] < dataframe['low'].shift(coin_params.get('momentum_low_lookback', 2))) ) | # Scenario 2: Momentum recovery after pullback (bearish) ( (dataframe['rsi'].shift(3) > coin_params.get('momentum_rsi_pivot', 55)) & (dataframe['rsi'] < coin_params.get('momentum_rsi_trigger_short', 48)) & (dataframe['close'] < dataframe['ema_short']) & (dataframe['volume'] > dataframe['volume'].shift(1)) ) | # Scenario 3: Breaking down from consolidation with momentum ( (dataframe['close'] < dataframe['low'].rolling(5).min().shift(1)) & (dataframe['rsi'] < 50) & (dataframe['volume'] > dataframe['volume'].rolling(10).mean() * 1.5) ) ) & # Basic trend filter (less restrictive) (dataframe['ema_short'] <= dataframe['ema_long']) & # Volume confirmation (flexible) (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('momentum_volume_window', 10)).mean() * coin_params.get('momentum_volume_multiplier', 1.0)) & # Not in extreme oversold (dataframe['close'] > dataframe['kc_lowerband'] * coin_params.get('momentum_kc_lower_factor', 1.002)) & # Momentum strength (dataframe['adx'] > coin_params.get('momentum_adx_min', 20)) & has_volume ) # === ADD NEW MOMENTUM BREAKOUT CONDITIONS === # Momentum Breakout Long long_momentum_breakout = ( # Trend context using existing trend parameters (dataframe['ema_short'] > dataframe['ema_long']) & # Overall bullish trend (dataframe['adx'] > coin_params.get('momentum_adx_min', 25)) & # Strong momentum # Enhanced breakout detection (dataframe['close'] > dataframe['high'].rolling(coin_params.get('trend_close_lookback', 2) * 10).max().shift(1)) & # Longer period breakout # Volume confirmation using existing parameters (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('momentum_volume_window', 10)).mean() * coin_params.get('volume_breakout_multiplier', 2.5)) & # Your existing volume breakout param # RSI positioning using existing parameters (dataframe['rsi'] > coin_params.get('volume_breakout_rsi_min', 45.0)) & (dataframe['rsi'] < coin_params.get('volume_breakout_rsi_max', 60.0)) & # Momentum strength filter (dataframe['close'] > dataframe['close'].shift(coin_params.get('momentum_high_lookback', 2))) & # ATR expansion for volatility (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('trend_atr_window', 20)).mean() * coin_params.get('trend_atr_multiplier', 1.0)) & has_volume ) # Momentum Breakout Short short_momentum_breakout = ( # Trend context using existing trend parameters (dataframe['ema_short'] < dataframe['ema_long']) & # Overall bearish trend (dataframe['adx'] > coin_params.get('momentum_adx_min', 25)) & # Strong momentum # Enhanced breakdown detection (dataframe['close'] < dataframe['low'].rolling(coin_params.get('trend_close_lookback', 2) * 10).min().shift(1)) & # Longer period breakdown # Volume confirmation using existing parameters (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('momentum_volume_window', 10)).mean() * coin_params.get('volume_breakout_multiplier', 2.5)) & # Your existing volume breakout param # RSI positioning - inverted for short (dataframe['rsi'] < coin_params.get('breakdown_rsi_upper', 60)) & # Using your breakdown params (dataframe['rsi'] > coin_params.get('breakdown_rsi_lower', 40)) & # Avoid oversold bounces # Momentum strength filter (dataframe['close'] < dataframe['close'].shift(coin_params.get('momentum_low_lookback', 2))) & # ATR expansion for volatility (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('trend_atr_window', 20)).mean() * coin_params.get('trend_atr_multiplier', 1.0)) & has_volume ) # === ADD PULLBACK MOMENTUM CONDITIONS === # Using existing optimized parameters for better confluence and filtering # Enhanced Long Pullback Momentum long_pullback_momentum = ( # Stronger trend context - multiple confirmations (dataframe['ema_short'] > dataframe['ema_long']) & (dataframe['ema50'] > dataframe['ema200']) & # Longer-term bullish context (dataframe['adx'] > coin_params.get('momentum_adx_min', 25)) & # Strong trend momentum # Better pullback detection - more substantial pullback required ( # Touched EMA20 support (existing) (dataframe['low'] <= dataframe['ema20']) & # Must have pulled back meaningfully from recent high (dataframe['low'] < dataframe['high'].rolling(coin_params.get('momentum_high_lookback', 2) * 3).max().shift(1) * 0.98) & # Price was above EMA20 recently (confirms this is a pullback, not breakdown) (dataframe['close'].shift(coin_params.get('momentum_rsi_lookback', 3)) > dataframe['ema20'].shift(coin_params.get('momentum_rsi_lookback', 3))) ) & # Stronger bounce confirmation (dataframe['close'] > dataframe['ema_short']) & # Back above support (dataframe['close'] > dataframe['open']) & # Green candle (dataframe['close'] > dataframe['close'].shift(1)) & # Higher close than previous # Enhanced volume and momentum confirmation (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('momentum_volume_window', 10)).mean() * coin_params.get('momentum_volume_multiplier', 1.2) * 1.5) & # 1.8x total volume requirement # Better RSI positioning and momentum (dataframe['rsi'] > coin_params.get('momentum_rsi_trigger', 55)) & # Above midpoint (dataframe['rsi'] < coin_params.get('volume_breakout_rsi_max', 60.0)) & # Not overbought (dataframe['rsi'] > dataframe['rsi'].rolling(coin_params.get('momentum_rsi_lookback', 3)).mean()) & # RSI improving over period # Price structure confirmation (dataframe['close'] > dataframe['kc_middleband']) & # Above middle of range (dataframe['close'] < dataframe['kc_upperband'] * coin_params.get('momentum_kc_upper_factor', 0.995)) & # Not at extreme top # Volatility and market conditions (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('trend_atr_window', 20)).mean() * coin_params.get('trend_atr_multiplier', 1.0)) & # Sufficient volatility has_volume ) # Enhanced Short Pullback Momentum short_pullback_momentum = ( # Stronger trend context - multiple confirmations (dataframe['ema_short'] < dataframe['ema_long']) & (dataframe['ema50'] < dataframe['ema200']) & # Longer-term bearish context (dataframe['adx'] > coin_params.get('momentum_adx_min', 25)) & # Strong trend momentum # Better pullback detection - more substantial pullback required ( # Touched EMA20 resistance (existing) (dataframe['high'] >= dataframe['ema20']) & # Must have pulled back meaningfully from recent low (dataframe['high'] > dataframe['low'].rolling(coin_params.get('momentum_low_lookback', 2) * 3).min().shift(1) * 1.02) & # Price was below EMA20 recently (confirms this is a pullback, not breakout) (dataframe['close'].shift(coin_params.get('momentum_rsi_lookback', 3)) < dataframe['ema20'].shift(coin_params.get('momentum_rsi_lookback', 3))) ) & # Stronger rejection confirmation (dataframe['close'] < dataframe['ema_short']) & # Back below resistance (dataframe['close'] < dataframe['open']) & # Red candle (dataframe['close'] < dataframe['close'].shift(1)) & # Lower close than previous # Enhanced volume and momentum confirmation (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('momentum_volume_window', 10)).mean() * coin_params.get('momentum_volume_multiplier', 1.2) * 1.5) & # 1.8x total volume requirement # Better RSI positioning and momentum (dataframe['rsi'] < coin_params.get('momentum_rsi_trigger_short', 45)) & # Below midpoint (dataframe['rsi'] > coin_params.get('volume_breakout_rsi_min', 45.0) - 15) & # Not oversold (30+) (dataframe['rsi'] < dataframe['rsi'].rolling(coin_params.get('momentum_rsi_lookback', 3)).mean()) & # RSI declining over period # Price structure confirmation (dataframe['close'] < dataframe['kc_middleband']) & # Below middle of range (dataframe['close'] > dataframe['kc_lowerband'] * coin_params.get('momentum_kc_lower_factor', 1.005)) & # Not at extreme bottom # Volatility and market conditions (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('trend_atr_window', 20)).mean() * coin_params.get('trend_atr_multiplier', 1.0)) & # Sufficient volatility has_volume ) # === SECONDARY CONDITIONS === long_condition_secondary = ( bullish_divergence & (dataframe['close'] > dataframe['close'].shift(coin_params.get('secondary_close_lookback', 2))) & (dataframe['rsi'].shift(1) > coin_params.get('secondary_rsi_min', 35)) & (dataframe['rsi'].shift(1) < coin_params.get('secondary_rsi_max', 70)) & has_volume ) short_condition_secondary = ( bearish_divergence & (dataframe['close'] < dataframe['close'].shift(coin_params.get('secondary_close_lookback', 2))) & (dataframe['rsi'].shift(1) > coin_params.get('secondary_rsi_min_short', 30)) & (dataframe['rsi'].shift(1) < coin_params.get('secondary_rsi_max_short', 80)) & has_volume ) # === TERTIARY CONDITIONS === long_condition_tertiary = ( bullish_divergence & (dataframe['close'] > dataframe['ema_short']) & (dataframe['rsi'].shift(1) > coin_params.get('tertiary_rsi_min', 40)) & (dataframe['rsi'].shift(1) < coin_params.get('tertiary_rsi_max', 65)) & (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('tertiary_volume_window', 10)).mean() * coin_params.get('tertiary_volume_multiplier', 1.0)) & has_volume ) short_condition_tertiary = ( bearish_divergence & (dataframe['close'] < dataframe['ema_short']) & (dataframe['rsi'].shift(1) > coin_params.get('tertiary_rsi_min_short', 35)) & (dataframe['rsi'].shift(1) < coin_params.get('tertiary_rsi_max_short', 75)) & (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('tertiary_volume_window', 10)).mean() * coin_params.get('tertiary_volume_multiplier', 1.0)) & has_volume ) # === QUATERNARY CONDITIONS (EMA CROSSOVER) === long_condition_quaternary = ( bullish_divergence & (dataframe['close'].shift(1) < dataframe['ema20'].shift(1)) & (dataframe['close'] > dataframe['ema_short']) & (dataframe['rsi'] > coin_params.get('quaternary_rsi_min', 45)) & (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('quaternary_volume_window', 5)).mean() * coin_params.get('quaternary_volume_multiplier', 1.2)) & has_volume ) short_condition_quaternary = ( bearish_divergence & (dataframe['close'].shift(1) > dataframe['ema20'].shift(1)) & (dataframe['close'] < dataframe['ema_short']) & (dataframe['rsi'] < coin_params.get('quaternary_rsi_max', 55)) & (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('quaternary_volume_window', 5)).mean() * coin_params.get('quaternary_volume_multiplier', 1.2)) & has_volume ) # === FIFTH CONDITIONS (KC MIDDLE BAND) === long_condition_fifth = ( bullish_divergence & (dataframe['close'] > dataframe['kc_middleband']) & (dataframe['close'] < dataframe['kc_upperband']) & (dataframe['close'] > dataframe['close'].shift(1)) & (dataframe['rsi'].shift(1) > coin_params.get('fifth_rsi_min', 35)) & (dataframe['rsi'].shift(1) < coin_params.get('fifth_rsi_max', 70)) & has_volume ) short_condition_fifth = ( bearish_divergence & (dataframe['close'] < dataframe['kc_middleband']) & (dataframe['close'] > dataframe['kc_lowerband']) & (dataframe['close'] < dataframe['close'].shift(1)) & (dataframe['rsi'].shift(1) > coin_params.get('fifth_rsi_min_short', 30)) & (dataframe['rsi'].shift(1) < coin_params.get('fifth_rsi_max_short', 65)) & has_volume ) # === SIXTH CONDITIONS - STRONG DIVERGENCE COUNT === long_condition_sixth = ( (dataframe['total_bullish_divergences_count'] >= coin_params.get('sixth_min_div_count', 2)) & bullish_divergence & (dataframe['rsi'].shift(1) > coin_params.get('sixth_rsi_min', 30)) & (dataframe['rsi'].shift(1) < coin_params.get('sixth_rsi_max', 75)) & has_volume ) short_condition_sixth = ( (dataframe['total_bearish_divergences_count'] >= coin_params.get('sixth_min_div_count', 2)) & bearish_divergence & (dataframe['rsi'].shift(1) > coin_params.get('sixth_rsi_min_short', 25)) & (dataframe['rsi'].shift(1) < coin_params.get('sixth_rsi_max_short', 70)) & has_volume ) # Enhanced Seventh Condition - Uses Optuna Parameters long_condition_seventh = ( # 1. Trend Context - Bullish bias (dataframe['ema_short'] > dataframe['ema_long']) & (dataframe['ema50'] > dataframe['ema200']) & # Add stronger trend filter # 2. Pullback and Recovery Pattern ( # Was below EMA20 recently (pullback happened) (dataframe['close'].shift(coin_params.get('seventh_close_lookback', 2)) < dataframe['ema20'].shift(coin_params.get('seventh_close_lookback', 2))) & # Now recovering above EMA20 (breakout from pullback) (dataframe['close'] > dataframe['ema_short']) & # Clear break above with momentum (dataframe['close'] > dataframe['ema20'] * 1.002) # 0.2% above EMA20 ) & # 3. Price Action Confirmation ( # Current candle is green (bullish momentum) (dataframe['close'] > dataframe['open']) & # Recent momentum building (dataframe['close'] > dataframe['close'].shift(1)) & # Not too extended above EMA20 (dataframe['close'] < dataframe['ema20'] * 1.02) # Within 2% of EMA20 ) & # 4. Volume and Strength Filters (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('seventh_volume_window', 20)).mean() * coin_params.get('seventh_volume_multiplier', 1.2)) & (dataframe['rsi'].shift(1) > coin_params.get('seventh_rsi_min', 35)) & # Not oversold (dataframe['rsi'].shift(1) < coin_params.get('seventh_rsi_max', 65)) & # Not overbought (dataframe['rsi'] > dataframe['rsi'].shift(1)) & # RSI improving # 5. Market Structure (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('seventh_atr_window', 20)).mean() * coin_params.get('seventh_atr_multiplier', 0.7)) & (dataframe['close'] > dataframe['kc_middleband']) & # Above middle band (dataframe['adx'] > coin_params.get('seventh_adx_min', 18)) & # 6. Recent Context Validation (dataframe['low'] <= dataframe['ema20'].shift(1)) & # Recently touched EMA20 support (dataframe['close'] > dataframe['low'] * 1.005) & # Clear bounce from low has_volume ) # Bear E7 - Improved Short Condition short_condition_seventh = ( # 1. Trend Context - Bearish bias (dataframe['ema_short'] < dataframe['ema_long']) & (dataframe['ema50'] < dataframe['ema200']) & # Strong bearish trend # 2. Pullback and Rejection Pattern ( # Was above EMA20 recently (pullback happened) (dataframe['close'].shift(coin_params.get('seventh_close_lookback', 2)) > dataframe['ema20'].shift(coin_params.get('seventh_close_lookback', 2))) & # Now rejected below EMA20 (breakdown from pullback) (dataframe['close'] < dataframe['ema_short']) & # Clear break below with momentum (dataframe['close'] < dataframe['ema20'] * 0.998) # 0.2% below EMA20 ) & # 3. Price Action Confirmation ( # Current candle is red (bearish momentum) (dataframe['close'] < dataframe['open']) & # Recent momentum declining (dataframe['close'] < dataframe['close'].shift(1)) & # Actually touched resistance (not just any EMA20 cross) (dataframe['high'] >= dataframe['ema_short'] * 0.999) & # Very close touch # Strong rejection with upper wick (dataframe['high'] - dataframe['close']) > (dataframe['close'] - dataframe['low']) ) & # 4. Volume and Strength Filters (dataframe['volume'] > dataframe['volume'].rolling(coin_params.get('seventh_volume_window', 20)).mean() * coin_params.get('seventh_volume_multiplier', 1.2)) & (dataframe['rsi'].shift(1) > coin_params.get('seventh_rsi_min_short', 35)) & # Not oversold (dataframe['rsi'].shift(1) < coin_params.get('seventh_rsi_max_short', 65)) & # Not overbought (dataframe['rsi'] < dataframe['rsi'].shift(1)) & # RSI declining # 5. Market Structure (dataframe['atr'] > dataframe['atr'].rolling(coin_params.get('seventh_atr_window', 20)).mean() * coin_params.get('seventh_atr_multiplier', 0.7)) & (dataframe['close'] < dataframe['kc_middleband']) & # Below middle band (dataframe['adx'] > coin_params.get('seventh_adx_min', 18)) & # 6. Recent Context Validation (dataframe['high'] >= dataframe['ema20'].shift(1)) & # Recently touched EMA20 resistance (dataframe['close'] < dataframe['high'] * 0.995) & # Clear rejection from high # Was showing recent strength before rejection (dataframe['rsi'].shift(3) > 40) & has_volume ) # === PURE DIVERGENCE CONDITIONS - FALLBACK === long_condition_div = ( bullish_divergence & (dataframe['rsi'].shift(1) > coin_params.get('div_rsi_min', 25)) & (dataframe['rsi'].shift(1) < coin_params.get('div_rsi_max', 80)) & has_volume ) short_condition_div = ( bearish_divergence & (dataframe['rsi'].shift(1) > coin_params.get('div_rsi_min_short', 20)) & (dataframe['rsi'].shift(1) < coin_params.get('div_rsi_max_short', 75)) & has_volume ) strong_bear_breakdown = ( (dataframe['close'] < dataframe['ema21']) & (dataframe['ma_alignment_bear']) & # EMAs aligned bearish (dataframe['volume_ratio'] > coin_params.get('breakdown_volume_threshold', 1.5)) & # Strong volume (dataframe['rsi'] < coin_params.get('breakdown_rsi_upper', 60)) & # Not overbought (dataframe['adx'] > coin_params.get('breakdown_adx_threshold', 25)) & # Strong trend (dataframe['strong_red']) & # Volume-confirmed red candle (dataframe['price_vs_ema21'] < -coin_params.get('breakdown_price_deviation', 1.0)) & # Below EMA21 (dataframe['rejected_at_ema21']) & # Previous rejection at EMA21 (dataframe['close'] < dataframe['ema50']) # Below intermediate MA ) # Strong Bull Breakdown (Reversal) Pattern strong_bull_breakdown = ( (dataframe['close'] > dataframe['ema21']) & (dataframe['ma_alignment_bull']) & # EMAs aligned bullish (dataframe['volume_ratio'] > coin_params.get('breakdown_volume_threshold', 1.5)) & # Strong volume (dataframe['rsi'] > coin_params.get('breakdown_rsi_lower', 40)) & # Not oversold (dataframe['adx'] > coin_params.get('breakdown_adx_threshold', 25)) & # Strong trend (dataframe['strong_green']) & # Volume-confirmed green candle (dataframe['price_vs_ema21'] > coin_params.get('breakdown_price_deviation', 1.0)) & # Above EMA21 (dataframe['bounced_from_ema21']) & # Previous bounce from EMA21 (dataframe['close'] > dataframe['ema50']) # Above intermediate MA ) # Resistance Rejection Pattern (Bears) resistance_rejection_bear = ( (dataframe['rejected_at_ema50']) & # Rejected at EMA50 resistance (dataframe['red_candle']) & # Follow-through down (dataframe['volume_ratio'] > coin_params.get('breakdown_volume_confirmation', 1.2)) & # Volume confirmation (dataframe['rsi'] < 65) & # Not extremely overbought (dataframe['close'] < dataframe['ema21']) & # Below shorter MA (dataframe['ma_alignment_bear']) # Overall bearish alignment ) # Support Rejection Pattern (Bulls) support_rejection_bull = ( (dataframe['bounced_from_ema50']) & # Bounced from EMA50 support (dataframe['green_candle']) & # Follow-through up (dataframe['volume_ratio'] > coin_params.get('breakdown_volume_confirmation', 1.2)) & # Volume confirmation (dataframe['rsi'] > 35) & # Not extremely oversold (dataframe['close'] > dataframe['ema21']) & # Above shorter MA (dataframe['ma_alignment_bull']) # Overall bullish alignment ) # Higher Timeframe Bias (using EMA200 as proxy) higher_tf_bearish = ( (dataframe['close'] < dataframe['ema200']) & # Below long-term trend (dataframe['ema50'] < dataframe['ema200']) & # MAs aligned bearish (dataframe['ema21'] < dataframe['ema50']) # Short-term also bearish ) higher_tf_bullish = ( (dataframe['close'] > dataframe['ema200']) & # Above long-term trend (dataframe['ema50'] > dataframe['ema200']) & # MAs aligned bullish (dataframe['ema21'] > dataframe['ema50']) # Short-term also bullish ) # Enhanced breakdown with multiple confirmation enhanced_bear_breakdown = ( (strong_bear_breakdown | resistance_rejection_bear) & higher_tf_bearish & (dataframe['price_vs_ema200'] < 0) # Below long-term trend ) enhanced_bull_breakdown = ( (strong_bull_breakdown | support_rejection_bull) & higher_tf_bullish & (dataframe['price_vs_ema200'] > 0) ) simple_breakout_long = ( (dataframe['close'] > dataframe['high'].rolling(10).max().shift(1)) & (dataframe['volume'] > dataframe['volume'].rolling(15).mean() * coin_params.get('breakout_volume_multiplier', 1.3)) & (dataframe['rsi'] > 40) & (dataframe['rsi'] < 75) & (dataframe['ema_short'] > dataframe['ema_long']) ) simple_breakdown_short = ( (dataframe['close'] < dataframe['low'].rolling(10).min().shift(1)) & (dataframe['volume'] > dataframe['volume'].rolling(15).mean() * coin_params.get('breakdown_volume_multiplier', 1.3)) & (dataframe['rsi'] > 35) & (dataframe['rsi'] < 60) & (dataframe['ema_short'] < dataframe['ema_long']) ) # === OPTIMIZED RSV1 REVERSAL CONDITIONS === # Replace your current reversal conditions with these optimized versions # Long reversal conditions - using coin-specific parameters oversold_conditions = [ (dataframe['rsi'] < coin_params.get('rsv1_rsi_oversold', 25.0)), (dataframe['willr'] < coin_params.get('rsv1_willr_oversold', -80.0)), (dataframe['cci'] < coin_params.get('rsv1_cci_oversold', -100.0)), (dataframe['close'] <= dataframe['bb_lower']), (dataframe['bb_percent'] < coin_params.get('rsv1_bb_oversold', 0.1)), ] long_reversal_signals = [ (dataframe['volume_ratio'] > coin_params.get('rsv1_volume_factor', 1.5)), (dataframe['close'] > dataframe['open']), (dataframe['macdhist'] > dataframe['macdhist'].shift(1)), (dataframe['price_position'] < coin_params.get('rsv1_price_position_low', 0.2)), ] long_trend_filter = [ (dataframe['ema_short'] > dataframe['ema_short'].shift(coin_params.get('trend_close_lookback', 3))), (dataframe['close'] > dataframe['close'].shift(coin_params.get('secondary_close_lookback', 2))), ] long_condition_reversal = ( (sum(oversold_conditions) >= coin_params.get('rsv1_min_oversold_conditions', 3)) & (sum(long_reversal_signals) >= coin_params.get('rsv1_min_reversal_signals', 2)) & (sum(long_trend_filter) >= coin_params.get('rsv1_min_trend_filters', 1)) & (dataframe['volume'] > 0) ) # Short reversal conditions - using coin-specific parameters overbought_conditions = [ (dataframe['rsi'] > coin_params.get('rsv1_rsi_overbought', 75.0)), (dataframe['willr'] > coin_params.get('rsv1_willr_overbought', -20.0)), (dataframe['cci'] > coin_params.get('rsv1_cci_overbought', 100.0)), (dataframe['close'] >= dataframe['bb_upper']), (dataframe['bb_percent'] > coin_params.get('rsv1_bb_overbought', 0.9)), ] short_reversal_signals = [ (dataframe['volume_ratio'] > coin_params.get('rsv1_volume_factor', 1.5)), (dataframe['close'] < dataframe['open']), (dataframe['macdhist'] < dataframe['macdhist'].shift(1)), (dataframe['price_position'] > coin_params.get('rsv1_price_position_high', 0.8)), ] short_trend_filter = [ (dataframe['ema_short'] < dataframe['ema_short'].shift(coin_params.get('trend_close_lookback', 3))), (dataframe['close'] < dataframe['close'].shift(coin_params.get('secondary_close_lookback', 2))), ] short_condition_reversal = ( (sum(overbought_conditions) >= coin_params.get('rsv1_min_overbought_conditions', 3)) & (sum(short_reversal_signals) >= coin_params.get('rsv1_min_reversal_signals', 2)) & (sum(short_trend_filter) >= coin_params.get('rsv1_min_trend_filters', 1)) & (dataframe['volume'] > 0) ) # === MEAN REVERSION CONDITIONS === mean_reversion_long = ( (dataframe['bb_percent'].shift(1) < coin_params.get('bb_oversold_threshold', 0.15)) & (dataframe['rsi'].shift(1) < coin_params.get('mean_reversion_rsi_oversold', 28)) & (dataframe['close'] > dataframe['low'].shift(1)) & # Not continuing down (dataframe['volume_ratio'].shift(1) > coin_params.get('volume_ratio_min', 1.1)) & (dataframe['close'] > dataframe['open']) & # Recovery candle has_volume ) mean_reversion_short = ( (dataframe['bb_percent'].shift(1) > coin_params.get('bb_overbought_threshold', 0.85)) & (dataframe['rsi'].shift(1) > coin_params.get('mean_reversion_rsi_overbought', 72)) & (dataframe['close'] < dataframe['high'].shift(1)) & # Not continuing up (dataframe['volume_ratio'].shift(1) > coin_params.get('volume_ratio_min', 1.1)) & (dataframe['close'] < dataframe['open']) & # Rejection candle has_volume ) # === MOMENTUM CONTINUATION CONDITIONS === momentum_continuation_long = ( (dataframe['ema9'] > dataframe['ema20']) & (dataframe['ema_short'] > dataframe['ema_long']) & (dataframe['close'].shift(1) < dataframe['ema9'].shift(1)) & # Pullback (dataframe['close'] > dataframe['ema9']) & # Back above (dataframe['price_position'].shift(1) > coin_params.get('momentum_pullback_max', 0.35)) & (dataframe['adx'].shift(1) > coin_params.get('adx_trending_min', 25)) & (dataframe['volume_ratio'].shift(1) > coin_params.get('volume_ratio_min', 1.1)) & has_volume ) momentum_continuation_short = ( (dataframe['ema9'] < dataframe['ema20']) & (dataframe['ema_short'] < dataframe['ema_long']) & (dataframe['close'].shift(1) > dataframe['ema9'].shift(1)) & # Pullback (dataframe['close'] < dataframe['ema9']) & # Back below (dataframe['price_position'].shift(1) < coin_params.get('momentum_pullback_min', 0.65)) & (dataframe['adx'].shift(1) > coin_params.get('adx_trending_min', 25)) & (dataframe['volume_ratio'].shift(1) > coin_params.get('volume_ratio_min', 1.1)) & has_volume ) # Combine breakdown patterns long_condition_breakdown = enhanced_bull_breakdown short_condition_breakdown = enhanced_bear_breakdown long_condition_hqdiv = bullish_divergence short_condition_hqdiv = bearish_divergence # 1. PRIMARY CONDITIONS (E1) - Check lock before assignment if not signal_lock_enabled or not self.is_signal_locked('Bull_E1', pair): dataframe.loc[long_condition_primary, 'enter_long'] = 1 dataframe.loc[long_condition_primary, 'enter_tag'] = 'Bull_E1' else: print(f"Signal LOCKED: Bull_E1 for {pair}") if not signal_lock_enabled or not self.is_signal_locked('Bear_E1', pair): dataframe.loc[short_condition_primary, 'enter_short'] = 1 dataframe.loc[short_condition_primary, 'enter_tag'] = 'Bear_E1' else: print(f"Signal LOCKED: Bear_E1 for {pair}") # 2. SUPPORT/RESISTANCE BOUNCE CONDITIONS if not signal_lock_enabled or not self.is_signal_locked('Bull_SR_Bounce', pair): sr_bounce_long = long_condition_support_bounce & (dataframe['enter_tag'] == "") dataframe.loc[sr_bounce_long, 'enter_long'] = 1 dataframe.loc[sr_bounce_long, 'enter_tag'] = 'Bull_SR_Bounce' if not signal_lock_enabled or not self.is_signal_locked('Bear_SR_Bounce', pair): sr_bounce_short = short_condition_resistance_bounce & (dataframe['enter_tag'] == "") dataframe.loc[sr_bounce_short, 'enter_short'] = 1 dataframe.loc[sr_bounce_short, 'enter_tag'] = 'Bear_SR_Bounce' # 3. STRONG SUPPORT/RESISTANCE CONDITIONS if not signal_lock_enabled or not self.is_signal_locked('Bull_Strong_Support', pair): strong_sr_long = long_condition_strong_support & (dataframe['enter_tag'] == "") dataframe.loc[strong_sr_long, 'enter_long'] = 1 dataframe.loc[strong_sr_long, 'enter_tag'] = 'Bull_Strong_Support' if not signal_lock_enabled or not self.is_signal_locked('Bear_Strong_Resistance', pair): strong_sr_short = short_condition_strong_resistance & (dataframe['enter_tag'] == "") dataframe.loc[strong_sr_short, 'enter_short'] = 1 dataframe.loc[strong_sr_short, 'enter_tag'] = 'Bear_Strong_Resistance' # 4. TREND BREAKOUT CONDITIONS if not signal_lock_enabled or not self.is_signal_locked('Bull_Trend', pair): trend_long = long_condition_trend & (dataframe['enter_tag'] == "") dataframe.loc[trend_long, 'enter_long'] = 1 dataframe.loc[trend_long, 'enter_tag'] = 'Bull_Trend' if not signal_lock_enabled or not self.is_signal_locked('Bear_Trend', pair): trend_short = short_condition_trend & (dataframe['enter_tag'] == "") dataframe.loc[trend_short, 'enter_short'] = 1 dataframe.loc[trend_short, 'enter_tag'] = 'Bear_Trend' # 5. MOMENTUM CONDITIONS if not signal_lock_enabled or not self.is_signal_locked('Bull_Momentum', pair): momentum_long = long_condition_momentum & (dataframe['enter_tag'] == "") dataframe.loc[momentum_long, 'enter_long'] = 1 dataframe.loc[momentum_long, 'enter_tag'] = 'Bull_Momentum' if not signal_lock_enabled or not self.is_signal_locked('Bear_Momentum', pair): momentum_short = short_condition_momentum & (dataframe['enter_tag'] == "") dataframe.loc[momentum_short, 'enter_short'] = 1 dataframe.loc[momentum_short, 'enter_tag'] = 'Bear_Momentum' # 6. MOMENTUM BREAKOUT CONDITIONS (Enhanced) if not signal_lock_enabled or not self.is_signal_locked('Bull_Momentum_Breakout', pair): breakout_long = long_momentum_breakout & (dataframe['enter_tag'] == "") dataframe.loc[breakout_long, 'enter_long'] = 1 dataframe.loc[breakout_long, 'enter_tag'] = 'Bull_Momentum_Breakout' if not signal_lock_enabled or not self.is_signal_locked('Bear_Momentum_Breakout', pair): breakout_short = short_momentum_breakout & (dataframe['enter_tag'] == "") dataframe.loc[breakout_short, 'enter_short'] = 1 dataframe.loc[breakout_short, 'enter_tag'] = 'Bear_Momentum_Breakout' # 7. PULLBACK MOMENTUM CONDITIONS (Enhanced) if not signal_lock_enabled or not self.is_signal_locked('Bull_Pullback_Momentum', pair): pullback_long = long_pullback_momentum & (dataframe['enter_tag'] == "") dataframe.loc[pullback_long, 'enter_long'] = 1 dataframe.loc[pullback_long, 'enter_tag'] = 'Bull_Pullback_Momentum' if not signal_lock_enabled or not self.is_signal_locked('Bear_Pullback_Momentum', pair): pullback_short = short_pullback_momentum & (dataframe['enter_tag'] == "") dataframe.loc[pullback_short, 'enter_short'] = 1 dataframe.loc[pullback_short, 'enter_tag'] = 'Bear_Pullback_Momentum' # 8. SECONDARY CONDITIONS (E2) if not signal_lock_enabled or not self.is_signal_locked('Bull_E2', pair): secondary_long = long_condition_secondary & (dataframe['enter_tag'] == "") dataframe.loc[secondary_long, 'enter_long'] = 1 dataframe.loc[secondary_long, 'enter_tag'] = 'Bull_E2' if not signal_lock_enabled or not self.is_signal_locked('Bear_E2', pair): secondary_short = short_condition_secondary & (dataframe['enter_tag'] == "") dataframe.loc[secondary_short, 'enter_short'] = 1 dataframe.loc[secondary_short, 'enter_tag'] = 'Bear_E2' # 9. BREAKDOWN CONDITIONS if not signal_lock_enabled or not self.is_signal_locked('Bull_Breakdown', pair): breakdown_long = long_condition_breakdown & (dataframe['enter_tag'] == "") dataframe.loc[breakdown_long, 'enter_long'] = 1 dataframe.loc[breakdown_long, 'enter_tag'] = 'Bull_Breakdown' if not signal_lock_enabled or not self.is_signal_locked('Bear_Breakdown', pair): breakdown_short = short_condition_breakdown & (dataframe['enter_tag'] == "") dataframe.loc[breakdown_short, 'enter_short'] = 1 dataframe.loc[breakdown_short, 'enter_tag'] = 'Bear_Breakdown' # 10. PURE DIVERGENCE CONDITIONS (Lower Priority) if not signal_lock_enabled or not self.is_signal_locked('Bull_Div', pair): div_long = long_condition_div & (dataframe['enter_tag'] == "") dataframe.loc[div_long, 'enter_long'] = 1 dataframe.loc[div_long, 'enter_tag'] = 'Bull_Div' if not signal_lock_enabled or not self.is_signal_locked('Bear_Div', pair): div_short = short_condition_div & (dataframe['enter_tag'] == "") dataframe.loc[div_short, 'enter_short'] = 1 dataframe.loc[div_short, 'enter_tag'] = 'Bear_Div' # 11. HIGH QUALITY DIVERGENCE CONDITIONS (Lowest Priority) if not signal_lock_enabled or not self.is_signal_locked('Bull_Div_HQ', pair): hqdiv_long = long_condition_hqdiv & (dataframe['enter_tag'] == "") dataframe.loc[hqdiv_long, 'enter_long'] = 1 dataframe.loc[hqdiv_long, 'enter_tag'] = 'Bull_Div_HQ' if not signal_lock_enabled or not self.is_signal_locked('Bear_Div_HQ', pair): hqdiv_short = short_condition_hqdiv & (dataframe['enter_tag'] == "") dataframe.loc[hqdiv_short, 'enter_short'] = 1 dataframe.loc[hqdiv_short, 'enter_tag'] = 'Bear_Div_HQ' # 12. RSV1 if not signal_lock_enabled or not self.is_signal_locked('Bull_RSV1', pair): rsv1_long = long_condition_reversal & (dataframe['enter_tag'] == "") dataframe.loc[rsv1_long, 'enter_long'] = 1 dataframe.loc[rsv1_long, 'enter_tag'] = 'Bull_RSV1' if not signal_lock_enabled or not self.is_signal_locked('Bear_RSV1', pair): rsv1_short = short_condition_hqdiv & (dataframe['enter_tag'] == "") dataframe.loc[rsv1_short, 'enter_short'] = 1 dataframe.loc[rsv1_short, 'enter_tag'] = 'Bear_RSV1' # 12. MR1 if not signal_lock_enabled or not self.is_signal_locked('Bull_MR1', pair): mr1_long = mean_reversion_long & (dataframe['enter_tag'] == "") dataframe.loc[mr1_long, 'enter_long'] = 1 dataframe.loc[mr1_long, 'enter_tag'] = 'Bull_MR1' if not signal_lock_enabled or not self.is_signal_locked('Bear_MR1', pair): mr1_short = mean_reversion_short & (dataframe['enter_tag'] == "") dataframe.loc[mr1_short, 'enter_short'] = 1 dataframe.loc[mr1_short, 'enter_tag'] = 'Bear_MR1' # 12. MC1 if not signal_lock_enabled or not self.is_signal_locked('Bull_MC1', pair): mc1_long = momentum_continuation_long & (dataframe['enter_tag'] == "") dataframe.loc[mc1_long, 'enter_long'] = 1 dataframe.loc[mc1_long, 'enter_tag'] = 'Bull_MC1' if not signal_lock_enabled or not self.is_signal_locked('Bear_MC1', pair): mc1_short = momentum_continuation_short & (dataframe['enter_tag'] == "") dataframe.loc[mc1_short, 'enter_short'] = 1 dataframe.loc[mc1_short, 'enter_tag'] = 'Bear_MC1' if not signal_lock_enabled or not self.is_signal_locked('Bull_E7', pair): seventh_long = long_condition_seventh & (dataframe['enter_tag'] == "") dataframe.loc[seventh_long, 'enter_long'] = 1 dataframe.loc[seventh_long, 'enter_tag'] = 'Bull_E7' else: print(f"Signal LOCKED: Bull_E7 for {pair}") if not signal_lock_enabled or not self.is_signal_locked('Bear_E7', pair): seventh_short = short_condition_seventh & (dataframe['enter_tag'] == "") dataframe.loc[seventh_short, 'enter_short'] = 1 dataframe.loc[seventh_short, 'enter_tag'] = 'Bear_E7' else: print(f"Signal LOCKED: Bear_E7 for {pair}") # 12. SIMPLE BREAKOUT CONDITIONS (New) if not signal_lock_enabled or not self.is_signal_locked('Bull_Simple_Breakout', pair): simple_breakout = simple_breakout_long & (dataframe['enter_tag'] == "") dataframe.loc[simple_breakout, 'enter_long'] = 1 dataframe.loc[simple_breakout, 'enter_tag'] = 'Bull_Simple_Breakout' else: print(f"Signal LOCKED: Bull_Simple_Breakout for {pair}") if not signal_lock_enabled or not self.is_signal_locked('Bear_Simple_Breakdown', pair): simple_breakdown = simple_breakdown_short & (dataframe['enter_tag'] == "") dataframe.loc[simple_breakdown, 'enter_short'] = 1 dataframe.loc[simple_breakdown, 'enter_tag'] = 'Bear_Simple_Breakdown' else: print(f"Signal LOCKED: Bear_Simple_Breakdown for {pair}") # === LOGGING === if len(dataframe) > 0: last_row = dataframe.iloc[-1] if last_row.get('enter_long', 0) == 1 or last_row.get('enter_short', 0) == 1: logger.info(f"🚀 {metadata['pair']} ENTRY DETECTED!") logger.info(f" 🏷️ Tag: {last_row.get('enter_tag', '')}") logger.info(f" 📊 RSI: {last_row.get('rsi', 0):.1f}") logger.info(f" 💧 Volume Ratio: {last_row.get('volume_ratio', 0):.2f}") logger.info(f" 🎯 Bull Div Count: {last_row.get('total_bullish_divergences_count', 0)}") logger.info(f" 🎯 Bear Div Count: {last_row.get('total_bearish_divergences_count', 0)}") logger.info(f" 📈 Signal Strength: {last_row.get('signal_strength', 0)}") logger.info(f" 🎯 ADX: {last_row.get('adx', 0):.1f}") long_signals = (dataframe['enter_long'] == 1).sum() short_signals = (dataframe['enter_short'] == 1).sum() print(f" Generated {long_signals} long signals, {short_signals} short signals") return dataframe 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: """ Adaptive leverage based on signal strength """ try: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) if len(dataframe) > 0: current_signal_strength = dataframe['signal_strength'].iloc[-1] # Reduce leverage for weaker signals if current_signal_strength >= 8: return self.leverage_value # Full leverage for strong signals elif current_signal_strength >= 6: return self.leverage_value * 0.8 # 80% leverage elif current_signal_strength >= 4: return self.leverage_value * 0.6 # 60% leverage else: return self.leverage_value * 0.4 # 40% leverage for weak signals except: pass return self.leverage_value * 0.5 # Conservative fallback def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs) -> float: """ Enhanced dynamic stoploss with breakeven and trailing functionality Works seamlessly with your existing custom_exit """ try: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) if len(dataframe) < 1: return self.stoploss # Get market data last_row = dataframe.iloc[-1] atr = last_row.get('atr', 0) current_price = last_row['close'] volatility = last_row.get('volatility', 0.02) signal_strength = last_row.get('signal_strength', 5) entry_tag = trade.enter_tag or "" trade_duration_minutes = (current_time - trade.open_date_utc).total_seconds() / 60 # === BREAKEVEN LOGIC === # Move to breakeven once we hit decent profit breakeven_trigger = 0.025 # 2.5% profit if any(tag in entry_tag for tag in ['MR1', 'E1', 'E2']): breakeven_trigger = 0.02 # Earlier breakeven for mean reversion elif any(tag in entry_tag for tag in ['Trend', 'SR_Bounce']): breakeven_trigger = 0.035 # Later breakeven for trend trades # Move to breakeven + small buffer if current_profit >= breakeven_trigger: breakeven_stop = 0.003 # 0.3% positive (covers fees) return stoploss_from_open(breakeven_stop, current_profit, is_short=trade.is_short) # === DYNAMIC TRAILING LOGIC === # Start trailing after higher profit threshold trailing_trigger = 0.045 # 4.5% profit if current_profit >= trailing_trigger: # Entry-specific trailing distances if any(tag in entry_tag for tag in ['MR1', 'E1', 'E2']): trailing_distance = 0.015 # Tight trailing for mean reversion elif any(tag in entry_tag for tag in ['MC1', 'Momentum', 'Breakout']): trailing_distance = 0.025 # Normal trailing for momentum elif any(tag in entry_tag for tag in ['Trend', 'SR_Bounce', 'Strong']): trailing_distance = 0.035 # Wide trailing for trend else: trailing_distance = 0.02 # Default trailing # Adjust for volatility volatility_adj = min(1.5, max(0.7, volatility / 0.02)) trailing_distance *= volatility_adj # Adjust for signal strength if signal_strength >= 8: trailing_distance *= 1.2 # Wider trailing for strong signals elif signal_strength <= 4: trailing_distance *= 0.8 # Tighter trailing for weak signals return stoploss_from_open(trailing_distance, current_profit, is_short=trade.is_short) # === INITIAL DYNAMIC STOPLOSS (same as before) === if signal_strength >= 8: atr_multiplier = 3.0 elif signal_strength >= 6: atr_multiplier = 2.5 elif signal_strength >= 4: atr_multiplier = 2.0 else: atr_multiplier = 1.5 # Adjust for volatility volatility_adjustment = min(1.5, max(0.7, volatility / 0.02)) atr_multiplier *= volatility_adjustment # Calculate stop distance if atr > 0: stop_distance = (atr * atr_multiplier) / current_price else: stop_distance = 0.08 + (volatility * 2) # Entry type adjustments if any(tag in entry_tag for tag in ['MR1', 'E1', 'E2']): stop_distance *= 0.8 elif any(tag in entry_tag for tag in ['MC1', 'Momentum', 'Breakout']): stop_distance *= 1.0 elif any(tag in entry_tag for tag in ['Trend', 'SR_Bounce', 'Strong']): stop_distance *= 1.2 elif any(tag in entry_tag for tag in ['Div', 'RSV1']): stop_distance *= 0.9 # Safety limits min_stop = 0.04 max_stop = 0.15 stop_distance = max(min_stop, min(max_stop, stop_distance)) dynamic_stoploss = -stop_distance # Time-based tightening for initial stops if trade_duration_minutes > 60: time_factor = min(0.2, (trade_duration_minutes - 60) / 300) dynamic_stoploss *= (1 - time_factor) # Never worse than original dynamic_stoploss = max(dynamic_stoploss, self.stoploss) # Minimal logging if hasattr(self, '_stoploss_log_counter'): self._stoploss_log_counter = getattr(self, '_stoploss_log_counter', 0) + 1 else: self._stoploss_log_counter = 1 if self._stoploss_log_counter % 30 == 0: if current_profit >= trailing_trigger: logger.info(f"Trailing {pair}: Profit={current_profit:.3f}, Stop={dynamic_stoploss:.3f}, Tag={entry_tag}") elif current_profit >= breakeven_trigger: logger.info(f"Breakeven {pair}: Profit={current_profit:.3f}, Stop={dynamic_stoploss:.3f}, Tag={entry_tag}") else: logger.info(f"Dynamic SL {pair}: Signal={signal_strength}, Stop={dynamic_stoploss:.3f}, Tag={entry_tag}") return dynamic_stoploss except Exception as e: logger.error(f"Error in custom_stoploss for {pair}: {e}") return self.stoploss def initialize_divergences_lists(self, dataframe: DataFrame): """Initialize divergence tracking columns""" # Bullish Divergences dataframe["total_bullish_divergences"] = np.nan dataframe["total_bullish_divergences_count"] = 0 dataframe["total_bullish_divergences_names"] = '' # Bearish Divergences dataframe["total_bearish_divergences"] = np.nan dataframe["total_bearish_divergences_count"] = 0 dataframe["total_bearish_divergences_names"] = '' def get_iterators(self, dataframe): """Get pivot point iterators for divergence detection""" low_iterator = [] high_iterator = [] for index, row in enumerate(dataframe.itertuples(index=True, name='Pandas')): if np.isnan(row.pivot_lows): low_iterator.append(0 if len(low_iterator) == 0 else low_iterator[-1]) else: low_iterator.append(index) if np.isnan(row.pivot_highs): high_iterator.append(0 if len(high_iterator) == 0 else high_iterator[-1]) else: high_iterator.append(index) return high_iterator, low_iterator def add_divergences(self, dataframe: DataFrame, indicator: str, high_iterator, low_iterator): """Add divergence detection for a specific indicator""" (bearish_divergences, bearish_lines, bullish_divergences, bullish_lines) = self.divergence_finder_dataframe( dataframe, indicator, high_iterator, low_iterator) dataframe['bearish_divergence_' + indicator + '_occurence'] = bearish_divergences dataframe['bullish_divergence_' + indicator + '_occurence'] = bullish_divergences def divergence_finder_dataframe(self, dataframe: DataFrame, indicator_source: str, high_iterator, low_iterator) -> Tuple[pd.Series, pd.Series]: """Enhanced divergence finder with improved logic""" bearish_lines = [np.empty(len(dataframe['close'])) * np.nan] bearish_divergences = np.empty(len(dataframe['close'])) * np.nan bullish_lines = [np.empty(len(dataframe['close'])) * np.nan] bullish_divergences = np.empty(len(dataframe['close'])) * np.nan for index, row in enumerate(dataframe.itertuples(index=True, name='Pandas')): # Bearish divergence detection bearish_occurence = self.bearish_divergence_finder( dataframe, dataframe[indicator_source], high_iterator, index) if bearish_occurence is not None: (prev_pivot, current_pivot) = bearish_occurence bearish_prev_pivot = dataframe['close'][prev_pivot] bearish_current_pivot = dataframe['close'][current_pivot] bearish_ind_prev_pivot = dataframe[indicator_source][prev_pivot] bearish_ind_current_pivot = dataframe[indicator_source][current_pivot] # Enhanced validation for bearish divergence price_diff = abs(bearish_current_pivot - bearish_prev_pivot) indicator_diff = abs(bearish_ind_current_pivot - bearish_ind_prev_pivot) time_diff = current_pivot - prev_pivot # Only accept divergences with sufficient magnitude and time separation if (price_diff > dataframe['atr'][current_pivot] * 0.5 and indicator_diff > 5 and time_diff >= 5): bearish_divergences[index] = row.close dataframe.loc[index, "total_bearish_divergences"] = row.close dataframe.loc[index, "total_bearish_divergences_count"] += 1 dataframe.loc[index, "total_bearish_divergences_names"] += indicator_source.upper() + '
' # Bullish divergence detection bullish_occurence = self.bullish_divergence_finder( dataframe, dataframe[indicator_source], low_iterator, index) if bullish_occurence is not None: (prev_pivot, current_pivot) = bullish_occurence bullish_prev_pivot = dataframe['close'][prev_pivot] bullish_current_pivot = dataframe['close'][current_pivot] bullish_ind_prev_pivot = dataframe[indicator_source][prev_pivot] bullish_ind_current_pivot = dataframe[indicator_source][current_pivot] # Enhanced validation for bullish divergence price_diff = abs(bullish_current_pivot - bullish_prev_pivot) indicator_diff = abs(bullish_ind_current_pivot - bullish_ind_prev_pivot) time_diff = current_pivot - prev_pivot # Only accept divergences with sufficient magnitude and time separation if (price_diff > dataframe['atr'][current_pivot] * 0.5 and indicator_diff > 5 and time_diff >= 5): bullish_divergences[index] = row.close dataframe.loc[index, "total_bullish_divergences"] = row.close # CORRECT - increment BULLISH counters for bullish divergence: dataframe.loc[index, "total_bullish_divergences_count"] += 1 dataframe.loc[index, "total_bullish_divergences_names"] += indicator_source.upper() + '
' return (bearish_divergences, bearish_lines, bullish_divergences, bullish_lines) def bearish_divergence_finder(self, dataframe, indicator, high_iterator, index): """Enhanced bearish divergence detection""" try: if high_iterator[index] == index: current_pivot = high_iterator[index] occurences = list(dict.fromkeys(high_iterator)) current_index = occurences.index(high_iterator[index]) for i in range(current_index-1, current_index - self.window.value - 1, -1): if i < 0 or i >= len(occurences): continue prev_pivot = occurences[i] if np.isnan(prev_pivot): continue # Enhanced divergence validation price_higher = dataframe['pivot_highs'][current_pivot] > dataframe['pivot_highs'][prev_pivot] indicator_lower = indicator[current_pivot] < indicator[prev_pivot] price_lower = dataframe['pivot_highs'][current_pivot] < dataframe['pivot_highs'][prev_pivot] indicator_higher = indicator[current_pivot] > indicator[prev_pivot] # Check for classic or hidden divergence if (price_higher and indicator_lower) or (price_lower and indicator_higher): # Additional validation: check trend consistency if self.validate_divergence_trend(dataframe, prev_pivot, current_pivot, 'bearish'): return (prev_pivot, current_pivot) except: pass return None def bullish_divergence_finder(self, dataframe, indicator, low_iterator, index): """Enhanced bullish divergence detection""" try: if low_iterator[index] == index: current_pivot = low_iterator[index] occurences = list(dict.fromkeys(low_iterator)) current_index = occurences.index(low_iterator[index]) for i in range(current_index-1, current_index - self.window.value - 1, -1): if i < 0 or i >= len(occurences): continue prev_pivot = occurences[i] if np.isnan(prev_pivot): continue # Enhanced divergence validation price_lower = dataframe['pivot_lows'][current_pivot] < dataframe['pivot_lows'][prev_pivot] indicator_higher = indicator[current_pivot] > indicator[prev_pivot] price_higher = dataframe['pivot_lows'][current_pivot] > dataframe['pivot_lows'][prev_pivot] indicator_lower = indicator[current_pivot] < indicator[prev_pivot] # Check for classic or hidden divergence if (price_lower and indicator_higher) or (price_higher and indicator_lower): # Additional validation: check trend consistency if self.validate_divergence_trend(dataframe, prev_pivot, current_pivot, 'bullish'): return (prev_pivot, current_pivot) except: pass return None def validate_divergence_trend(self, dataframe, prev_pivot, current_pivot, divergence_type): """Validate divergence by checking intermediate trend""" try: # Check if there's a clear trend between pivots mid_point = (prev_pivot + current_pivot) // 2 if divergence_type == 'bearish': # For bearish divergence, expect uptrend in between return dataframe['ema20'][mid_point] > dataframe['ema20'][prev_pivot] else: # For bullish divergence, expect downtrend in between return dataframe['ema20'][mid_point] < dataframe['ema20'][prev_pivot] except: return True # Default to accepting divergence if validation fails @property def protections(self): """Enhanced protection configuration""" prot = [] if self.use_cooldown_protection.value: prot.append({ "method": "CooldownPeriod", "stop_duration_candles": self.cooldown_lookback.value }) if self.use_max_drawdown_protection.value: prot.append({ "method": "MaxDrawdown", "lookback_period_candles": self.max_drawdown_lookback.value, "trade_limit": self.max_drawdown_trade_limit.value, "stop_duration_candles": self.max_drawdown_stop_duration.value, "max_allowed_drawdown": self.max_allowed_drawdown.value }) if self.use_stop_protection.value: prot.append({ "method": "StoplossGuard", "lookback_period_candles": self.stoploss_guard_lookback.value, "trade_limit": self.stoploss_guard_trade_limit.value, "stop_duration_candles": self.stop_duration.value, "only_per_pair": self.stoploss_guard_only_per_pair.value, }) return prot def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # Initialize dataframe['exit_long'] = 0 dataframe['exit_short'] = 0 dataframe['exit_tag'] = '' # === YOUR OTHER EXIT CONDITIONS FIRST === # (Add any other exit conditions you have here) # any_long_exit = (some_other_condition) # any_short_exit = (some_other_condition) # === EXIT ON OPPOSITE SIGNALS (your previous approach) === if 'enter_long' in dataframe.columns and 'enter_short' in dataframe.columns: # Exit longs on short signals reversal_long_exit = (dataframe['enter_short'] == 1) dataframe.loc[reversal_long_exit, 'exit_long'] = 1 dataframe.loc[reversal_long_exit, 'exit_tag'] = 'Reversal_Short_Signal' # Exit shorts on long signals if self.can_short: reversal_short_exit = (dataframe['enter_long'] == 1) dataframe.loc[reversal_short_exit, 'exit_short'] = 1 dataframe.loc[reversal_short_exit, 'exit_tag'] = 'Reversal_Long_Signal' return dataframe def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs): """ Simple and effective custom exit - just take profits! 15m timeframe optimized - triggers exits reliably """ from logging import getLogger logger = getLogger(__name__) # Get entry tag and trade duration entry_tag = getattr(trade, 'enter_tag', '') trade_duration_minutes = (current_time - trade.open_date_utc).total_seconds() / 60 # === GET BASIC MARKET DATA FOR SAFETY EXITS === rsi = 50 momentum_score = 0 try: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) if not dataframe.empty: last_row = dataframe.iloc[-1] if 'rsi' in dataframe.columns: rsi = last_row['rsi'] # Simple momentum calculation if len(dataframe) >= 5: momentum_score = (last_row['close'] - dataframe.iloc[-5]['close']) / dataframe.iloc[-5]['close'] except Exception: pass # === ENHANCED LOG AND EXIT WITH SIGNAL TRACKING === def log_and_exit(reason: str): # UPDATE SIGNAL PERFORMANCE TRACKING if hasattr(trade, 'enter_tag') and trade.enter_tag: try: profit_ratio = trade.calc_profit_ratio(current_rate) self.update_signal_performance(trade.enter_tag, pair, profit_ratio) logger.debug(f"Updated signal performance: {trade.enter_tag} -> {profit_ratio:.4f}") except Exception as e: logger.error(f"Failed to update signal performance: {e}") # LOG EXIT DETAILS logger.info(f"🔤 {pair} EXIT: {reason}") logger.info(f" 🕐 Time Held: {int(trade_duration_minutes)} min") logger.info(f" 💰 Profit: {current_profit * 100:.2f}%") logger.info(f" 📊 RSI: {rsi:.1f}") logger.info(f" 📈 Momentum: {momentum_score * 100:.2f}%") logger.info(f" 🏷️ Entry Tag: {entry_tag}") logger.info(f" 🔄 Exit Trigger: {reason}") return reason # === SIMPLE PROFIT TAKING - NO COMPLEX LOGIC === # === SIMPLE PROFIT TAKING - NO COMPLEX LOGIC === # Immediate exits for big profits if current_profit >= 0.12: # 12% return log_and_exit("profit_12pct") elif current_profit >= 0.10: # 10% return log_and_exit("profit_10pct") elif current_profit >= 0.08: # 8% return log_and_exit("profit_8pct") elif current_profit >= 0.06: # 6% return log_and_exit("profit_6pct") elif current_profit >= 0.05: # 5% return log_and_exit("profit_5pct") elif current_profit >= 0.04: # 4% return log_and_exit("profit_4pct") # Time-based exits - COVERS ALL TRADES UNDER 4% TOO elif current_profit >= 0.035 and trade_duration_minutes >= 15: # 3.5% after 15min return log_and_exit("profit_35pct_15min") elif current_profit >= 0.03 and trade_duration_minutes >= 30: # 3% after 30min return log_and_exit("profit_3pct_30min") elif current_profit >= 0.025 and trade_duration_minutes >= 45: # 2.5% after 45min return log_and_exit("profit_25pct_45min") elif current_profit >= 0.02 and trade_duration_minutes >= 60: # 2% after 1hr return log_and_exit("profit_2pct_1hr") elif current_profit >= 0.015 and trade_duration_minutes >= 90: # 1.5% after 1.5hr return log_and_exit("profit_15pct_90min") elif current_profit >= 0.01 and trade_duration_minutes >= 120: # 1% after 2hr return log_and_exit("profit_1pct_2hr") elif current_profit >= 0.008 and trade_duration_minutes >= 180: # 0.8% after 3hr return log_and_exit("profit_08pct_3hr") elif current_profit >= 0.005 and trade_duration_minutes >= 240: # 0.5% after 4hr return log_and_exit("force_exit_4hr") # === GET BASIC MARKET DATA FOR SAFETY EXITS === rsi = 50 momentum_score = 0 try: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) if not dataframe.empty: last_row = dataframe.iloc[-1] if 'rsi' in dataframe.columns: rsi = last_row['rsi'] # Simple momentum calculation if len(dataframe) >= 5: momentum_score = (last_row['close'] - dataframe.iloc[-5]['close']) / dataframe.iloc[-5]['close'] except Exception: pass # === ENHANCED LOG AND EXIT WITH SIGNAL TRACKING === def log_and_exit(reason: str): # UPDATE SIGNAL PERFORMANCE TRACKING if hasattr(trade, 'enter_tag') and trade.enter_tag: try: profit_ratio = trade.calc_profit_ratio(current_rate) self.update_signal_performance(trade.enter_tag, pair, profit_ratio) logger.debug(f"Updated signal performance: {trade.enter_tag} -> {profit_ratio:.4f}") except Exception as e: logger.error(f"Failed to update signal performance: {e}") # LOG EXIT DETAILS logger.info(f"🔤 {pair} EXIT: {reason}") logger.info(f" 🕐 Time Held: {int(trade_duration_minutes)} min") logger.info(f" 💰 Profit: {current_profit * 100:.2f}%") logger.info(f" 📊 RSI: {rsi:.1f}") logger.info(f" 📈 Momentum: {momentum_score * 100:.2f}%") logger.info(f" 🏷️ Entry Tag: {entry_tag}") logger.info(f" 🔄 Exit Trigger: {reason}") return reason # === SIMPLE RSI EXTREME EXITS === if current_profit >= 0.02: # Only if we have some profit if not trade.is_short and rsi >= 80: # Very overbought return log_and_exit("rsi_extreme_80") elif trade.is_short and rsi <= 20: # Very oversold return log_and_exit("rsi_extreme_20") # === SIMPLE RSI WARNING EXITS === if current_profit >= 0.015: # Only if we have decent profit if not trade.is_short and rsi >= 75: # Overbought return log_and_exit("rsi_overbought_75") elif trade.is_short and rsi <= 25: # Oversold return log_and_exit("rsi_oversold_25") # === EMERGENCY EXITS FOR HUGE PROFITS === if current_profit >= 0.30: # 30% - definitely exit! return log_and_exit("emergency_30pct") elif current_profit >= 0.25: # 25% - definitely exit! return log_and_exit("emergency_25pct") elif current_profit >= 0.20: # 20% - definitely exit! return log_and_exit("emergency_20pct") elif current_profit >= 0.15: # 15% - definitely exit! return log_and_exit("emergency_15pct") # === REVERSAL SIGNAL EXITS === try: if dataframe is not None and not dataframe.empty: last_row = dataframe.iloc[-1] # Exit on opposite signals if we have any profit if current_profit >= 0.005: # 0.5% minimum if not trade.is_short and last_row.get('enter_short', 0) == 1: return log_and_exit("reversal_short_signal") elif trade.is_short and last_row.get('enter_long', 0) == 1: return log_and_exit("reversal_long_signal") except Exception: pass # === WEEKEND SAFETY EXIT === if current_time.weekday() == 4 and current_time.hour >= 20: # Friday evening if current_profit >= 0.01: # 1% minimum return log_and_exit("friday_close") # === FINAL SAFETY - EXIT ANYTHING AFTER 6 HOURS === if trade_duration_minutes >= 360: # 6 hours if current_profit >= 0.003: # Even 0.3% profit return log_and_exit("final_safety_6hr") return None def maybe_optimize_coin(self, pair: str, force_startup: bool = False): """ENHANCED optimization trigger with better permanent optimization""" # ADDED: Komplette neue Methode für Optimierungslogik if not self.optuna_manager: logger.debug(f"🚫 [OPTUNA] OptunaManager not available for {pair}") return # ENHANCED: Force startup optimization or intelligent check if force_startup: logger.info(f"🚀 [OPTUNA] FORCED STARTUP OPTIMIZATION for {pair}") should_optimize = True else: should_optimize = self.optuna_manager.should_optimize(pair) if not should_optimize: return logger.info(f"🔄 [OPTUNA] Checking optimization conditions for {pair}") try: trades_count = self.get_coin_trades_count(pair) optimization_count = self.optuna_manager.optimization_trigger_count.get(pair, 0) logger.info(f"📊 [OPTUNA] {pair} has {trades_count} trades, {optimization_count} optimizations done") # ENHANCED: More flexible trade requirements min_trades_required = max(0, optimization_count * 5) # Require more trades for subsequent optimizations if not force_startup and trades_count < min_trades_required: logger.info(f"⏳ [OPTUNA] Not enough trades for {pair} optimization ({trades_count}/{min_trades_required})") return # Track optimization type if force_startup: opt_type = "STARTUP" elif self.optuna_manager.should_optimize_based_on_performance(pair): opt_type = "PERFORMANCE-TRIGGERED" else: opt_type = "PERIODIC" logger.info(f"✨ [OPTUNA] {opt_type} OPTIMIZATION for {pair} (trades: {trades_count})") objective_func = self.create_objective_function(pair) # ENHANCED: More trials for performance-triggered optimizations n_trials = 15 if opt_type == "PERFORMANCE-TRIGGERED" else 10 # Start optimization self.optuna_manager.optimize_coin(pair, objective_func, n_trials=n_trials) # Track optimization count self.optuna_manager.optimization_trigger_count[pair] = optimization_count + 1 logger.info(f"✅ [OPTUNA] Completed {opt_type} optimization for {pair}") except Exception as e: logger.error(f"❌ [OPTUNA] Optimization failed for {pair}: {e}") import traceback logger.debug(f"🔍 [OPTUNA] Full traceback: {traceback.format_exc()}") def get_coin_trades_count(self, pair: str) -> int: """Get number of trades for specific coin""" # ADDED: Hilfsmethode für Trade-Anzahl pro Coin try: from freqtrade.persistence import Trade trades = Trade.get_trades_proxy(pair=pair) count = len(trades) if trades else 0 logger.debug(f"📊 [OPTUNA] Trade count for {pair}: {count}") return count except Exception as e: logger.error(f"❌ [OPTUNA] Failed to get trade count for {pair}: {e}") return 0 def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool: """Track trade exits and update performance - works in backtesting""" try: profit_ratio = trade.calc_profit_ratio(rate) # Update Optuna performance if available if self.optuna_manager: self.optuna_manager.update_performance(pair, profit_ratio) logger.debug(f"📈 [OPTUNA] Updated performance for {pair}: {profit_ratio:.4f}") if pair not in self.coin_performance: self.coin_performance[pair] = 0.0 self.coin_performance[pair] += profit_ratio # ADD THIS: Update signal performance tracking for backtesting enter_tag = getattr(trade, 'enter_tag', None) if enter_tag: self.update_signal_performance(enter_tag, pair, profit_ratio) # Debug output result_type = "WIN" if profit_ratio > 0 else "LOSS" print(f"📊 Signal Performance Tracked: {pair} {enter_tag} - {result_type}") print(f" Profit: {profit_ratio:.4f} ({profit_ratio*100:.2f}%)") print(f" Exit reason: {exit_reason}") # Log significant performance updates if abs(profit_ratio) > 0.02: # More than 2% gain/loss logger.info(f"💰 [OPTUNA] Significant trade result for {pair}: {profit_ratio:.2%} (cumulative: {self.coin_performance[pair]:.2%})") except Exception as e: logger.error(f"⛔ [OPTUNA] Failed to update performance for {pair}: {e}") return True def on_trade_close(self, trade: Trade, **kwargs) -> None: """Called when a trade is closed - captures ALL exits including stoploss""" try: entry_tag = getattr(trade, 'enter_tag', '') if entry_tag: # Only track if we have an entry tag pair = trade.pair profit_ratio = trade.calc_profit_ratio() # Update signal performance for ALL exits (wins and losses) self.update_signal_performance(entry_tag, pair, profit_ratio) # Log the trade result result_type = "WIN" if profit_ratio > 0 else "LOSS" exit_reason = getattr(trade, 'exit_reason', 'unknown') print(f"Trade closed: {pair} {entry_tag} - {result_type}") print(f" Profit: {profit_ratio:.4f} ({profit_ratio*100:.2f}%)") print(f" Exit reason: {exit_reason}") except Exception as e: print(f"Error in on_trade_close: {e}") def choppiness_index(high, low, close, window=14): """Calculate Choppiness Index""" natr = pd.Series(ta.NATR(high, low, close, window=window)) high_max = high.rolling(window=window).max() low_min = low.rolling(window=window).min() choppiness = 100 * np.log10((natr.rolling(window=window).sum()) / (high_max - low_min)) / np.log10(window) return choppiness def resample(indicator): """Resample function for compatibility""" return indicator def two_bands_check_long(dataframe): """Allow long when price is near/at lower band (oversold area)""" return ( (dataframe['low'] <= dataframe['kc_lowerband']) | (dataframe['close'] <= dataframe['kc_lowerband']) ) def two_bands_check_short(dataframe): """Allow short when price is near/at upper band (overbought area)""" return ( (dataframe['high'] >= dataframe['kc_upperband']) | (dataframe['close'] >= dataframe['kc_upperband']) ) def green_candle(dataframe): """Check for green candle""" return dataframe[resample('open')] < dataframe[resample('close')] def red_candle(dataframe): """Check for red candle""" return dataframe[resample('open')] > dataframe[resample('close')] def pivot_points(dataframe: DataFrame, window: int = 5, pivot_source=None) -> DataFrame: """Enhanced pivot point detection""" from enum import Enum class PivotSource(Enum): HighLow = 0 Close = 1 if pivot_source is None: pivot_source = PivotSource.Close high_source = 'close' if pivot_source == PivotSource.Close else 'high' low_source = 'close' if pivot_source == PivotSource.Close else 'low' pivot_points_lows = np.empty(len(dataframe['close'])) * np.nan pivot_points_highs = np.empty(len(dataframe['close'])) * np.nan last_values = deque() # Find pivot points with enhanced validation for index, row in enumerate(dataframe.itertuples(index=True, name='Pandas')): last_values.append(row) if len(last_values) >= window * 2 + 1: current_value = last_values[window] is_greater = True is_less = True for window_index in range(0, window): left = last_values[window_index] right = last_values[2 * window - window_index] local_is_greater, local_is_less = check_if_pivot_is_greater_or_less( current_value, high_source, low_source, left, right) is_greater &= local_is_greater is_less &= local_is_less # Additional validation: ensure pivot is significant if is_greater: current_high = getattr(current_value, high_source) # Check if high is significant enough (above ATR threshold) if hasattr(current_value, 'atr') and current_high > 0: pivot_points_highs[index - window] = current_high if is_less: current_low = getattr(current_value, low_source) # Check if low is significant enough if hasattr(current_value, 'atr') and current_low > 0: pivot_points_lows[index - window] = current_low last_values.popleft() return pd.DataFrame(index=dataframe.index, data={ 'pivot_lows': pivot_points_lows, 'pivot_highs': pivot_points_highs }) def check_if_pivot_is_greater_or_less(current_value, high_source: str, low_source: str, left, right) -> Tuple[bool, bool]: """Helper function for pivot point validation""" is_greater = True is_less = True if (getattr(current_value, high_source) <= getattr(left, high_source) or getattr(current_value, high_source) <= getattr(right, high_source)): is_greater = False if (getattr(current_value, low_source) >= getattr(left, low_source) or getattr(current_value, low_source) >= getattr(right, low_source)): is_less = False return (is_greater, is_less) def emaKeltner(dataframe): """Calculate EMA-based Keltner Channels""" keltner = {} atr = qtpylib.atr(dataframe, window=10) ema20 = ta.EMA(dataframe, timeperiod=20) keltner['upper'] = ema20 + atr keltner['mid'] = ema20 keltner['lower'] = ema20 - atr return keltner def chaikin_money_flow(dataframe, n=20, fillna=False) -> Series: """Calculate Chaikin Money Flow indicator""" df = dataframe.copy() mfv = ((df['close'] - df['low']) - (df['high'] - df['close'])) / (df['high'] - df['low']) mfv = mfv.fillna(0.0) mfv *= df['volume'] cmf = (mfv.rolling(n, min_periods=0).sum() / df['volume'].rolling(n, min_periods=0).sum()) if fillna: cmf = cmf.replace([np.inf, -np.inf], np.nan).fillna(0) return Series(cmf, name='cmf')