Enhanced strategy on the 15-minute timeframe with Market Correlation Filters.
Timeframe
15m
Direction
Long & Short
Stoploss
-15.0%
Trailing Stop
No
ROI
0m: 7.0%, 5m: 5.5%, 10m: 4.0%, 20m: 3.0%
Interface Version
N/A
Startup Candles
N/A
Indicators
6
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
import logging
import numpy as np
import pandas as pd
import warnings
from datetime import datetime
from typing import Optional, Dict, List, Tuple
import talib.abstract as ta
import pandas_ta as pta # pandas_ta is imported but not explicitly used in the provided code.
# If it's for future use or part of an older version, that's okay.
# Otherwise, it can be removed if not needed.
from scipy.signal import argrelextrema
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter, BooleanParameter
from freqtrade.persistence import Trade
warnings.simplefilter(action="ignore", category=pd.errors.PerformanceWarning)
logger = logging.getLogger(__name__)
# Define Murrey Math level names for consistency
MML_LEVEL_NAMES = [
"[-3/8]P", "[-2/8]P", "[-1/8]P", "[0/8]P", "[1/8]P",
"[2/8]P", "[3/8]P", "[4/8]P", "[5/8]P", "[6/8]P",
"[7/8]P", "[8/8]P", "[+1/8]P", "[+2/8]P", "[+3/8]P"
]
def calculate_minima_maxima(df, window):
if df is None or df.empty:
return np.zeros(0), np.zeros(0)
minima = np.zeros(len(df))
maxima = np.zeros(len(df))
for i in range(window, len(df)):
window_data = df['ha_close'].iloc[i - window:i + 1]
if df['ha_close'].iloc[i] == window_data.min() and (window_data == df['ha_close'].iloc[i]).sum() == 1:
minima[i] = -window
if df['ha_close'].iloc[i] == window_data.max() and (window_data == df['ha_close'].iloc[i]).sum() == 1:
maxima[i] = window
return minima, maxima
def calculate_exit_signals(dataframe: pd.DataFrame) -> pd.DataFrame:
"""Calculate advanced exit signals based on market deterioration"""
# === MOMENTUM DETERIORATION ===
dataframe['momentum_deteriorating'] = (
(dataframe['momentum_quality'] < dataframe['momentum_quality'].shift(1)) &
(dataframe['momentum_acceleration'] < 0) &
(dataframe['price_momentum'] < dataframe['price_momentum'].shift(1))
).astype(int)
# === VOLUME DETERIORATION ===
dataframe['volume_deteriorating'] = (
(dataframe['volume_strength'] < 0.8) &
(dataframe['selling_pressure'] > dataframe['buying_pressure']) &
(dataframe['volume_pressure'] < 0)
).astype(int)
# === STRUCTURE DETERIORATION ===
dataframe['structure_deteriorating'] = (
(dataframe['structure_score'] < -1) &
(dataframe['bearish_structure'] > dataframe['bullish_structure']) &
(dataframe['structure_break_down'] == 1)
).astype(int)
# === CONFLUENCE BREAKDOWN ===
dataframe['confluence_breakdown'] = (
(dataframe['confluence_score'] < 2) &
(dataframe['near_resistance'] == 1) &
(dataframe['volume_spike'] == 0)
).astype(int)
# === TREND WEAKNESS ===
dataframe['trend_weakening'] = (
(dataframe['trend_strength'] < 0) &
(dataframe['close'] < dataframe['ema50']) &
(dataframe['strong_downtrend'] == 1)
).astype(int)
# === ULTIMATE EXIT SCORE ===
dataframe['exit_pressure'] = (
dataframe['momentum_deteriorating'] * 2 +
dataframe['volume_deteriorating'] * 2 +
dataframe['structure_deteriorating'] * 2 +
dataframe['confluence_breakdown'] * 1 +
dataframe['trend_weakening'] * 1
)
# === RSI OVERBOUGHT WITH DIVERGENCE ===
dataframe['rsi_exit_signal'] = (
(dataframe['rsi'] > 75) &
(
(dataframe['rsi_divergence_bear'] == 1) |
(dataframe['rsi'] > dataframe['rsi'].shift(1)) &
(dataframe['close'] < dataframe['close'].shift(1))
)
).astype(int)
# === PROFIT TAKING LEVELS ===
mml_resistance_levels = ['[6/8]P', '[8/8]P']
dataframe['near_resistance_level'] = 0
for level in mml_resistance_levels:
if level in dataframe.columns:
near_level = (
(dataframe['close'] >= dataframe[level] * 0.99) &
(dataframe['close'] <= dataframe[level] * 1.02)
).astype(int)
dataframe['near_resistance_level'] += near_level
# === VOLATILITY SPIKE EXIT ===
dataframe['volatility_spike'] = (
dataframe['atr'] > dataframe['atr'].rolling(20).mean() * 1.5
).astype(int)
# === EXHAUSTION SIGNALS ===
dataframe['bullish_exhaustion'] = (
(dataframe['consecutive_green'] >= 4) &
(dataframe['rsi'] > 70) &
(dataframe['volume'] < dataframe['avg_volume'] * 0.8) &
(dataframe['momentum_acceleration'] < 0)
).astype(int)
return dataframe
def calculate_dynamic_profit_targets(dataframe: pd.DataFrame, entry_type_col: str = 'entry_type') -> pd.DataFrame:
"""Calculate dynamic profit targets based on entry quality and market conditions"""
# Base profit targets based on ATR
dataframe['base_profit_target'] = dataframe['atr'] * 2
# Adjust based on entry type
dataframe['profit_multiplier'] = 1.0
if entry_type_col in dataframe.columns:
dataframe.loc[dataframe[entry_type_col] == 3, 'profit_multiplier'] = 2.0 # High quality
dataframe.loc[dataframe[entry_type_col] == 2, 'profit_multiplier'] = 1.5 # Medium quality
dataframe.loc[dataframe[entry_type_col] == 1, 'profit_multiplier'] = 1.2 # Backup
dataframe.loc[dataframe[entry_type_col] == 4, 'profit_multiplier'] = 2.5 # Breakout
dataframe.loc[dataframe[entry_type_col] == 5, 'profit_multiplier'] = 1.8 # Reversal
# Final profit target
dataframe['dynamic_profit_target'] = dataframe['base_profit_target'] * dataframe['profit_multiplier']
return dataframe
def calculate_advanced_stop_loss(dataframe: pd.DataFrame) -> pd.DataFrame:
dataframe['base_stop_loss'] = dataframe['atr'] * 1.5
if 'minima_sort_threshold' in dataframe.columns:
dataframe['support_stop_loss'] = dataframe['close'] - dataframe['minima_sort_threshold']
dataframe['support_stop_loss'] = dataframe['support_stop_loss'].clip(
dataframe['base_stop_loss'] * 0.5,
dataframe['base_stop_loss'] * 1.5 # Reduced from 2.0
)
dataframe['final_stop_loss'] = np.minimum(
dataframe['base_stop_loss'],
dataframe['support_stop_loss']
).clip(-0.15, -0.01) # Hard cap at -15%
else:
dataframe['final_stop_loss'] = dataframe['base_stop_loss'].clip(-0.15, -0.01)
return dataframe
def calculate_confluence_score(dataframe: pd.DataFrame) -> pd.DataFrame:
"""Multi-factor confluence analysis - much better than BTC correlation"""
# Support/Resistance Confluence
dataframe['near_support'] = (
(dataframe['close'] <= dataframe['minima_sort_threshold'] * 1.02) &
(dataframe['close'] >= dataframe['minima_sort_threshold'] * 0.98)
).astype(int)
dataframe['near_resistance'] = (
(dataframe['close'] <= dataframe['maxima_sort_threshold'] * 1.02) &
(dataframe['close'] >= dataframe['maxima_sort_threshold'] * 0.98)
).astype(int)
# MML Level Confluence
mml_levels = ['[0/8]P', '[2/8]P', '[4/8]P', '[6/8]P', '[8/8]P']
dataframe['near_mml'] = 0
for level in mml_levels:
if level in dataframe.columns:
near_level = (
(dataframe['close'] <= dataframe[level] * 1.015) &
(dataframe['close'] >= dataframe[level] * 0.985)
).astype(int)
dataframe['near_mml'] += near_level
# Volume Confluence
dataframe['volume_spike'] = (
dataframe['volume'] > dataframe['avg_volume'] * 1.5
).astype(int)
# RSI Confluence Zones
dataframe['rsi_oversold'] = (dataframe['rsi'] < 30).astype(int)
dataframe['rsi_overbought'] = (dataframe['rsi'] > 70).astype(int)
dataframe['rsi_neutral'] = (
(dataframe['rsi'] >= 40) & (dataframe['rsi'] <= 60)
).astype(int)
# EMA Confluence
dataframe['above_ema'] = (dataframe['close'] > dataframe['ema50']).astype(int)
# CONFLUENCE SCORE (0-6)
dataframe['confluence_score'] = (
dataframe['near_support'] +
dataframe['near_mml'].clip(0, 2) + # Max 2 points for MML
dataframe['volume_spike'] +
dataframe['rsi_oversold'] +
dataframe['above_ema'] +
(dataframe['trend_strength'] > 0.01).astype(int) # Positive trend
)
return dataframe
def calculate_smart_volume(dataframe: pd.DataFrame) -> pd.DataFrame:
"""Advanced volume analysis - beats any external correlation"""
# Volume-Price Trend (VPT)
price_change_pct = (dataframe['close'] - dataframe['close'].shift(1)) / dataframe['close'].shift(1)
dataframe['vpt'] = (dataframe['volume'] * price_change_pct).fillna(0).cumsum()
# Volume moving averages
dataframe['volume_sma20'] = dataframe['volume'].rolling(20).mean()
dataframe['volume_sma50'] = dataframe['volume'].rolling(50).mean()
# Volume strength
dataframe['volume_strength'] = dataframe['volume'] / dataframe['volume_sma20']
# Smart money indicators
dataframe['accumulation'] = (
(dataframe['close'] > dataframe['open']) & # Green candle
(dataframe['volume'] > dataframe['volume_sma20'] * 1.2) & # High volume
(dataframe['close'] > (dataframe['high'] + dataframe['low']) / 2) # Close in upper half
).astype(int)
dataframe['distribution'] = (
(dataframe['close'] < dataframe['open']) & # Red candle
(dataframe['volume'] > dataframe['volume_sma20'] * 1.2) & # High volume
(dataframe['close'] < (dataframe['high'] + dataframe['low']) / 2) # Close in lower half
).astype(int)
# Buying/Selling pressure
dataframe['buying_pressure'] = dataframe['accumulation'].rolling(5).sum()
dataframe['selling_pressure'] = dataframe['distribution'].rolling(5).sum()
# Net volume pressure
dataframe['volume_pressure'] = dataframe['buying_pressure'] - dataframe['selling_pressure']
# Volume trend
dataframe['volume_trend'] = (
dataframe['volume_sma20'] > dataframe['volume_sma50']
).astype(int)
# Money flow
typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
money_flow = typical_price * dataframe['volume']
positive_flow = money_flow.where(typical_price > typical_price.shift(1), 0)
negative_flow = money_flow.where(typical_price < typical_price.shift(1), 0)
positive_flow_sum = positive_flow.rolling(14).sum()
negative_flow_sum = negative_flow.rolling(14).sum()
dataframe['money_flow_ratio'] = positive_flow_sum / (negative_flow_sum + 1e-10)
dataframe['money_flow_index'] = 100 - (100 / (1 + dataframe['money_flow_ratio']))
return dataframe
def calculate_advanced_momentum(dataframe: pd.DataFrame) -> pd.DataFrame:
"""Multi-timeframe momentum system - superior to BTC correlation"""
# Multi-timeframe momentum
dataframe['momentum_3'] = dataframe['close'].pct_change(6)
dataframe['momentum_7'] = dataframe['close'].pct_change(14)
dataframe['momentum_14'] = dataframe['close'].pct_change(28)
dataframe['momentum_21'] = dataframe['close'].pct_change(21)
# Momentum acceleration
dataframe['momentum_acceleration'] = (
dataframe['momentum_3'] - dataframe['momentum_3'].shift(3)
)
# Momentum consistency
dataframe['momentum_consistency'] = (
(dataframe['momentum_3'] > 0).astype(int) +
(dataframe['momentum_7'] > 0).astype(int) +
(dataframe['momentum_14'] > 0).astype(int)
)
# Momentum divergence with volume
dataframe['price_momentum_rank'] = dataframe['momentum_7'].rolling(20).rank(pct=True)
dataframe['volume_momentum_rank'] = dataframe['volume_strength'].rolling(20).rank(pct=True)
dataframe['momentum_divergence'] = (
dataframe['price_momentum_rank'] - dataframe['volume_momentum_rank']
).abs()
# Momentum strength
dataframe['momentum_strength'] = (
dataframe['momentum_3'].abs() +
dataframe['momentum_7'].abs() +
dataframe['momentum_14'].abs()
) / 3
# Momentum quality score (0-5)
dataframe['momentum_quality'] = (
(dataframe['momentum_3'] > 0).astype(int) +
(dataframe['momentum_7'] > 0).astype(int) +
(dataframe['momentum_acceleration'] > 0).astype(int) +
(dataframe['volume_strength'] > 1.1).astype(int) +
(dataframe['momentum_divergence'] < 0.3).astype(int)
)
# Rate of Change
dataframe['roc_5'] = dataframe['close'].pct_change(5) * 100
dataframe['roc_10'] = dataframe['close'].pct_change(10) * 100
dataframe['roc_20'] = dataframe['close'].pct_change(20) * 100
# Momentum oscillator
dataframe['momentum_oscillator'] = (
dataframe['roc_5'] + dataframe['roc_10'] + dataframe['roc_20']
) / 3
return dataframe
def calculate_market_structure(dataframe: pd.DataFrame) -> pd.DataFrame:
"""Market structure analysis - intrinsic trend recognition"""
# Higher highs, higher lows detection
dataframe['higher_high'] = (
(dataframe['high'] > dataframe['high'].shift(1)) &
(dataframe['high'].shift(1) > dataframe['high'].shift(2))
).astype(int)
dataframe['higher_low'] = (
(dataframe['low'] > dataframe['low'].shift(1)) &
(dataframe['low'].shift(1) > dataframe['low'].shift(2))
).astype(int)
dataframe['lower_high'] = (
(dataframe['high'] < dataframe['high'].shift(1)) &
(dataframe['high'].shift(1) < dataframe['high'].shift(2))
).astype(int)
dataframe['lower_low'] = (
(dataframe['low'] < dataframe['low'].shift(1)) &
(dataframe['low'].shift(1) < dataframe['low'].shift(2))
).astype(int)
# Market structure scores
dataframe['bullish_structure'] = (
dataframe['higher_high'].rolling(5).sum() +
dataframe['higher_low'].rolling(5).sum()
)
dataframe['bearish_structure'] = (
dataframe['lower_high'].rolling(5).sum() +
dataframe['lower_low'].rolling(5).sum()
)
dataframe['structure_score'] = (
dataframe['bullish_structure'] - dataframe['bearish_structure']
)
# Swing highs and lows
dataframe['swing_high'] = (
(dataframe['high'] > dataframe['high'].shift(1)) &
(dataframe['high'] > dataframe['high'].shift(-1))
).astype(int)
dataframe['swing_low'] = (
(dataframe['low'] < dataframe['low'].shift(1)) &
(dataframe['low'] < dataframe['low'].shift(-1))
).astype(int)
# Market structure breaks
swing_highs = dataframe['high'].where(dataframe['swing_high'] == 1)
swing_lows = dataframe['low'].where(dataframe['swing_low'] == 1)
# Structure break detection
dataframe['structure_break_up'] = (
dataframe['close'] > swing_highs.ffill()
).astype(int)
dataframe['structure_break_down'] = (
dataframe['close'] < swing_lows.ffill()
).astype(int)
# Trend strength based on structure
dataframe['structure_trend_strength'] = (
dataframe['structure_score'] / 10 # Normalize
).clip(-1, 1)
# Support and resistance strength
dataframe['support_strength'] = dataframe['swing_low'].rolling(20).sum()
dataframe['resistance_strength'] = dataframe['swing_high'].rolling(20).sum()
return dataframe
def calculate_advanced_entry_signals(dataframe: pd.DataFrame) -> pd.DataFrame:
"""Advanced entry signal generation"""
# Multi-factor signal strength
dataframe['signal_strength'] = 0
# Confluence signals
dataframe['confluence_signal'] = (dataframe['confluence_score'] >= 3).astype(int)
dataframe['signal_strength'] += dataframe['confluence_signal'] * 2
# Volume signals
dataframe['volume_signal'] = (
(dataframe['volume_pressure'] >= 2) &
(dataframe['volume_strength'] > 1.2)
).astype(int)
dataframe['signal_strength'] += dataframe['volume_signal'] * 2
# Momentum signals
dataframe['momentum_signal'] = (
(dataframe['momentum_quality'] >= 3) &
(dataframe['momentum_acceleration'] > 0)
).astype(int)
dataframe['signal_strength'] += dataframe['momentum_signal'] * 2
# Structure signals
dataframe['structure_signal'] = (
(dataframe['structure_score'] > 0) &
(dataframe['structure_break_up'] == 1)
).astype(int)
dataframe['signal_strength'] += dataframe['structure_signal'] * 1
# RSI position signal
dataframe['rsi_signal'] = (
(dataframe['rsi'] > 30) & (dataframe['rsi'] < 70)
).astype(int)
dataframe['signal_strength'] += dataframe['rsi_signal'] * 1
# Trend alignment signal
dataframe['trend_signal'] = (
(dataframe['close'] > dataframe['ema50']) &
(dataframe['trend_strength'] > 0)
).astype(int)
dataframe['signal_strength'] += dataframe['trend_signal'] * 1
# Money flow signal
dataframe['money_flow_signal'] = (
dataframe['money_flow_index'] > 50
).astype(int)
dataframe['signal_strength'] += dataframe['money_flow_signal'] * 1
return dataframe
class AlexNexusForgeV7(IStrategy):
"""
Enhanced strategy on the 15-minute timeframe with Market Correlation Filters.
Key improvements:
- Removed lookahead Bias
- TRAILING STOP and TRAILING BLOCKED
- Adjustments of Signals medium_qulity_short
- Dynamic stoploss based on ATR.
- Dynamic leverage calculation.
- Murrey Math level calculation (rolling window for performance).
- Enhanced DCA (Average Price) logic.
- Translated to English and code structured for clarity.
- Parameterization of internal constants for optimization.
- Changed Exit Signals for Opposite.
- Change SL to -0.15
- Changed stake amout for renentry
- FIXED: Prevents opening opposite position when trade is active
- FIXED: Trailing stop properly disabled
- NEW: Market correlation filters for better entry timing
"""
# General strategy parameters
timeframe = "15m"
startup_candle_count: int = 100
stoploss = -0.15
#trailing_stop = False
#trailing_stop_positive = None
#trailing_stop_positive_offset = None
#trailing_only_offset_is_reached = None
trailing_stop = True
trailing_stop_positive = 0.005 # Trail at 0.5% below peak profit
trailing_stop_positive_offset = 0.03 # Start trailing only at 3% profit
trailing_only_offset_is_reached = True # Ensure trailing only starts after offset is reached
use_custom_stoploss = False # Disable custom logic
stoploss_on_exchange = True # Let exchange handle it
stoploss_on_exchange_interval = 60 # Check every 60 seconds
position_adjustment_enable = True
can_short = True
use_exit_signal = True
ignore_roi_if_entry_signal = True
max_stake_per_trade = 5.0
max_portfolio_percentage_per_trade = 0.03
max_entry_position_adjustment = 1
process_only_new_candles = True
max_dca_orders = 3
max_total_stake_per_pair = 10
max_single_dca_amount = 5
use_custom_exits_advanced = True
use_emergency_exits = True
# 🚨 REGIME CHANGE DETECTION PARAMETERS (NEU)
regime_change_enabled = BooleanParameter(default=True, space="sell", optimize=True, load=True)
regime_change_sensitivity = DecimalParameter(0.3, 0.8, default=0.5, decimals=2, space="sell", optimize=True, load=True)
# Flash Move Detection
flash_move_enabled = BooleanParameter(default=True, space="sell", optimize=True, load=True)
flash_move_threshold = DecimalParameter(0.03, 0.08, default=0.05, decimals=3, space="sell", optimize=True, load=True)
flash_move_candles = IntParameter(3, 10, default=5, space="sell", optimize=True, load=True)
# Volume Spike Detection
volume_spike_enabled = BooleanParameter(default=True, space="sell", optimize=True, load=True)
volume_spike_multiplier = DecimalParameter(2.0, 5.0, default=3.0, decimals=1, space="sell", optimize=True, load=True)
# Emergency Exit Protection
emergency_exit_enabled = BooleanParameter(default=True, space="sell", optimize=True, load=True)
emergency_exit_profit_threshold = DecimalParameter(0.005, 0.03, default=0.015, decimals=3, space="sell", optimize=True, load=True)
# Market Sentiment Protection
sentiment_protection_enabled = BooleanParameter(default=True, space="sell", optimize=True, load=True)
sentiment_shift_threshold = DecimalParameter(0.2, 0.4, default=0.3, decimals=2, space="sell", optimize=True, load=True)
# 🔧ATR STOPLOSS PARAMETERS (Anpassbar machen)
atr_stoploss_multiplier = DecimalParameter(0.8, 2.0, default=1.0, decimals=1, space="sell", optimize=True, load=True)
atr_stoploss_minimum = DecimalParameter(-0.25, -0.10, default=-0.12, decimals=2, space="sell", optimize=True, load=True)
atr_stoploss_maximum = DecimalParameter(-0.30, -0.15, default=-0.18, decimals=2, space="sell", optimize=True, load=True)
atr_stoploss_ceiling = DecimalParameter(-0.10, -0.06, default=-0.06, decimals=2, space="sell", optimize=True, load=True)
# DCA parameters
initial_safety_order_trigger = DecimalParameter(
low=-0.02, high=-0.01, default=-0.018, decimals=3, space="buy", optimize=True, load=True
)
max_safety_orders = IntParameter(1, 3, default=1, space="buy", optimize=True, load=True)
safety_order_step_scale = DecimalParameter(
low=1.05, high=1.5, default=1.25, decimals=2, space="buy", optimize=True, load=True
)
safety_order_volume_scale = DecimalParameter(
low=1.1, high=2.0, default=1.4, decimals=1, space="buy", optimize=True, load=True
)
h2 = IntParameter(20, 60, default=40, space="buy", optimize=True, load=True)
h1 = IntParameter(10, 40, default=20, space="buy", optimize=True, load=True)
h0 = IntParameter(5, 20, default=10, space="buy", optimize=True, load=True)
cp = IntParameter(5, 20, default=10, space="buy", optimize=True, load=True)
# Entry parameters
increment_for_unique_price = DecimalParameter(
low=1.0005, high=1.002, default=1.001, decimals=4, space="buy", optimize=True, load=True
)
last_entry_price: Optional[float] = None
# Protection parameters
cooldown_lookback = IntParameter(2, 48, default=1, space="protection", optimize=True)
stop_duration = IntParameter(12, 200, default=4, space="protection", optimize=True)
use_stop_protection = BooleanParameter(default=True, space="protection", optimize=True)
# Murrey Math level parameters
mml_const1 = DecimalParameter(1.0, 1.1, default=1.0699, decimals=4, space="buy", optimize=True, load=True)
mml_const2 = DecimalParameter(0.99, 1.0, default=0.99875, decimals=5, space="buy", optimize=True, load=True)
indicator_mml_window = IntParameter(32, 128, default=64, space="buy", optimize=True, load=True)
# Dynamic Stoploss parameters
# Add these parameters
stoploss_atr_multiplier = DecimalParameter(1.0, 3.0, default=1.5, space="sell", optimize=True)
stoploss_max_reasonable = DecimalParameter(-0.30, -0.15, default=-0.20, space="sell", optimize=True)
# === Hyperopt Parameters ===
dominance_threshold = IntParameter(1, 10, default=3, space="buy", optimize=True)
tightness_factor = DecimalParameter(0.5, 2.0, default=1.0, space="buy", optimize=True)
long_rsi_threshold = IntParameter(50, 65, default=50, space="buy", optimize=True)
short_rsi_threshold = IntParameter(30, 45, default=35, space="sell", optimize=True)
# Dynamic Leverage parameters
leverage_window_size = IntParameter(20, 100, default=50, space="buy", optimize=True, load=True)
leverage_base = DecimalParameter(5.0, 20.0, default=5.0, decimals=1, space="buy", optimize=True, load=True)
leverage_rsi_low = DecimalParameter(20.0, 40.0, default=30.0, decimals=1, space="buy", optimize=True, load=True)
leverage_rsi_high = DecimalParameter(60.0, 80.0, default=70.0, decimals=1, space="buy", optimize=True, load=True)
leverage_long_increase_factor = DecimalParameter(1.1, 2.0, default=1.5, decimals=1, space="buy", optimize=True,
load=True)
leverage_long_decrease_factor = DecimalParameter(0.3, 0.9, default=0.5, decimals=1, space="buy", optimize=True,
load=True)
leverage_volatility_decrease_factor = DecimalParameter(0.5, 0.95, default=0.8, decimals=2, space="buy",
optimize=True, load=True)
leverage_atr_threshold_pct = DecimalParameter(0.01, 0.05, default=0.03, decimals=3, space="buy", optimize=True,
load=True)
# Indicator parameters
indicator_extrema_order = IntParameter(3, 15, default=8, space="buy", optimize=True, load=True) # War 5
indicator_mml_window = IntParameter(50, 200, default=50, space="buy", optimize=True, load=True) # War 50
indicator_rolling_window_threshold = IntParameter(20, 100, default=50, space="buy", optimize=True, load=True) # War 20
indicator_rolling_check_window = IntParameter(5, 20, default=10, space="buy", optimize=True, load=True) # War 5
# Market breadth parameters
market_breadth_enabled = BooleanParameter(default=True, space="buy", optimize=True)
market_breadth_threshold = DecimalParameter(0.3, 0.6, default=0.45, space="buy", optimize=True)
# Total market cap parameters
total_mcap_filter_enabled = BooleanParameter(default=True, space="buy", optimize=True)
total_mcap_ma_period = IntParameter(20, 100, default=50, space="buy", optimize=True)
# Market regime parameters
regime_filter_enabled = BooleanParameter(default=True, space="buy", optimize=True)
regime_lookback_period = IntParameter(24, 168, default=48, space="buy", optimize=True) # hours
# Fear & Greed parameters
fear_greed_enabled = BooleanParameter(default=False, space="buy", optimize=True) # Optional
fear_greed_extreme_threshold = IntParameter(20, 30, default=25, space="buy", optimize=True)
fear_greed_greed_threshold = IntParameter(70, 80, default=75, space="buy", optimize=True)
# Momentum
avoid_strong_trends = BooleanParameter(default=True, space="buy", optimize=True)
trend_strength_threshold = DecimalParameter(0.01, 0.05, default=0.02, space="buy", optimize=True)
momentum_confirmation_candles = IntParameter(1, 5, default=2, space="buy", optimize=True)
# Dynamic exit based on entry quality
dynamic_exit_enabled = BooleanParameter(default=True, space="sell", optimize=False, load=True)
exit_on_confluence_loss = BooleanParameter(default=True, space="sell", optimize=False, load=True)
exit_on_structure_break = BooleanParameter(default=True, space="sell", optimize=False, load=True)
# Profit target multipliers based on entry type
high_quality_profit_multiplier = DecimalParameter(1.2, 3.0, default=2.0, space="sell", optimize=True, load=True)
medium_quality_profit_multiplier = DecimalParameter(1.0, 2.5, default=1.5, space="sell", optimize=True, load=True)
backup_profit_multiplier = DecimalParameter(0.8, 2.0, default=1.2, space="sell", optimize=True, load=True)
# Advanced exit thresholds
volume_decline_exit_threshold = DecimalParameter(0.3, 0.8, default=0.5, space="sell", optimize=True, load=True)
momentum_decline_exit_threshold = IntParameter(1, 4, default=2, space="sell", optimize=True, load=True)
structure_deterioration_threshold = DecimalParameter(-3.0, 0.0, default=-1.5, space="sell", optimize=True, load=True)
# RSI exit levels
rsi_overbought_exit = IntParameter(70, 85, default=75, space="sell", optimize=True, load=True)
rsi_divergence_exit_enabled = BooleanParameter(default=True, space="sell", optimize=False, load=True)
# Trailing stop improvements
use_advanced_trailing = BooleanParameter(default=False, space="sell", optimize=False, load=True)
trailing_stop_positive_offset_high_quality = DecimalParameter(0.02, 0.08, default=0.04, space="sell", optimize=True, load=True)
trailing_stop_positive_offset_medium_quality = DecimalParameter(0.015, 0.06, default=0.03, space="sell", optimize=True, load=True)
# === NEUE ADVANCED PARAMETERS ===
# Confluence Analysis
confluence_enabled = BooleanParameter(default=True, space="buy", optimize=False, load=True)
confluence_threshold = DecimalParameter(2.0, 4.0, default=2.5, space="buy", optimize=True, load=True) # War 3.0
# Volume Analysis
volume_analysis_enabled = BooleanParameter(default=True, space="buy", optimize=False, load=True)
volume_strength_threshold = DecimalParameter(1.1, 2.0, default=1.3, space="buy", optimize=True, load=True)
volume_pressure_threshold = IntParameter(1, 3, default=1, space="buy", optimize=True, load=True) # War 2
# Momentum Analysis
momentum_analysis_enabled = BooleanParameter(default=True, space="buy", optimize=False, load=True)
momentum_quality_threshold = IntParameter(2, 4, default=2, space="buy", optimize=True, load=True) # War 3
# Market Structure Analysis
structure_analysis_enabled = BooleanParameter(default=True, space="buy", optimize=False, load=True)
structure_score_threshold = DecimalParameter(-2.0, 5.0, default=0.5, space="buy", optimize=True, load=True)
# Ultimate Score
ultimate_score_threshold = DecimalParameter(0.5, 3.0, default=1.5, space="buy", optimize=True, load=True)
# Advanced Entry Filters
require_volume_confirmation = BooleanParameter(default=True, space="buy", optimize=False, load=True)
require_momentum_confirmation = BooleanParameter(default=True, space="buy", optimize=False, load=True)
require_structure_confirmation = BooleanParameter(default=True, space="buy", optimize=False, load=True)
# ✅ Replace your old ROI with this:
minimal_roi = {
"0": 0.07, # 7% immediate (vs your 12%)
"5": 0.055, # 5.5% after 1.25h
"10": 0.04, # 4% after 2.5h
"20": 0.03, # 3% after 5h
"40": 0.025, # 2.5% after 10h
"80": 0.02, # 2% after 20h
"160": 0.015, # 1.5% after 40h
"320": 0.01 # 1% after 80h
}
# Plot configuration for backtesting UI
plot_config = {
"main_plot": {
# Trend indicators
"ema50": {"color": "gray", "type": "line"},
# Support/Resistance
"minima_sort_threshold": {"color": "#4ae747", "type": "line"},
"maxima_sort_threshold": {"color": "#5b5e4b", "type": "line"},
},
"subplots": {
"extrema_analysis": {
"s_extrema": {"color": "#f53580", "type": "line"},
"maxima": {"color": "#a29db9", "type": "scatter"},
"minima": {"color": "#aac7fc", "type": "scatter"},
},
"murrey_math_levels": {
"[4/8]P": {"color": "blue", "type": "line"}, # 50% MML
"[6/8]P": {"color": "green", "type": "line"}, # 75% MML
"[2/8]P": {"color": "orange", "type": "line"}, # 25% MML
"[8/8]P": {"color": "red", "type": "line"}, # 100% MML
"[0/8]P": {"color": "red", "type": "line"}, # 0% MML
"mmlextreme_oscillator": {"color": "purple", "type": "line"},
},
"rsi_analysis": {
"rsi": {"color": "purple", "type": "line"},
"rsi_divergence_bull": {"color": "green", "type": "scatter"},
"rsi_divergence_bear": {"color": "red", "type": "scatter"},
},
"confluence_analysis": {
"confluence_score": {"color": "gold", "type": "line"},
"near_support": {"color": "green", "type": "scatter"},
"near_resistance": {"color": "red", "type": "scatter"},
"near_mml": {"color": "blue", "type": "line"},
"volume_spike": {"color": "orange", "type": "scatter"},
},
"volume_analysis": {
"volume_strength": {"color": "cyan", "type": "line"},
"volume_pressure": {"color": "magenta", "type": "line"},
"buying_pressure": {"color": "green", "type": "line"},
"selling_pressure": {"color": "red", "type": "line"},
"money_flow_index": {"color": "yellow", "type": "line"},
},
"momentum_analysis": {
"momentum_quality": {"color": "brown", "type": "line"},
"momentum_acceleration": {"color": "pink", "type": "line"},
"momentum_consistency": {"color": "lime", "type": "line"},
"momentum_oscillator": {"color": "navy", "type": "line"},
},
"structure_analysis": {
"structure_score": {"color": "teal", "type": "line"},
"bullish_structure": {"color": "green", "type": "line"},
"bearish_structure": {"color": "red", "type": "line"},
"structure_break_up": {"color": "lime", "type": "scatter"},
"structure_break_down": {"color": "crimson", "type": "scatter"},
},
"trend_strength": {
"trend_strength": {"color": "indigo", "type": "line"},
"trend_strength_5": {"color": "lightblue", "type": "line"},
"trend_strength_10": {"color": "mediumblue", "type": "line"},
"trend_strength_20": {"color": "darkblue", "type": "line"},
},
"ultimate_signals": {
"ultimate_score": {"color": "gold", "type": "line"},
"signal_strength": {"color": "silver", "type": "line"},
"high_quality_setup": {"color": "lime", "type": "scatter"},
"entry_type": {"color": "white", "type": "line"},
},
"market_conditions": {
"strong_uptrend": {"color": "green", "type": "scatter"},
"strong_downtrend": {"color": "red", "type": "scatter"},
"ranging": {"color": "yellow", "type": "scatter"},
"strong_up_momentum": {"color": "lime", "type": "scatter"},
"strong_down_momentum": {"color": "crimson", "type": "scatter"},
},
"di_analysis": {
"DI_values": {"color": "orange", "type": "line"},
"DI_catch": {"color": "red", "type": "scatter"},
"plus_di": {"color": "green", "type": "line"},
"minus_di": {"color": "red", "type": "line"},
}
},
}
# Helper method to check if we have an active position in the opposite direction
def has_active_trade(self, pair: str, side: str) -> bool:
"""
Check if there's an active trade in the specified direction
"""
try:
trades = Trade.get_open_trades()
for trade in trades:
if trade.pair == pair:
if side == "long" and not trade.is_short:
return True
elif side == "short" and trade.is_short:
return True
except Exception as e:
logger.warning(f"Error checking active trades for {pair}: {e}")
return False
@staticmethod
def _calculate_mml_core(mn: float, finalH: float, mx: float, finalL: float,
mml_c1: float, mml_c2: float) -> Dict[str, float]:
dmml_calc = ((finalH - finalL) / 8.0) * mml_c1
if dmml_calc == 0 or np.isinf(dmml_calc) or np.isnan(dmml_calc) or finalH == finalL:
return {key: finalL for key in MML_LEVEL_NAMES}
mml_val = (mx * mml_c2) + (dmml_calc * 3)
if np.isinf(mml_val) or np.isnan(mml_val):
return {key: finalL for key in MML_LEVEL_NAMES}
ml = [mml_val - (dmml_calc * i) for i in range(16)]
return {
"[-3/8]P": ml[14], "[-2/8]P": ml[13], "[-1/8]P": ml[12],
"[0/8]P": ml[11], "[1/8]P": ml[10], "[2/8]P": ml[9],
"[3/8]P": ml[8], "[4/8]P": ml[7], "[5/8]P": ml[6],
"[6/8]P": ml[5], "[7/8]P": ml[4], "[8/8]P": ml[3],
"[+1/8]P": ml[2], "[+2/8]P": ml[1], "[+3/8]P": ml[0],
}
def calculate_rolling_murrey_math_levels_optimized(self, df: pd.DataFrame, window_size: int) -> Dict[str, pd.Series]:
"""
OPTIMIZED Version - Calculate MML levels every 5 candles using only past data
"""
murrey_levels_data: Dict[str, list] = {key: [np.nan] * len(df) for key in MML_LEVEL_NAMES}
mml_c1 = self.mml_const1.value
mml_c2 = self.mml_const2.value
calculation_step = 5
for i in range(0, len(df), calculation_step):
if i < window_size:
continue
# Use data up to the previous candle for the rolling window
window_end = i - 1
window_start = window_end - window_size + 1
if window_start < 0:
window_start = 0
window_data = df.iloc[window_start:window_end]
mn_period = window_data["low"].min()
mx_period = window_data["high"].max()
current_close = df["close"].iloc[window_end] if window_end > 0 else df["close"].iloc[0]
if pd.isna(mn_period) or pd.isna(mx_period) or mn_period == mx_period:
for key in MML_LEVEL_NAMES:
murrey_levels_data[key][window_end] = current_close
continue
levels = self._calculate_mml_core(mn_period, mx_period, mx_period, mn_period, mml_c1, mml_c2)
for key in MML_LEVEL_NAMES:
murrey_levels_data[key][window_end] = levels.get(key, current_close)
# Interpolate using only past data up to each point
for key in MML_LEVEL_NAMES:
series = pd.Series(murrey_levels_data[key], index=df.index)
# Interpolate forward only up to the current point, avoiding future data
series = series.expanding().mean().ffill() # Use expanding mean as a safe alternative
murrey_levels_data[key] = series.tolist()
return {key: pd.Series(data, index=df.index) for key, data in murrey_levels_data.items()}
def calculate_trend_strength(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Calculate trend strength to avoid entering against strong trends
"""
# Linear regression slope
def calc_slope(series, period=10):
"""Calculate linear regression slope"""
if len(series) < period:
return 0
x = np.arange(period)
y = series.iloc[-period:].values
if np.isnan(y).any():
return 0
slope = np.polyfit(x, y, 1)[0]
return slope
# Calculate trend strength using multiple timeframes
df['slope_5'] = df['close'].rolling(5).apply(lambda x: calc_slope(x, 5), raw=False)
df['slope_10'] = df['close'].rolling(10).apply(lambda x: calc_slope(x, 10), raw=False)
df['slope_20'] = df['close'].rolling(20).apply(lambda x: calc_slope(x, 20), raw=False)
# Normalize slopes by price
df['trend_strength_5'] = df['slope_5'] / df['close'] * 100
df['trend_strength_10'] = df['slope_10'] / df['close'] * 100
df['trend_strength_20'] = df['slope_20'] / df['close'] * 100
# Combined trend strength
df['trend_strength'] = (df['trend_strength_5'] + df['trend_strength_10'] + df['trend_strength_20']) / 3
# Trend classification
strong_up_threshold = self.trend_strength_threshold.value
strong_down_threshold = -self.trend_strength_threshold.value
df['strong_uptrend'] = df['trend_strength'] > strong_up_threshold
df['strong_downtrend'] = df['trend_strength'] < strong_down_threshold
df['ranging'] = (df['trend_strength'].abs() < strong_up_threshold * 0.5)
return df
@property
def protections(self):
prot = [{"method": "CooldownPeriod", "stop_duration_candles": self.cooldown_lookback.value}]
if self.use_stop_protection.value:
prot.append({
"method": "StoplossGuard",
"lookback_period_candles": 72,
"trade_limit": 2,
"stop_duration_candles": self.stop_duration.value,
"only_per_pair": False,
})
return prot
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
leverage: float, entry_tag: Optional[str], side: str, **kwargs) -> float:
# ===========================================
# STAKE LIMITS DEFINIEREN
# ===========================================
# Maximaler Stake pro Trade (in USDT) - kannst du anpassen
MAX_STAKE_PER_TRADE = self.max_stake_per_trade
# Maximaler Stake basierend auf Portfolio
try:
total_portfolio = self.wallets.get_total_stake_amount()
MAX_STAKE_PERCENTAGE = self.max_portfolio_percentage_per_trade
max_stake_from_portfolio = total_portfolio * MAX_STAKE_PERCENTAGE
except:
# Fallback wenn wallets nicht verfügbar
max_stake_from_portfolio = MAX_STAKE_PER_TRADE
total_portfolio = 1000.0 # Dummy value
# Market condition check für volatility-based stake reduction (DEIN CODE)
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
if not dataframe.empty:
last_candle = dataframe.iloc[-1]
current_volatility = last_candle.get("volatility", 0.02)
# Reduziere Stake in hochvolatilen Märkten
if current_volatility > 0.05: # 5% ATR/Price ratio
volatility_reduction = min(0.5, current_volatility * 10) # Max 50% reduction
proposed_stake *= (1 - volatility_reduction)
logger.info(f"{pair} Stake reduced by {volatility_reduction:.1%} due to high volatility ({current_volatility:.2%})")
# DCA Multiplier Berechnung (DEIN CODE)
calculated_max_dca_multiplier = 1.0
if self.position_adjustment_enable:
num_safety_orders = int(self.max_safety_orders.value)
volume_scale = self.safety_order_volume_scale.value
if num_safety_orders > 0 and volume_scale > 0:
current_order_relative_size = 1.0
for _ in range(num_safety_orders):
current_order_relative_size *= volume_scale
calculated_max_dca_multiplier += current_order_relative_size
else:
logger.warning(f"{pair}: Could not calculate max_dca_multiplier due to "
f"invalid max_safety_orders ({num_safety_orders}) or "
f"safety_order_volume_scale ({volume_scale}). Defaulting to 1.0.")
else:
logger.debug(f"{pair}: Position adjustment not enabled. max_dca_multiplier is 1.0.")
if calculated_max_dca_multiplier > 0:
stake_amount = proposed_stake / calculated_max_dca_multiplier
# ===========================================
# NEUE STAKE LIMITS ANWENDEN
# ===========================================
# Verschiedene Limits prüfen
final_stake = min(
stake_amount,
MAX_STAKE_PER_TRADE,
max_stake_from_portfolio,
max_stake # Freqtrade's max_stake
)
# Bestimme welches Limit gegriffen hat
limit_reason = "calculated"
if final_stake == MAX_STAKE_PER_TRADE:
limit_reason = "max_per_trade"
elif final_stake == max_stake_from_portfolio:
limit_reason = "portfolio_percentage"
elif final_stake == max_stake:
limit_reason = "freqtrade_max"
logger.info(f"{pair} Initial stake calculated: {final_stake:.8f} (Proposed: {proposed_stake:.8f}, "
f"Calculated Max DCA Multiplier: {calculated_max_dca_multiplier:.2f}, "
f"Limited by: {limit_reason}, Portfolio %: {(final_stake/total_portfolio)*100:.1f}%)")
# Min stake prüfen (DEIN CODE)
if min_stake is not None and final_stake < min_stake:
logger.info(f"{pair} Initial stake {final_stake:.8f} was below min_stake {min_stake:.8f}. "
f"Adjusting to min_stake. Consider tuning your DCA parameters or proposed stake.")
final_stake = min_stake
return final_stake
else:
# Fallback (DEIN CODE)
logger.warning(
f"{pair} Calculated max_dca_multiplier is {calculated_max_dca_multiplier:.2f}, which is invalid. "
f"Using proposed_stake: {proposed_stake:.8f}")
return proposed_stake
def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime,
proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
if dataframe.empty:
logger.warning(f"{pair} Empty DataFrame in custom_entry_price. Returning proposed_rate.")
return proposed_rate
last_candle = dataframe.iloc[-1]
entry_price = (last_candle["close"] + last_candle["open"] + proposed_rate) / 3.0
if side == "long":
if proposed_rate < entry_price:
entry_price = proposed_rate
elif side == "short":
if proposed_rate > entry_price:
entry_price = proposed_rate
logger.info(
f"{pair} Calculated Entry Price: {entry_price:.8f} | Last Close: {last_candle['close']:.8f}, "
f"Last Open: {last_candle['open']:.8f}, Proposed Rate: {proposed_rate:.8f}")
if self.last_entry_price is not None and abs(entry_price - self.last_entry_price) < 0.000005:
increment_factor = self.increment_for_unique_price.value if side == "long" else (
1.0 / self.increment_for_unique_price.value)
entry_price *= increment_factor
logger.info(
f"{pair} Entry price incremented to {entry_price:.8f} (previous: {self.last_entry_price:.8f}) due to proximity.")
self.last_entry_price = entry_price
return entry_price
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
if dataframe.empty or 'atr' not in dataframe.columns:
return self.stoploss # Use strategy stoploss (-0.15) as fallback
atr = dataframe["atr"].iat[-1]
if pd.isna(atr) or atr <= 0:
return self.stoploss # Fallback to -0.15
atr_percent = atr / current_rate
# Profit-based multiplier adjustment
if current_profit > 0.15:
multiplier = 1.0
elif current_profit > 0.08:
multiplier = 1.2
elif current_profit > 0.03:
multiplier = 1.4
else:
multiplier = 1.6
calculated_stoploss = -(atr_percent * multiplier * self.atr_stoploss_multiplier.value)
# Initialize trailing_offset
trailing_offset = 0.0
# Dynamic adjustment with trailing-like behavior
if current_profit > self.trailing_stop_positive_offset: # 0.03
trailing_offset = max(0, current_profit - self.trailing_stop_positive) # Adjust based on 1.5% trail
calculated_stoploss = min(calculated_stoploss, -trailing_offset) # Trail up in profit
final_stoploss = max(
min(calculated_stoploss, self.atr_stoploss_ceiling.value),
self.atr_stoploss_maximum.value
)
logger.info(f"{pair} Custom SL: {final_stoploss:.3f} | ATR: {atr:.6f} | Profit: {current_profit:.3f} | Trailing: {trailing_offset:.3f}")
return final_stoploss
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float, **kwargs) -> Optional[float]:
"""
IMPROVED VERSION: Cleaner logic, better coordination with stop loss
"""
# Get market conditions
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
if not dataframe.empty:
last_candle = dataframe.iloc[-1]
btc_trend = last_candle.get('btc_trend', 0)
market_score = last_candle.get('market_score', 0.5)
market_breadth = last_candle.get('market_breadth', 0.5)
rsi = last_candle.get('rsi', 50)
else:
btc_trend = 0
market_score = 0.5
market_breadth = 0.5
rsi = 50
count_of_entries = trade.nr_of_successful_entries
count_of_exits = trade.nr_of_successful_exits
# ==========================================
# PROFIT TAKING LOGIC (IMPROVED)
# ==========================================
# 1. Extreme greed protection (more conservative)
if market_score > 0.8 and current_profit > 0.18 and count_of_exits == 0:
logger.info(f"{trade.pair} Extreme greed profit taking: {market_score:.2f}")
amount_to_sell = (trade.amount * current_rate) * 0.25 # Reduced from 33%
return -amount_to_sell
# 2. Standard profit taking (unchanged)
if current_profit > 0.15 and count_of_exits == 0:
logger.info(f"{trade.pair} Standard profit taking (25%) at {current_profit:.2%}")
amount_to_sell = (trade.amount * current_rate) * 0.25
return -amount_to_sell
# 3. Bear market profit taking (IMPROVED - more conservative)
if not trade.is_short and btc_trend < -0.02: # Significant downtrend
if current_profit > 0.12 and count_of_exits == 0: # Higher threshold
logger.info(f"{trade.pair} Bear market profit taking at {current_profit:.2%}")
amount_to_sell = (trade.amount * current_rate) * 0.3 # Reduced from 50%
return -amount_to_sell
# 4. Secondary profit taking (unchanged)
if current_profit > 0.30 and count_of_exits == 1:
logger.info(f"{trade.pair} Secondary profit taking (33%) at {current_profit:.2%}")
amount_to_sell = (trade.amount * current_rate) * (1 / 3)
return -amount_to_sell
# ==========================================
# DCA LOGIC (UNCHANGED - LOOKS GOOD)
# ==========================================
if not self.position_adjustment_enable:
return None
# Validate strategy variables
if not hasattr(self, 'max_dca_orders'):
logger.error(f"{trade.pair} Missing max_dca_orders parameter")
return None
# Get DCA limits
max_dca_for_pair = self.max_dca_orders
max_total_stake = getattr(self, 'max_total_stake_per_pair', 10)
max_single_dca = getattr(self, 'max_single_dca_amount', 5)
# Check DCA limits
if count_of_entries > max_dca_for_pair:
logger.info(f"{trade.pair} 🛑 MAX DCA REACHED: {count_of_entries}/{max_dca_for_pair}")
return None
if trade.stake_amount >= max_total_stake:
logger.info(f"{trade.pair} 🛑 MAX STAKE REACHED: {trade.stake_amount:.2f}/{max_total_stake} USDT")
return None
# Market condition blocks
if market_breadth < 0.25 and not trade.is_short:
logger.info(f"{trade.pair} Blocking DCA due to bearish market: {market_breadth:.2%}")
return None
# Volatility adjustment
dca_multiplier = 1.5 if last_candle.get('market_regime') == 'high_volatility' else 1.0
# DCA trigger calculation (FIXED)
trigger = getattr(self, 'initial_safety_order_trigger', DecimalParameter(-0.02, -0.01, default=-0.018)).value
trigger = trigger * dca_multiplier
# CORRECTED DCA LOGIC: DCA when we're LOSING money
if (current_profit > trigger / 2.0 and count_of_entries == 1) or \
(current_profit > trigger and count_of_entries == 2) or \
(current_profit > trigger * 1.5 and count_of_entries == 3):
logger.info(f"{trade.pair} DCA not triggered. Profit {current_profit:.2%} > threshold {trigger:.2%}")
return None
# Calculate DCA amount (unchanged - looks good)
try:
filled_entry_orders = trade.select_filled_orders(trade.entry_side)
if not filled_entry_orders:
return None
# Progressive DCA sizing
dca_multipliers = [1.0, 0.8, 0.6]
current_multiplier = dca_multipliers[count_of_entries - 1] if count_of_entries <= len(dca_multipliers) else 0.5
dca_stake_amount = max_single_dca * current_multiplier
# Apply limits
remaining_budget = max_total_stake - trade.stake_amount
if dca_stake_amount > remaining_budget:
if remaining_budget > 1:
dca_stake_amount = remaining_budget
else:
return None
if min_stake and dca_stake_amount < min_stake:
dca_stake_amount = min_stake
logger.info(f"{trade.pair} 🛑 DCA #{count_of_entries}: +{dca_stake_amount:.2f} USDT")
return dca_stake_amount
except Exception as e:
logger.error(f"DCA calculation error for {trade.pair}: {e}")
return None
def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float,
max_leverage: float, side: str, **kwargs) -> float:
window_size = self.leverage_window_size.value
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
if len(dataframe) < window_size:
logger.warning(
f"{pair} Not enough data ({len(dataframe)} candles) to calculate dynamic leverage (requires {window_size}). Using proposed: {proposed_leverage}")
return proposed_leverage
close_prices_series = dataframe["close"].tail(window_size)
high_prices_series = dataframe["high"].tail(window_size)
low_prices_series = dataframe["low"].tail(window_size)
base_leverage = self.leverage_base.value
rsi_array = ta.RSI(close_prices_series, timeperiod=14)
atr_array = ta.ATR(high_prices_series, low_prices_series, close_prices_series, timeperiod=14)
sma_array = ta.SMA(close_prices_series, timeperiod=20)
macd_output = ta.MACD(close_prices_series, fastperiod=12, slowperiod=26, signalperiod=9)
current_rsi = rsi_array[-1] if rsi_array.size > 0 and not np.isnan(rsi_array[-1]) else 50.0
current_atr = atr_array[-1] if atr_array.size > 0 and not np.isnan(atr_array[-1]) else 0.0
current_sma = sma_array[-1] if sma_array.size > 0 and not np.isnan(sma_array[-1]) else current_rate
current_macd_hist = 0.0
if isinstance(macd_output, pd.DataFrame):
if not macd_output.empty and 'macdhist' in macd_output.columns:
valid_macdhist_series = macd_output['macdhist'].dropna()
if not valid_macdhist_series.empty:
current_macd_hist = valid_macdhist_series.iloc[-1]
# Apply rules based on indicators
if side == "long":
if current_rsi < self.leverage_rsi_low.value:
base_leverage *= self.leverage_long_increase_factor.value
elif current_rsi > self.leverage_rsi_high.value:
base_leverage *= self.leverage_long_decrease_factor.value
if current_atr > 0 and current_rate > 0:
if (current_atr / current_rate) > self.leverage_atr_threshold_pct.value:
base_leverage *= self.leverage_volatility_decrease_factor.value
if current_macd_hist > 0:
base_leverage *= self.leverage_long_increase_factor.value
if current_sma > 0 and current_rate < current_sma:
base_leverage *= self.leverage_long_decrease_factor.value
adjusted_leverage = round(max(1.0, min(base_leverage, max_leverage)), 2)
logger.info(
f"{pair} Dynamic Leverage: {adjusted_leverage:.2f} (Base: {base_leverage:.2f}, RSI: {current_rsi:.2f}, "
f"ATR: {current_atr:.4f}, MACD Hist: {current_macd_hist:.4f}, SMA: {current_sma:.4f})")
return adjusted_leverage
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
ULTIMATE indicator calculations with advanced market analysis
"""
# === STANDARD INDICATORS ===
dataframe["ema50"] = ta.EMA(dataframe["close"], timeperiod=50)
dataframe["ema100"] = ta.EMA(dataframe["close"], timeperiod=100) # Neu hinzufügen
dataframe["rsi"] = ta.RSI(dataframe["close"])
dataframe["atr"] = ta.ATR(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=10)
dataframe["plus_di"] = ta.PLUS_DI(dataframe)
dataframe["minus_di"] = ta.MINUS_DI(dataframe)
dataframe["DI_values"] = dataframe["plus_di"] - dataframe["minus_di"]
dataframe["DI_cutoff"] = 0
# === EXTREMA DETECTION ===
extrema_order = self.indicator_extrema_order.value
dataframe["maxima"] = (
dataframe["close"] == dataframe["close"].shift(1).rolling(window=extrema_order).max()
).astype(int)
dataframe["minima"] = (
dataframe["close"] == dataframe["close"].shift(1).rolling(window=extrema_order).min()
).astype(int)
dataframe["s_extrema"] = 0
dataframe.loc[dataframe["minima"] == 1, "s_extrema"] = -1
dataframe.loc[dataframe["maxima"] == 1, "s_extrema"] = 1
# === HEIKIN-ASHI ===
dataframe["ha_close"] = (dataframe["open"] + dataframe["high"] + dataframe["low"] + dataframe["close"]) / 4
# === ROLLING EXTREMA ===
dataframe["minh2"], dataframe["maxh2"] = calculate_minima_maxima(dataframe, self.h2.value)
dataframe["minh1"], dataframe["maxh1"] = calculate_minima_maxima(dataframe, self.h1.value)
dataframe["minh0"], dataframe["maxh0"] = calculate_minima_maxima(dataframe, self.h0.value)
dataframe["mincp"], dataframe["maxcp"] = calculate_minima_maxima(dataframe, self.cp.value)
# === MURREY MATH LEVELS ===
mml_window = self.indicator_mml_window.value
murrey_levels = self.calculate_rolling_murrey_math_levels_optimized(dataframe, window_size=mml_window)
for level_name in MML_LEVEL_NAMES:
if level_name in murrey_levels:
dataframe[level_name] = murrey_levels[level_name]
else:
dataframe[level_name] = dataframe["close"]
# === MML OSCILLATOR ===
mml_4_8 = dataframe.get("[4/8]P")
mml_plus_3_8 = dataframe.get("[+3/8]P")
mml_minus_3_8 = dataframe.get("[-3/8]P")
if mml_4_8 is not None and mml_plus_3_8 is not None and mml_minus_3_8 is not None:
osc_denominator = (mml_plus_3_8 - mml_minus_3_8).replace(0, np.nan)
dataframe["mmlextreme_oscillator"] = 100 * ((dataframe["close"] - mml_4_8) / osc_denominator)
else:
dataframe["mmlextreme_oscillator"] = np.nan
# === DI CATCH ===
dataframe["DI_catch"] = np.where(dataframe["DI_values"] > dataframe["DI_cutoff"], 0, 1)
# === ROLLING THRESHOLDS ===
rolling_window_threshold = self.indicator_rolling_window_threshold.value
dataframe["minima_sort_threshold"] = dataframe["close"].rolling(
window=rolling_window_threshold, min_periods=1
).min()
dataframe["maxima_sort_threshold"] = dataframe["close"].rolling(
window=rolling_window_threshold, min_periods=1
).max()
# === EXTREMA CHECKS ===
rolling_check_window = self.indicator_rolling_check_window.value
dataframe["minima_check"] = (
dataframe["minima"].rolling(window=rolling_check_window, min_periods=1).sum() == 0
).astype(int)
dataframe["maxima_check"] = (
dataframe["maxima"].rolling(window=rolling_check_window, min_periods=1).sum() == 0
).astype(int)
# === VOLATILITY INDICATORS ===
dataframe["volatility_range"] = dataframe["high"] - dataframe["low"]
dataframe["avg_volatility"] = dataframe["volatility_range"].rolling(window=50).mean()
dataframe["avg_volume"] = dataframe["volume"].rolling(window=50).mean()
# === TREND STRENGTH INDICATORS ===
def calc_slope(series, period):
"""Calculate linear regression slope"""
if len(series) < period:
return 0
x = np.arange(period)
y = series.values
if np.isnan(y).any():
return 0
try:
slope = np.polyfit(x, y, 1)[0]
return slope
except:
return 0
dataframe['slope_5'] = dataframe['close'].rolling(5).apply(lambda x: calc_slope(x, 5), raw=False)
dataframe['slope_10'] = dataframe['close'].rolling(10).apply(lambda x: calc_slope(x, 10), raw=False)
dataframe['slope_20'] = dataframe['close'].rolling(20).apply(lambda x: calc_slope(x, 20), raw=False)
dataframe['trend_strength_5'] = dataframe['slope_5'] / dataframe['close'] * 100
dataframe['trend_strength_10'] = dataframe['slope_10'] / dataframe['close'] * 100
dataframe['trend_strength_20'] = dataframe['slope_20'] / dataframe['close'] * 100
dataframe['trend_strength'] = (dataframe['trend_strength_5'] + dataframe['trend_strength_10'] + dataframe['trend_strength_20']) / 3
strong_threshold = 0.02
dataframe['strong_uptrend'] = dataframe['trend_strength'] > strong_threshold
dataframe['strong_downtrend'] = dataframe['trend_strength'] < -strong_threshold
dataframe['ranging'] = dataframe['trend_strength'].abs() < (strong_threshold * 0.5)
# === MOMENTUM INDICATORS ===
dataframe['price_momentum'] = dataframe['close'].pct_change(3)
dataframe['momentum_increasing'] = dataframe['price_momentum'] > dataframe['price_momentum'].shift(1)
dataframe['momentum_decreasing'] = dataframe['price_momentum'] < dataframe['price_momentum'].shift(1)
dataframe['volume_momentum'] = dataframe['volume'].rolling(3).mean() / dataframe['volume'].rolling(20).mean()
dataframe['rsi_divergence_bull'] = (
(dataframe['close'] < dataframe['close'].shift(5)) &
(dataframe['rsi'] > dataframe['rsi'].shift(5))
)
dataframe['rsi_divergence_bear'] = (
(dataframe['close'] > dataframe['close'].shift(5)) &
(dataframe['rsi'] < dataframe['rsi'].shift(5))
)
# === CANDLE PATTERNS ===
dataframe['green_candle'] = dataframe['close'] > dataframe['open']
dataframe['red_candle'] = dataframe['close'] < dataframe['open']
dataframe['consecutive_green'] = dataframe['green_candle'].rolling(3).sum()
dataframe['consecutive_red'] = dataframe['red_candle'].rolling(3).sum()
dataframe['strong_up_momentum'] = (
(dataframe['consecutive_green'] >= 3) &
(dataframe['volume'] > dataframe['avg_volume']) &
(dataframe['trend_strength'] > strong_threshold)
)
dataframe['strong_down_momentum'] = (
(dataframe['consecutive_red'] >= 3) &
(dataframe['volume'] > dataframe['avg_volume']) &
(dataframe['trend_strength'] < -strong_threshold)
)
# === 🚀 ADVANCED ANALYSIS MODULES ===
# 1. CONFLUENCE ANALYSIS
if self.confluence_enabled.value:
dataframe = calculate_confluence_score(dataframe)
else:
dataframe['confluence_score'] = 0
# 2. SMART VOLUME ANALYSIS
if self.volume_analysis_enabled.value:
dataframe = calculate_smart_volume(dataframe)
else:
dataframe['volume_pressure'] = 0
dataframe['volume_strength'] = 1.0
dataframe['money_flow_index'] = 50
# 3. ADVANCED MOMENTUM
if self.momentum_analysis_enabled.value:
dataframe = calculate_advanced_momentum(dataframe)
else:
dataframe['momentum_quality'] = 0
dataframe['momentum_acceleration'] = 0
# 4. MARKET STRUCTURE
if self.structure_analysis_enabled.value:
dataframe = calculate_market_structure(dataframe)
else:
dataframe['structure_score'] = 0
dataframe['structure_break_up'] = 0
# 5. ADVANCED ENTRY SIGNALS
dataframe = calculate_advanced_entry_signals(dataframe)
# === 🎯 ULTIMATE MARKET SCORE ===
dataframe['ultimate_score'] = (
dataframe['confluence_score'] * 0.25 + # 25% confluence
dataframe['volume_pressure'] * 0.2 + # 20% volume pressure
dataframe['momentum_quality'] * 0.2 + # 20% momentum quality
(dataframe['structure_score'] / 5) * 0.15 + # 15% structure (normalized)
(dataframe['signal_strength'] / 10) * 0.2 # 20% signal strength
)
# Normalize ultimate score to 0-1 range
dataframe['ultimate_score'] = dataframe['ultimate_score'].clip(0, 5) / 5
# === FINAL QUALITY CHECKS ===
dataframe['high_quality_setup'] = (
(dataframe['ultimate_score'] > self.ultimate_score_threshold.value) &
(dataframe['signal_strength'] >= 5) &
(dataframe['volume_strength'] > 1.1) &
(dataframe['rsi'] > 30) & (dataframe['rsi'] < 70)
).astype(int)
# === DEBUG INFO ===
if metadata['pair'] in ['BTC/USDT:USDT', 'ETH/USDT:USDT']: # Only log for major pairs
latest_score = dataframe['ultimate_score'].iloc[-1]
latest_signal = dataframe['signal_strength'].iloc[-1]
logger.info(f"{metadata['pair']} Ultimate Score: {latest_score:.3f}, Signal Strength: {latest_signal}")
# ===========================================
# 🚨 REGIME CHANGE DETECTION (NEU HINZUFÜGEN)
# ===========================================
if self.regime_change_enabled.value:
# ===========================================
# âš¡ FLASH MOVE DETECTION
# ===========================================
flash_candles = self.flash_move_candles.value
flash_threshold = self.flash_move_threshold.value
# Schnelle Preisbewegungen
dataframe['price_change_fast'] = dataframe['close'].pct_change(flash_candles)
dataframe['flash_pump'] = dataframe['price_change_fast'] > flash_threshold
dataframe['flash_dump'] = dataframe['price_change_fast'] < -flash_threshold
dataframe['flash_move'] = dataframe['flash_pump'] | dataframe['flash_dump']
# ===========================================
# 🔊 VOLUME SPIKE DETECTION
# ===========================================
volume_ma20 = dataframe['volume'].rolling(20).mean()
volume_multiplier = self.volume_spike_multiplier.value
dataframe['volume_spike'] = dataframe['volume'] > (volume_ma20 * volume_multiplier)
# Volume + Bewegung kombiniert
dataframe['volume_pump'] = dataframe['volume_spike'] & dataframe['flash_pump']
dataframe['volume_dump'] = dataframe['volume_spike'] & dataframe['flash_dump']
# ===========================================
# 🌊 MARKET SENTIMENT DETECTION
# ===========================================
# Market Breadth Change (falls vorhanden)
if 'market_breadth' in dataframe.columns:
dataframe['market_breadth_change'] = dataframe['market_breadth'].diff(3)
sentiment_threshold = self.sentiment_shift_threshold.value
dataframe['sentiment_shift_bull'] = dataframe['market_breadth_change'] > sentiment_threshold
dataframe['sentiment_shift_bear'] = dataframe['market_breadth_change'] < -sentiment_threshold
else:
dataframe['sentiment_shift_bull'] = False
dataframe['sentiment_shift_bear'] = False
# ===========================================
# â‚¿ BTC CORRELATION MONITORING
# ===========================================
# BTC Flash Moves (falls BTC Daten vorhanden)
if 'btc_close' in dataframe.columns:
dataframe['btc_change_fast'] = dataframe['btc_close'].pct_change(flash_candles)
dataframe['btc_flash_pump'] = dataframe['btc_change_fast'] > flash_threshold
dataframe['btc_flash_dump'] = dataframe['btc_change_fast'] < -flash_threshold
# Correlation Break: BTC bewegt sich stark, Coin nicht
pair_movement = dataframe['price_change_fast'].abs()
btc_movement = dataframe['btc_change_fast'].abs()
dataframe['correlation_break'] = (btc_movement > flash_threshold) & (pair_movement < flash_threshold * 0.4)
else:
dataframe['btc_flash_pump'] = False
dataframe['btc_flash_dump'] = False
dataframe['correlation_break'] = False
# ===========================================
# 🎯 REGIME CHANGE SCORE
# ===========================================
# Kombiniere alle Signale
regime_signals = [
'flash_move', 'volume_spike',
'sentiment_shift_bull', 'sentiment_shift_bear',
'btc_flash_pump', 'btc_flash_dump', 'correlation_break'
]
dataframe['regime_change_score'] = 0
for signal in regime_signals:
if signal in dataframe.columns:
dataframe['regime_change_score'] += dataframe[signal].astype(int)
# Normalisiere auf 0-1
max_signals = len(regime_signals)
dataframe['regime_change_intensity'] = dataframe['regime_change_score'] / max_signals
# Alert Level
sensitivity = self.regime_change_sensitivity.value
dataframe['regime_alert'] = dataframe['regime_change_intensity'] >= sensitivity
else:
# Falls Regime Detection deaktiviert
dataframe['flash_pump'] = False
dataframe['flash_dump'] = False
dataframe['volume_pump'] = False
dataframe['volume_dump'] = False
dataframe['regime_alert'] = False
dataframe['regime_change_intensity'] = 0.0
return dataframe
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
ULTIMATE ENTRY LOGIC - Multi-factor confluence system
"""
# ===========================================
# INITIALIZE ENTRY COLUMNS
# ===========================================
dataframe["enter_long"] = 0
dataframe["enter_short"] = 0
dataframe["enter_tag"] = ""
# === CORE CONDITIONS (must all be true) ===
core_conditions = (
# Basic trend and momentum
(dataframe['close'] > dataframe['ema50']) &
(dataframe['rsi'] > 25) & (dataframe['rsi'] < 75) &
(dataframe['trend_strength'] > -0.02) & # Not in strong downtrend
# Volume confirmation
(dataframe['volume'] > dataframe['avg_volume'] * 0.7) & # Minimum volume
# No recent distribution
(dataframe['selling_pressure'] <= 4)
)
# === ADVANCED CONDITIONS (weighted scoring) ===
# 1. CONFLUENCE CONDITIONS
confluence_conditions = (
(dataframe['confluence_score'] >= self.confluence_threshold.value) |
(
(dataframe['confluence_score'] >= (self.confluence_threshold.value - 1)) &
(dataframe['near_support'] == 1) &
(dataframe['volume_spike'] == 1)
)
)
# 2. VOLUME CONDITIONS
volume_conditions = (
(dataframe['volume_pressure'] >= self.volume_pressure_threshold.value) &
(dataframe['volume_strength'] > self.volume_strength_threshold.value) &
(dataframe['money_flow_index'] > 45)
) if self.require_volume_confirmation.value else True
# 3. MOMENTUM CONDITIONS
momentum_conditions = (
(dataframe['momentum_quality'] >= self.momentum_quality_threshold.value) &
(dataframe['momentum_acceleration'] > -0.01) &
(dataframe['momentum_consistency'] >= 2)
) if self.require_momentum_confirmation.value else True
# 4. STRUCTURE CONDITIONS
structure_conditions = (
(dataframe['structure_score'] >= self.structure_score_threshold.value) &
(dataframe['bullish_structure'] > dataframe['bearish_structure'])
) if self.require_structure_confirmation.value else True
# === ORIGINAL EXTREMA CONDITIONS ===
extrema_conditions = (
# Minima conditions
(
(dataframe["minima"] == 1) &
(dataframe["minima_check"] == 1) &
(dataframe["close"] <= dataframe["minima_sort_threshold"] * 1.02) &
(dataframe["DI_catch"] == 1)
) |
# Alternative: MML level conditions
(
(dataframe["close"] <= dataframe["[0/8]P"] * 1.01) |
(dataframe["close"] <= dataframe["[2/8]P"] * 1.01) |
(dataframe["close"] <= dataframe["[4/8]P"] * 1.01)
) |
# Rolling extrema conditions
(
(dataframe["close"] <= dataframe["minh2"] * 1.015) |
(dataframe["close"] <= dataframe["minh1"] * 1.015) |
(dataframe["close"] <= dataframe["minh0"] * 1.015)
)
)
# === QUALITY FILTERS ===
quality_filters = (
# Ultimate score threshold
(dataframe['ultimate_score'] > self.ultimate_score_threshold.value) &
# Signal strength
(dataframe['signal_strength'] >= 5) &
# No extreme RSI
(dataframe['rsi'] < 80) &
# Positive momentum
(dataframe['price_momentum'] > -0.02) &
# Volume not declining
(dataframe['volume_trend'] == 1) |
(dataframe['volume_strength'] > 1.1)
)
# === RISK MANAGEMENT CONDITIONS ===
risk_conditions = (
# No consecutive red candles
(dataframe['consecutive_red'] <= 2) &
# ATR not too high (volatility control)
(dataframe['atr'] < dataframe['close'] * 0.05) & # Max 5% ATR
# No strong bearish momentum
(dataframe['strong_down_momentum'] == 0) &
# RSI divergence protection
(dataframe['rsi_divergence_bear'] == 0)
)
# === MARKET REGIME ADAPTATIONS ===
# In ranging markets, require stronger confluence
ranging_market_adjustment = (
(~dataframe['ranging']) | # Not ranging, OR
(dataframe['confluence_score'] >= (self.confluence_threshold.value + 1)) # Higher confluence if ranging
)
# In strong trends, allow more aggressive entries
trend_market_adjustment = (
(~dataframe['strong_uptrend']) | # Not in strong uptrend, OR
(dataframe['volume_pressure'] >= 1) # Lower volume requirement in strong trends
)
# === FINAL ENTRY CONDITION COMBINATIONS ===
# HIGH QUALITY ENTRIES (all advanced filters)
high_quality_entry = (
core_conditions &
confluence_conditions &
volume_conditions &
momentum_conditions &
structure_conditions &
extrema_conditions &
quality_filters &
risk_conditions &
ranging_market_adjustment &
trend_market_adjustment
)
# MEDIUM QUALITY ENTRIES (relaxed requirements)
medium_quality_entry = (
core_conditions &
extrema_conditions &
(
# Either strong confluence OR strong momentum OR strong volume
confluence_conditions |
(dataframe['momentum_quality'] >= 4) |
(dataframe['volume_pressure'] >= 3)
) &
# Basic quality filters
(dataframe['ultimate_score'] > (self.ultimate_score_threshold.value * 0.7)) &
(dataframe['signal_strength'] >= 3) &
risk_conditions
)
# BACKUP ENTRIES (original logic only)
backup_entry = (
core_conditions &
extrema_conditions &
(dataframe['volume'] > dataframe['avg_volume']) &
(dataframe['trend_strength'] > 0)
)
# === ADDITIONAL ENTRY SIGNALS FOR SPECIFIC SCENARIOS ===
# Momentum breakout entries
momentum_breakout = (
(dataframe['structure_break_up'] == 1) &
(dataframe['volume_strength'] > 1.5) &
(dataframe['momentum_quality'] >= 4) &
(dataframe['close'] > dataframe['ema50']) &
(dataframe['rsi'] < 75) &
risk_conditions
)
# Volume spike reversals
volume_reversal = (
(dataframe['volume_spike'] == 1) &
(dataframe['rsi_oversold'] == 1) &
(dataframe['near_support'] == 1) &
(dataframe['buying_pressure'] >= 2) &
(dataframe['close'] > dataframe['open']) & # Green candle
risk_conditions
)
# === SHORT LOGIC ===
# CORE SHORT CONDITIONS (mirror of long)
core_short_conditions = (
(dataframe['close'] < dataframe['ema50']) & # Below EMA
(dataframe['rsi'] > 30) & (dataframe['rsi'] < 70) & # Not extreme
(dataframe['trend_strength'] < 0.01) & # Downtrend or neutral
(dataframe['volume'] > dataframe['avg_volume'] * 0.8) &
(dataframe['buying_pressure'] <= 3) # Not too much buying
)
# SHORT CONFLUENCE (inverted)
short_confluence_conditions = (
(dataframe['confluence_score'] >= self.confluence_threshold.value) |
(
(dataframe['confluence_score'] >= (self.confluence_threshold.value - 1)) &
(dataframe['near_resistance'] == 1) & # Near resistance for SHORT
(dataframe['volume_spike'] == 1)
)
)
# SHORT VOLUME CONDITIONS
short_volume_conditions = (
(dataframe['volume_pressure'] <= -self.volume_pressure_threshold.value) & # Negative pressure
(dataframe['volume_strength'] > self.volume_strength_threshold.value) &
(dataframe['money_flow_index'] < 55) # Money flowing out
) if self.require_volume_confirmation.value else True
# SHORT MOMENTUM CONDITIONS
short_momentum_conditions = (
(dataframe['momentum_quality'] <= -2) & # Negative momentum quality
(dataframe['momentum_acceleration'] < 0.01) & # Decelerating
(dataframe['momentum_consistency'] <= 1) # Inconsistent upward momentum
) if self.require_momentum_confirmation.value else True
# SHORT STRUCTURE CONDITIONS
short_structure_conditions = (
(dataframe['structure_score'] <= -self.structure_score_threshold.value) & # Negative structure
(dataframe['bearish_structure'] > dataframe['bullish_structure']) # More bearish signals
) if self.require_structure_confirmation.value else True
# SHORT EXTREMA CONDITIONS (inverted)
short_extrema_conditions = (
# Maxima conditions (resistance for shorts)
(
(dataframe["maxima"] == 1) &
(dataframe["maxima_check"] == 1) &
(dataframe["close"] >= dataframe["maxima_sort_threshold"] * 0.98) &
(dataframe["DI_catch"] == 1)
) |
# MML resistance levels
(
(dataframe["close"] >= dataframe["[6/8]P"] * 0.99) |
(dataframe["close"] >= dataframe["[8/8]P"] * 0.99) |
(dataframe["close"] >= dataframe["[+1/8]P"] * 0.99)
) |
# Rolling maxima
(
(dataframe["close"] >= dataframe["maxh2"] * 0.985) |
(dataframe["close"] >= dataframe["maxh1"] * 0.985) |
(dataframe["close"] >= dataframe["maxh0"] * 0.985)
)
)
# SHORT QUALITY FILTERS
short_quality_filters = (
(dataframe['ultimate_score'] < (1 - self.ultimate_score_threshold.value)) & # Low score for shorts
(dataframe['signal_strength'] <= -3) & # Negative signal strength
(dataframe['rsi'] > 20) & # Not oversold
(dataframe['price_momentum'] < 0.02) & # Negative momentum
(dataframe['volume_trend'] == 0) | # Volume declining
(dataframe['volume_strength'] < 0.9) # Weak volume
)
# SHORT RISK CONDITIONS
short_risk_conditions = (
(dataframe['consecutive_green'] <= 2) & # Not too many green candles
(dataframe['atr'] < dataframe['close'] * 0.05) & # Volatility control
(dataframe['strong_up_momentum'] == 0) & # No strong bullish momentum
(dataframe['rsi_divergence_bull'] == 0) # No bullish divergence
)
# === SHORT ENTRY COMBINATIONS ===
# HIGH QUALITY SHORT ENTRIES
high_quality_short = (
core_short_conditions &
short_confluence_conditions &
short_volume_conditions &
short_momentum_conditions &
short_structure_conditions &
short_extrema_conditions &
short_quality_filters &
short_risk_conditions
)
# MEDIUM QUALITY SHORT ENTRIES
medium_quality_short = (
core_short_conditions &
short_extrema_conditions &
(
short_confluence_conditions |
(dataframe['momentum_quality'] <= -3) |
(dataframe['volume_pressure'] <= -3)
) &
short_risk_conditions &
~(
(dataframe['close'] > dataframe['ema50']) | # Exclude if above EMA50 (long condition)
(dataframe['trend_strength'] > 0.01) | # Exclude if in uptrend
(dataframe['rsi'] < 30) | # Exclude if oversold (long bias)
(dataframe['ultimate_score'] > self.ultimate_score_threshold.value) # Exclude high-quality long setups
)
)
# === ENTRY PRIORITY SYSTEM ===
# Initialize entry type column
dataframe['entry_type'] = 0
# === APPLY LONG ENTRIES ===
# HIGH QUALITY LONG
dataframe.loc[high_quality_entry, "enter_long"] = 1
dataframe.loc[high_quality_entry, 'entry_type'] = 3
dataframe.loc[high_quality_entry, "enter_tag"] = "high_quality_long"
# MEDIUM QUALITY LONG
dataframe.loc[medium_quality_entry & ~high_quality_entry, "enter_long"] = 1
dataframe.loc[medium_quality_entry & ~high_quality_entry, 'entry_type'] = 2
dataframe.loc[medium_quality_entry & ~high_quality_entry, "enter_tag"] = "medium_quality_long"
# BACKUP LONG
dataframe.loc[backup_entry & ~(high_quality_entry | medium_quality_entry), "enter_long"] = 1
dataframe.loc[backup_entry & ~(high_quality_entry | medium_quality_entry), 'entry_type'] = 1
dataframe.loc[backup_entry & ~(high_quality_entry | medium_quality_entry), "enter_tag"] = "backup_long"
# BREAKOUT LONG
dataframe.loc[momentum_breakout, "enter_long"] = 1
dataframe.loc[momentum_breakout, 'entry_type'] = 4
dataframe.loc[momentum_breakout, "enter_tag"] = "breakout_long"
# REVERSAL LONG
dataframe.loc[volume_reversal, "enter_long"] = 1
dataframe.loc[volume_reversal, 'entry_type'] = 5
dataframe.loc[volume_reversal, "enter_tag"] = "reversal_long"
# === APPLY SHORT ENTRIES ===
dataframe.loc[high_quality_short, "enter_short"] = 1
dataframe.loc[high_quality_short, 'entry_type'] = 6 # High quality short
dataframe.loc[high_quality_short, "enter_tag"] = "high_quality_short"
dataframe.loc[
medium_quality_short & ~high_quality_short,
"enter_short"
] = 1
dataframe.loc[
medium_quality_short & ~high_quality_short,
'entry_type'
] = 7 # Medium quality short
dataframe.loc[
medium_quality_short & ~high_quality_short,
"enter_tag"
] = "medium_quality_short"
# === ENTRY DEBUGGING ===
# Log entry signals for major pairs
if metadata['pair'] in ['BTC/USDT:USDT', 'ETH/USDT:USDT']:
recent_entries = dataframe['enter_long'].tail(5).sum() + dataframe['enter_short'].tail(5).sum()
if recent_entries > 0:
entry_type = dataframe['entry_type'].iloc[-1]
entry_types = {0: 'None', 1: 'Backup', 2: 'Medium', 3: 'High', 4: 'Breakout', 5: 'Reversal', 6: 'High Short', 7: 'Medium Short'}
logger.info(f"{metadata['pair']} ENTRY SIGNAL - Type: {entry_types.get(entry_type, 'Unknown')}")
logger.info(f" Ultimate Score: {dataframe['ultimate_score'].iloc[-1]:.3f}")
logger.info(f" Signal Strength: {dataframe['signal_strength'].iloc[-1]}")
logger.info(f" Confluence Score: {dataframe['confluence_score'].iloc[-1]}")
return dataframe
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
UNIFIED EXIT SYSTEM - Choose between Custom MML Exits or Simple Opposite Signal Exits
"""
# ===========================================
# INITIALIZE EXIT COLUMNS
# ===========================================
dataframe["exit_long"] = 0
dataframe["exit_short"] = 0
dataframe["exit_tag"] = ""
# ===========================================
# CHOOSE EXIT SYSTEM
# ===========================================
if self.use_custom_exits_advanced:
# Use Alex's Advanced MML-based Exit System
return self._populate_custom_exits_advanced(dataframe, metadata)
else:
# Use Simple Opposite Signal Exit System
return self._populate_simple_exits(dataframe, metadata)
def _populate_custom_exits_advanced(self, df: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
ALEX'S ADVANCED MML-BASED EXIT SYSTEM
Profit-protecting exit strategy with better signal coordination
"""
# ===========================================
# MML MARKET STRUCTURE FOR EXITS
# ===========================================
# Bullish/Bearish structure (same as entry)
bullish_mml = (
(df["close"] > df["[6/8]P"]) |
((df["close"] > df["[4/8]P"]) & (df["close"].shift(5) < df["[4/8]P"].shift(5)))
)
bearish_mml = (
(df["close"] < df["[2/8]P"]) |
((df["close"] < df["[4/8]P"]) & (df["close"].shift(5) > df["[4/8]P"].shift(5)))
)
# MML resistance/support levels for exits
at_resistance = (
(df["high"] >= df["[6/8]P"]) | # At 75%
(df["high"] >= df["[7/8]P"]) | # At 87.5%
(df["high"] >= df["[8/8]P"]) # At 100%
)
at_support = (
(df["low"] <= df["[2/8]P"]) | # At 25%
(df["low"] <= df["[1/8]P"]) | # At 12.5%
(df["low"] <= df["[0/8]P"]) # At 0%
)
# ===========================================
# LONG EXIT SIGNALS (ADVANCED MML SYSTEM)
# ===========================================
# 1. Profit-Taking Exits
long_exit_resistance_profit = (
at_resistance &
(df["close"] < df["high"]) & # Failed to close at high
(df["rsi"] > 65) & # Overbought
(df["maxima"] == 1) & # Local top
(df["volume"] > df["volume"].rolling(10).mean())
)
long_exit_extreme_overbought = (
(df["close"] > df["[7/8]P"]) &
(df["rsi"] > 75) &
(df["close"] < df["close"].shift(1)) & # Price turning down
(df["maxima"] == 1)
)
long_exit_volume_exhaustion = (
at_resistance &
(df["volume"] < df["volume"].rolling(20).mean() * 0.6) & # Tightened from 0.8
(df["rsi"] > 70) &
(df["close"] < df["close"].shift(1)) &
(df["close"] < df["close"].rolling(3).mean()) # Added price confirmation
)
# 2. Structure Breakdown (Improved with strong filters)
long_exit_structure_breakdown = (
(df["close"] < df["[4/8]P"]) &
(df["close"].shift(1) >= df["[4/8]P"].shift(1)) &
bullish_mml.shift(1) &
(df["close"] < df["[4/8]P"] * 0.995) &
(df["close"] < df["close"].shift(1)) &
(df["close"] < df["close"].shift(2)) &
(df["rsi"] < 45) & # Tightened from 50
(df["volume"] > df["volume"].rolling(15).mean() * 2.0) & # Increased from 1.5
(df["close"] < df["open"]) &
(df["low"] < df["low"].shift(1)) &
(df["close"] < df["close"].rolling(3).mean()) &
(df["momentum_quality"] < 0) # Added momentum check
)
# 3. Momentum Divergence
long_exit_momentum_divergence = (
at_resistance &
(df["rsi"] < df["rsi"].shift(1)) & # RSI falling
(df["rsi"].shift(1) < df["rsi"].shift(2)) & # RSI was falling
(df["rsi"] < df["rsi"].shift(3)) & # 3-candle RSI decline
(df["close"] >= df["close"].shift(1)) & # Price still up/flat
(df["maxima"] == 1) &
(df["rsi"] > 60) # Only in overbought territory
)
# 4. Range Exit
long_exit_range = (
(df["close"] >= df["[2/8]P"]) &
(df["close"] <= df["[6/8]P"]) & # In range
(df["high"] >= df["[6/8]P"]) & # HIGH touched 75%, not close
(df["close"] < df["[6/8]P"] * 0.995) & # But closed below
(df["rsi"] > 65) & # More conservative RSI
(df["maxima"] == 1) &
(df["volume"] > df["volume"].rolling(10).mean() * 1.2) # Volume confirmation
)
# 5. Emergency Exit
long_exit_emergency = (
(df["close"] < df["[0/8]P"]) &
(df["rsi"] < 20) & # Changed from 15
(df["volume"] > df["volume"].rolling(20).mean() * 2.5) & # Reduced from 3
(df["close"] < df["close"].shift(1)) &
(df["close"] < df["close"].shift(2)) &
(df["close"] < df["open"])
) if self.use_emergency_exits else pd.Series([False] * len(df), index=df.index)
# Combine all Long Exit signals
any_long_exit = (
long_exit_resistance_profit |
long_exit_extreme_overbought |
long_exit_volume_exhaustion |
long_exit_structure_breakdown |
long_exit_momentum_divergence |
long_exit_range |
long_exit_emergency
)
# ===========================================
# SHORT EXIT SIGNALS (if enabled)
# ===========================================
if self.can_short:
# 1. Profit-Taking Exits
short_exit_support_profit = (
at_support &
(df["close"] > df["low"]) & # Failed to close at low
(df["rsi"] < 35) & # Oversold
(df["minima"] == 1) & # Local bottom
(df["volume"] > df["volume"].rolling(10).mean())
)
short_exit_extreme_oversold = (
(df["close"] < df["[1/8]P"]) &
(df["rsi"] < 25) &
(df["close"] > df["close"].shift(1)) & # Price turning up
(df["minima"] == 1)
)
short_exit_volume_exhaustion = (
at_support &
(df["volume"] < df["volume"].rolling(20).mean() * 0.6) & # Tightened from 0.8
(df["rsi"] < 30) &
(df["close"] > df["close"].shift(1)) &
(df["close"] > df["close"].rolling(3).mean()) # Added price confirmation
)
# 2. Structure Breakout
short_exit_structure_breakout = (
(df["close"] > df["[4/8]P"]) &
(df["close"].shift(1) <= df["[4/8]P"].shift(1)) &
bearish_mml.shift(1) &
(df["close"] > df["[4/8]P"] * 1.005) &
(df["close"] > df["close"].shift(1)) &
(df["close"] > df["close"].shift(2)) &
(df["rsi"] > 55) & # Tightened from 50
(df["volume"] > df["volume"].rolling(15).mean() * 2.0) & # Increased from 1.5
(df["close"] > df["open"]) &
(df["high"] > df["high"].shift(1)) &
(df["momentum_quality"] > 0) # Added momentum check
)
# 3. Momentum Divergence
short_exit_momentum_divergence = (
at_support &
(df["rsi"] > df["rsi"].shift(1)) & # RSI rising
(df["rsi"].shift(1) > df["rsi"].shift(2)) & # RSI was rising
(df["rsi"] > df["rsi"].shift(3)) & # 3-candle RSI rise
(df["close"] <= df["close"].shift(1)) & # Price still down/flat
(df["minima"] == 1) &
(df["rsi"] < 40) # Only in oversold territory
)
# 4. Range Exit
short_exit_range = (
(df["close"] >= df["[2/8]P"]) &
(df["close"] <= df["[6/8]P"]) & # In range
(df["low"] <= df["[2/8]P"]) & # LOW touched 25%
(df["close"] > df["[2/8]P"] * 1.005) & # But closed above
(df["rsi"] < 35) & # More conservative RSI
(df["minima"] == 1) &
(df["volume"] > df["volume"].rolling(10).mean() * 1.2) # Volume confirmation
)
# 5. Emergency Exit
short_exit_emergency = (
(df["close"] > df["[8/8]P"]) &
(df["rsi"] > 80) & # Changed from 85
(df["volume"] > df["volume"].rolling(20).mean() * 2.5) & # Reduced from 3
(df["close"] > df["close"].shift(1)) &
(df["close"] > df["close"].shift(2)) &
(df["close"] > df["open"])
) if self.use_emergency_exits else pd.Series([False] * len(df), index=df.index)
# Combine all Short Exit signals
any_short_exit = (
short_exit_support_profit |
short_exit_extreme_oversold |
short_exit_volume_exhaustion |
short_exit_structure_breakout |
short_exit_momentum_divergence |
short_exit_range |
short_exit_emergency
)
else:
any_short_exit = pd.Series([False] * len(df), index=df.index)
# ===========================================
# COORDINATION WITH ENTRY SIGNALS
# ===========================================
# If we have new Entry signals, they override Exit signals
has_long_entry = "enter_long" in df.columns and (df["enter_long"] == 1).any()
has_short_entry = "enter_short" in df.columns and (df["enter_short"] == 1).any()
if has_long_entry:
long_entry_mask = df["enter_long"] == 1
any_long_exit = any_long_exit & (~long_entry_mask)
if has_short_entry and self.can_short:
short_entry_mask = df["enter_short"] == 1
any_short_exit = any_short_exit & (~short_entry_mask)
# ===========================================
# SET FINAL EXIT SIGNALS AND TAGS
# ===========================================
# Long Exits
df.loc[any_long_exit, "exit_long"] = 1
# Tags for Long Exits (Priority: Emergency > Structure > Profit)
df.loc[any_long_exit & long_exit_emergency, "exit_tag"] = "MML_Emergency_Long_Exit"
df.loc[any_long_exit & long_exit_structure_breakdown & (df["exit_tag"] == ""), "exit_tag"] = "MML_Structure_Breakdown_Confirmed"
df.loc[any_long_exit & long_exit_resistance_profit & (df["exit_tag"] == ""), "exit_tag"] = "MML_Resistance_Profit"
df.loc[any_long_exit & long_exit_extreme_overbought & (df["exit_tag"] == ""), "exit_tag"] = "MML_Extreme_Overbought"
df.loc[any_long_exit & long_exit_volume_exhaustion & (df["exit_tag"] == ""), "exit_tag"] = "MML_Volume_Exhaustion_Long"
df.loc[any_long_exit & long_exit_momentum_divergence & (df["exit_tag"] == ""), "exit_tag"] = "MML_Momentum_Divergence_Long"
df.loc[any_long_exit & long_exit_range & (df["exit_tag"] == ""), "exit_tag"] = "MML_Range_Exit_Long"
# Short Exits
if self.can_short:
df.loc[any_short_exit, "exit_short"] = 1
# Tags for Short Exits (Priority: Emergency > Structure > Profit)
df.loc[any_short_exit & short_exit_emergency, "exit_tag"] = "MML_Emergency_Short_Exit"
df.loc[any_short_exit & short_exit_structure_breakout & (df["exit_tag"] == ""), "exit_tag"] = "MML_Structure_Breakout_Confirmed"
df.loc[any_short_exit & short_exit_support_profit & (df["exit_tag"] == ""), "exit_tag"] = "MML_Support_Profit"
df.loc[any_short_exit & short_exit_extreme_oversold & (df["exit_tag"] == ""), "exit_tag"] = "MML_Extreme_Oversold"
df.loc[any_short_exit & short_exit_volume_exhaustion & (df["exit_tag"] == ""), "exit_tag"] = "MML_Volume_Exhaustion_Short"
df.loc[any_short_exit & short_exit_momentum_divergence & (df["exit_tag"] == ""), "exit_tag"] = "MML_Momentum_Divergence_Short"
df.loc[any_short_exit & short_exit_range & (df["exit_tag"] == ""), "exit_tag"] = "MML_Range_Exit_Short"
return df
def _populate_simple_exits(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
SIMPLE OPPOSITE SIGNAL EXIT SYSTEM - SYNTAX FIXED
"""
# Exit LONG when any SHORT signal appears
long_exit_on_short = (dataframe["enter_short"] == 1)
# Exit SHORT when any LONG signal appears
short_exit_on_long = (dataframe["enter_long"] == 1)
# Emergency exits (if enabled)
if self.use_emergency_exits:
emergency_long_exit = (
(dataframe['rsi'] > 85) &
(dataframe['volume'] > dataframe['avg_volume'] * 3) &
(dataframe['close'] < dataframe['open']) &
(dataframe['close'] < dataframe['low'].shift(1))
) | (
(dataframe.get('structure_break_down', 0) == 1) &
(dataframe['volume'] > dataframe['avg_volume'] * 2.5) &
(dataframe['atr'] > dataframe['atr'].rolling(20).mean() * 2)
)
emergency_short_exit = (
(dataframe['rsi'] < 15) &
(dataframe['volume'] > dataframe['avg_volume'] * 3) &
(dataframe['close'] > dataframe['open']) &
(dataframe['close'] > dataframe['high'].shift(1))
) | (
(dataframe.get('structure_break_up', 0) == 1) &
(dataframe['volume'] > dataframe['avg_volume'] * 2.5) &
(dataframe['atr'] > dataframe['atr'].rolling(20).mean() * 2)
)
else:
emergency_long_exit = pd.Series([False] * len(dataframe), index=dataframe.index)
emergency_short_exit = pd.Series([False] * len(dataframe), index=dataframe.index)
# Apply exits
dataframe.loc[long_exit_on_short, "exit_long"] = 1
dataframe.loc[long_exit_on_short, "exit_tag"] = "trend_reversal"
dataframe.loc[short_exit_on_long, "exit_short"] = 1
dataframe.loc[short_exit_on_long, "exit_tag"] = "trend_reversal"
# Emergency exits
dataframe.loc[emergency_long_exit & ~long_exit_on_short, "exit_long"] = 1
dataframe.loc[emergency_long_exit & ~long_exit_on_short, "exit_tag"] = "emergency_exit"
dataframe.loc[emergency_short_exit & ~short_exit_on_long, "exit_short"] = 1
dataframe.loc[emergency_short_exit & ~short_exit_on_long, "exit_tag"] = "emergency_exit"
# DEBUGGING (FIXED THE ERROR HERE)
if metadata['pair'] in ['BTC/USDT:USDT', 'ETH/USDT:USDT']:
recent_exits = dataframe['exit_long'].tail(5).sum() + dataframe['exit_short'].tail(5).sum()
if recent_exits > 0:
exit_tag = dataframe['exit_tag'].iloc[-1]
logger.info(f"{metadata['pair']} EXIT SIGNAL - Tag: {exit_tag}")
# ✅ FIXED: Use the correct attribute name
logger.info(f" Exit System: {'Custom MML' if self.use_custom_exits_advanced else 'Simple Opposite'}")
logger.info(f" RSI: {dataframe['rsi'].iloc[-1]:.1f}")
return dataframe
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:
current_profit_ratio = trade.calc_profit_ratio(rate)
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600 # Hours
always_allow = [
"stoploss", "stop_loss", "custom_stoploss",
"roi", "trend_reversal", "emergency_exit"
]
# Allow regime protection exits
if any(char in exit_reason for char in ["⚡", "🔊", "🌊", "🎯", "₿"]):
return True
# Allow known good exits
if exit_reason in always_allow:
return True
# Allow trailing stop only if in profit
if exit_reason in ["trailing_stop_loss", "trailing_stop"]:
if current_profit_ratio > 0: # Only allow trailing if trade is profitable
logger.info(f"{pair} Allowing trailing stop exit. Profit: {current_profit_ratio:.2%}")
return True
else:
logger.info(f"{pair} Blocking trailing stop exit. Trade not in profit: {current_profit_ratio:.2%}")
return False
# Allow timeout exit after 48 hours
if trade_duration > 48:
logger.info(f"{pair} Forcing exit due to max holding time (48h). Profit: {current_profit_ratio:.2%}")
return True
# Allow all other exits
return True