# source: https://raw.githubusercontent.com/m0rfy/freqtrade/4b808aaa7f7d31b12967be5f4155e660b6b5fccd/user_data/strategies/BestStrategy.py
from freqtrade.strategy import IStrategy, informative, merge_informative_pair  # type: ignore
from pandas import DataFrame  # type: ignore
import talib.abstract as ta  # type: ignore
import pandas as pd  # type: ignore
import numpy as np  # type: ignore
from datetime import datetime, timedelta, timezone
from freqtrade.persistence import Trade  # type: ignore
from freqtrade.exchange import timeframe_to_minutes  # type: ignore
from typing import Dict, List, Optional, Tuple, Union, Any
import math
import json
from pathlib import Path
import logging

logger = logging.getLogger(__name__)


class Github_m0rfy_freqtrade__BestStrategy__20251120_195924(IStrategy):
    INTERFACE_VERSION = 3
    can_short = True
    position_adjustment_enable = True
    process_only_new_candles = False
    startup_candle_count: int = 100
    use_custom_stoploss = False
    liquidation_buffer = 0.0005

    # --- Общие настройки ---
    minimal_roi = {"0": 0.01}
    stoploss = -99.0
    use_exit_signal = False
    exit_profit_only = True
    ignore_roi_if_entry_signal = False
    timeframe = '5m'

    # --- Настройки режимов ---
    TRADING_MODE = 'custom'  # 'scalping' или 'custom'

    # --- Настройки индикаторов ---
    USE_BOLLINGER_BANDS = True
    USE_HTF_BOLLINGER_BANDS = True
    USE_TREND_FILTER = False
    bb_period = 12
    bb_std = 2.0
    breakout_threshold = 0.01
    min_candle_size = 0.01
    ema_fast_period = 21
    ema_slow_period = 50
    rsi_period = 14
    rsi_oversold = 30
    rsi_overbought = 70
    rsi_oversold_soft = 40
    rsi_overbought_soft = 60

    # --- Настройки трендового фильтра ---
    USE_TREND_EMA_PULLBACK = False
    USE_TREND_RSI_OVERSOLD = False
    USE_TREND_SMA_PULLBACK = False
    USE_TREND_ADX_STRONG = False

    # --- Параметры для индикаторов тренда ---
    sma_short_period = 50
    sma_long_period = 200
    adx_period = 14
    adx_threshold = 25

    # --- DCA ---
    DCA_LEVELS_ENABLED = True
    ADAPTIVE_DCA_LOSS_ENABLED = True
    DCA_MIN_LOSS_START = 0.10
    DCA_MIN_LOSS_INCREMENT = 0.10
    DCA_SIZE_MULTIPLIER = 2.0
    MIN_DCA_TIME_SECONDS = 180
    LEVERAGE_TIME_MULTIPLIER = 1.1

    # --- Настройки управления капиталом ---
    AUTO_POSITION_SIZE_RATIO_ENABLED = True
    ADAPTIVE_MAX_TRADES_ENABLED = True
    MAX_OPEN_TRADES_DEFAULT = 30
    POSITION_SIZE_BALANCE_RATIO = 0.02
    MIN_ORDER_SIZE_PERCENT = 0.005
    MAX_ORDER_SIZE_PERCENT = 0.10
    ABSOLUTE_MIN_ORDER = 5.0
    ABSOLUTE_MAX_ORDER = 10000.0
    FIRST_ENTRY_RATIO = 0.08
    DCA_BUDGET_RATIO = 0.80

    # --- Настройки динамического MAX_OPEN_TRADES ---
    MIN_TRADES_FOR_CRITICAL_USAGE = 5
    MIN_TRADES_FOR_VERY_HIGH_USAGE = 10
    MIN_TRADES_FOR_HIGH_USAGE = 15
    MIN_TRADES_FOR_MEDIUM_USAGE = 20
    MIN_TRADES_FOR_LOW_USAGE = 30
    CAPITAL_USAGE_CRITICAL_THRESHOLD = 0.90
    CAPITAL_USAGE_VERY_HIGH_THRESHOLD = 0.82
    CAPITAL_USAGE_HIGH_THRESHOLD = 0.75
    CAPITAL_USAGE_MEDIUM_THRESHOLD = 0.60
    CAPITAL_USAGE_LOW_THRESHOLD = 0.40

    # --- Настройки плеча ---
    ADAPTIVE_LEVERAGE_ENABLED = False
    MAX_LEVERAGE = 4.0

    # --- Настройки защиты от ликвидации ---
    LIQUIDATION_PROTECTION_ENABLED = True
    REGULAR_DCA_COUNT = 0
    LIQUIDATION_DANGER_THRESHOLD = 0.20
    LIQUIDATION_EMERGENCY_THRESHOLD = 0.15
    LIQUIDATION_LAST_CHANCE_THRESHOLD = 0.10
    EMERGENCY_DCA_MULTIPLIERS = [1.5, 1.7, 2.0]
    # -1 для безлимита, 0 для отключения, N для конкретного количества
    EMERGENCY_DCA_COUNT = 3
    # --- Настройки динамических интервалов экстренных докупок (в секундах) ---
    EMERGENCY_DCA_TIME_LEVEL_1_SECONDS = 30
    EMERGENCY_DCA_TIME_LEVEL_2_SECONDS = 20
    EMERGENCY_DCA_TIME_LEVEL_3_SECONDS = 10

    # --- Настройки трейлинг входа ---
    TRAIL_ENTRY_ENABLED = True
    INSTANT_ENTRY_MODE = False
    TRAIL_BEST_PRICE_UPDATE_PERCENT = 0.005
    TRAIL_ENTRY_TRIGGER_PERCENT = 0.0025
    TRAIL_ENTRY_CONFIRMATION_TICKS = 2
    HV_TRAIL_BEST_PRICE_UPDATE_PERCENT = 0.01
    HV_TRAIL_ENTRY_TRIGGER_PERCENT = 0.0049
    HV_TRAIL_ENTRY_CONFIRMATION_TICKS = 2
    EXTREME_TRAIL_ENTRY_CONFIRMATION_TICKS = 1
    TRAIL_ENTRY_MAX_TIME = 150
    MAX_SIGNAL_CANDLE_SIZE_PERCENT = 5.0

    # --- Настройки адаптивного трейлинг-входа ---
    USE_ADAPTIVE_TRAILING_ENTRY = True
    # --- Настройки индикаторов HTF (15m) ---
    BB_PERIOD_15M = 12
    BB_STD_15M = 2.0
    EMA_FAST_PERIOD_15M = 21
    EMA_SLOW_PERIOD_15M = 50
    RSI_PERIOD_15M = 14
    RSI_OVERSOLD_SOFT_15M = 40
    RSI_OVERBOUGHT_SOFT_15M = 60
    SMA_SHORT_PERIOD_15M = 50
    SMA_LONG_PERIOD_15M = 200
    ADX_PERIOD_15M = 14
    ADX_THRESHOLD_15M = 25
    HTF_TRAIL_BB_BREAKOUT_THRESHOLD_15M = 0.01

    # --- Настройки индикаторов HTF (30m) ---
    BB_PERIOD_30M = 12
    BB_STD_30M = 2.0
    EMA_FAST_PERIOD_30M = 21
    EMA_SLOW_PERIOD_30M = 50
    RSI_PERIOD_30M = 14
    RSI_OVERSOLD_SOFT_30M = 40
    RSI_OVERBOUGHT_SOFT_30M = 60
    SMA_SHORT_PERIOD_30M = 50
    SMA_LONG_PERIOD_30M = 200
    ADX_PERIOD_30M = 14
    ADX_THRESHOLD_30M = 25
    HTF_TRAIL_BB_BREAKOUT_THRESHOLD_30M = 0.02

    # --- Настройки индикаторов HTF (1h) ---
    BB_PERIOD_1H = 12
    BB_STD_1H = 2.0
    EMA_FAST_PERIOD_1H = 21
    EMA_SLOW_PERIOD_1H = 50
    RSI_PERIOD_1H = 14
    RSI_OVERSOLD_SOFT_1H = 40
    RSI_OVERBOUGHT_SOFT_1H = 60
    SMA_SHORT_PERIOD_1H = 50
    SMA_LONG_PERIOD_1H = 200
    ADX_PERIOD_1H = 14
    ADX_THRESHOLD_1H = 25
    HTF_TRAIL_BB_BREAKOUT_THRESHOLD_1H = 0.03

    # --- Настройки индикаторов HTF (4h) ---
    BB_PERIOD_4H = 12
    BB_STD_4H = 2.0
    EMA_FAST_PERIOD_4H = 21
    EMA_SLOW_PERIOD_4H = 50
    RSI_PERIOD_4H = 14
    RSI_OVERSOLD_SOFT_4H = 40
    RSI_OVERBOUGHT_SOFT_4H = 60
    SMA_SHORT_PERIOD_4H = 50
    SMA_LONG_PERIOD_4H = 200
    ADX_PERIOD_4H = 14
    ADX_THRESHOLD_4H = 25
    HTF_TRAIL_BB_BREAKOUT_THRESHOLD_4H = 0.04

    # --- Параметры трейлинга при экстриме на HTF ---
    HTF_EXTREME_TRAIL_TRIGGER_PERCENT = 0.002
    HTF_EXTREME_TRAIL_CONFIRMATION_TICKS = 1

    # --- Настройки подтверждения DCA ---
    DCA_CONFIRMATION_LOOKBACK = 3

    # --- Настройки ценовых буферов ---
    ENTRY_PRICE_BUFFER = 0.002
    EXIT_PRICE_BUFFER = 0.002
    DCA_PRICE_BUFFER = 0.0001

    # --- Настройки авто-планирования DCA ---
    AUTO_DCA_PLANNING_ENABLED = True

    # --- Настройки инфо-таймфреймов ---
    USE_INFORMATIVE_15m = True
    USE_INFORMATIVE_30m = True
    USE_INFORMATIVE_1h = True
    USE_INFORMATIVE_4h = True
    USE_HTF_ENTRY_FILTER = False
    MIN_NET_PROFIT_PERCENT = 1.0

    # --- Настройки фильтра по ставкам финансирования ---
    USE_FUNDING_RATE_FILTER = True
    MAX_FUNDING_RATE_THRESHOLD = 0.00075

    # --- Флаги для отключения фильтров на основном таймфрейме ---
    DISABLE_MAIN_EMA_PULLBACK = True
    DISABLE_MAIN_RSI_OVERSOLD = True
    DISABLE_MAIN_SMA_PULLBACK = True
    DISABLE_MAIN_ADX_STRONG = True

    def get_adaptive_dca_config(self) -> dict:

        regular_dca = self.REGULAR_DCA_COUNT
        emergency_dca = self.EMERGENCY_DCA_COUNT

        total_dca = -1
        if regular_dca != -1 and emergency_dca != -1:
            total_dca = regular_dca + emergency_dca

        return {
            'total_dca': total_dca,
            'regular_dca': regular_dca,
            'emergency_dca': emergency_dca,
            'config_source': None
        }

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.dca_plans = {}
        self.last_emergency_dca_time = {}
        self.last_dca_time = {}
        self.stats = {'total_entries': 0, 'total_exits': 0,
                      'total_dca': 0, 'emergency_dca': 0, 'signals_checked': 0}
        self.bybit_maintenance_margin_rates = {'default': 0.004}
        self.trailing_entry_data = {}
        self.pending_trailing_init = {}
        self.signal_price_tracking = {}
        self.signal_candles = {}
        self.custom_entry_prices = {}
        self.emergency_dca_flags = {}

        dca_growth_factor = self._calculate_dca_growth_factor()
        if dca_growth_factor > 0:
            self.FIRST_ENTRY_RATIO = 1.0 / dca_growth_factor
            logger.info(
                f"Динамически рассчитанный FIRST_ENTRY_RATIO: {self.FIRST_ENTRY_RATIO:.4f} (фактор роста DCA: {dca_growth_factor:.2f})")
        else:
            logger.warning(
                "Не удалось рассчитать фактор роста DCA, FIRST_ENTRY_RATIO остался неизменным.")

        if self.POSITION_SIZE_BALANCE_RATIO > 0 and self.ABSOLUTE_MIN_ORDER > 0 and dca_growth_factor > 0:
            simulated_full_dca_value = self.ABSOLUTE_MIN_ORDER * dca_growth_factor
            total_balance_for_check, _ = self._get_balance_info()
            if total_balance_for_check and total_balance_for_check > 0:
                required_position_ratio = simulated_full_dca_value / total_balance_for_check
                if self.POSITION_SIZE_BALANCE_RATIO < required_position_ratio:
                    logger.warning(
                        f"ВНИМАНИЕ: Параметр POSITION_SIZE_BALANCE_RATIO ({self.POSITION_SIZE_BALANCE_RATIO:.2%}) "
                        f"слишком мал для полного выполнения DCA-плана при ABSOLUTE_MIN_ORDER ({self.ABSOLUTE_MIN_ORDER:.2f} USDT). "
                        f"Потенциальный полный объем сделки: {simulated_full_dca_value:.2f} USDT. "
                        f"Рекомендуемое POSITION_SIZE_BALANCE_RATIO: {required_position_ratio:.2%}"
                    )
            elif self.EMERGENCY_DCA_COUNT == -1:
                logger.info(f"Безлимитное экстренное усреднение активно (EMERGENCY_DCA_COUNT = -1). "
                            f"POSITION_SIZE_BALANCE_RATIO ({self.POSITION_SIZE_BALANCE_RATIO:.2%}) "
                            f"является мягким лимитом. Реальные ограничения: свободный баланс и ABSOLUTE_MAX_ORDER.")

        self._apply_trading_mode()
        self._load_bybit_leverage_tiers()

    def _load_bybit_leverage_tiers(self):
        self.bybit_leverage_tiers = {}
        try:
            file_path = Path(self.config['user_data_dir']).parent / \
                'data' / 'bybit' / 'futures' / 'leverage_tiers_USDT.json'
            if file_path.is_file():
                with open(file_path, 'r') as f:
                    data = json.load(f)
                    for item in data:
                        self.bybit_leverage_tiers[item['symbol']] = sorted(
                            item['leverage_tiers'],
                            key=lambda x: float(x['max_position_value'])
                        )
        except Exception:
            pass

    def _calculate_dca_growth_factor(self) -> float:
        initial_entry_stake_unit = 1.0
        total_stake_multiplier = initial_entry_stake_unit
        if self.REGULAR_DCA_COUNT > 0:
            current_dca_unit_size = initial_entry_stake_unit
            for i in range(self.REGULAR_DCA_COUNT):
                current_dca_unit_size *= self.DCA_SIZE_MULTIPLIER
                total_stake_multiplier += current_dca_unit_size

        elif self.EMERGENCY_DCA_COUNT > 0:
            last_regular_dca_unit = initial_entry_stake_unit
            if self.REGULAR_DCA_COUNT > 0:
                last_regular_dca_unit *= (self.DCA_SIZE_MULTIPLIER **
                                          self.REGULAR_DCA_COUNT)

            for i in range(self.EMERGENCY_DCA_COUNT):
                emergency_multiplier = self.EMERGENCY_DCA_MULTIPLIERS[min(
                    i, len(self.EMERGENCY_DCA_MULTIPLIERS) - 1)]
                total_stake_multiplier += last_regular_dca_unit * emergency_multiplier

        return total_stake_multiplier if total_stake_multiplier > 0 else 1.0

    def _apply_trading_mode(self) -> None:
        if self.TRADING_MODE == 'scalping':
            self.minimal_roi = {"0": 0.01}
            self.ENTRY_PRICE_BUFFER = 0.0005
            self.EXIT_PRICE_BUFFER = 0.0005
            self.DCA_PRICE_BUFFER = 0.0005
            self.DCA_MIN_LOSS_START = 0.10
            self.DCA_MIN_LOSS_INCREMENT = 0.03
            self.MIN_DCA_TIME_SECONDS = 180
            self.DCA_SIZE_MULTIPLIER = 2.0
            self.MAX_OPEN_TRADES_DEFAULT = 10
            self.TRAIL_ENTRY_ENABLED = True
            self.INSTANT_ENTRY_MODE = False
            self.breakout_threshold = 0.002
            self.min_candle_size = 0.01

    def _populate_trend_indicators(self, dataframe: DataFrame, timeframe: str = '5m') -> DataFrame:
        # --- EMA ---
        ema_fast_period = getattr(
            self, f'EMA_FAST_PERIOD_{timeframe.upper()}', self.ema_fast_period)
        ema_slow_period = getattr(
            self, f'EMA_SLOW_PERIOD_{timeframe.upper()}', self.ema_slow_period)
        if self.USE_TREND_EMA_PULLBACK or self.USE_HTF_ENTRY_FILTER:
            dataframe['ema_fast'] = ta.EMA(
                dataframe, timeperiod=ema_fast_period)
            dataframe['ema_slow'] = ta.EMA(
                dataframe, timeperiod=ema_slow_period)
        # --- SMA ---
        sma_short_period = getattr(
            self, f'SMA_SHORT_PERIOD_{timeframe.upper()}', self.sma_short_period)
        sma_long_period = getattr(
            self, f'SMA_LONG_PERIOD_{timeframe.upper()}', self.sma_long_period)
        if self.USE_TREND_SMA_PULLBACK:
            dataframe['sma_short'] = ta.SMA(
                dataframe, timeperiod=sma_short_period)
            dataframe['sma_long'] = ta.SMA(
                dataframe, timeperiod=sma_long_period)
        # --- RSI ---
        rsi_period = getattr(
            self, f'RSI_PERIOD_{timeframe.upper()}', self.rsi_period)
        if self.USE_TREND_RSI_OVERSOLD:
            dataframe['rsi'] = ta.RSI(dataframe, timeperiod=rsi_period)
        # --- ADX ---
        adx_period = getattr(
            self, f'ADX_PERIOD_{timeframe.upper()}', self.adx_period)
        if self.USE_TREND_ADX_STRONG:
            dataframe['adx'] = ta.ADX(dataframe, timeperiod=adx_period)
        # --- Bollinger Bands ---
        bb_period = getattr(
            self, f'BB_PERIOD_{timeframe.upper()}', self.bb_period)
        bb_std = getattr(self, f'BB_STD_{timeframe.upper()}', self.bb_std)
        if self.USE_BOLLINGER_BANDS:
            bollinger = ta.BBANDS(
                dataframe, timeperiod=bb_period, nbdevup=bb_std, nbdevdn=bb_std)
            dataframe['bb_lower'] = bollinger['lowerband']
            dataframe['bb_middle'] = bollinger['middleband']
            dataframe['bb_upper'] = bollinger['upperband']
        return dataframe

    def informative_pairs(self) -> List[Tuple[str, str]]:
        if not self.USE_HTF_BOLLINGER_BANDS and not self.USE_TREND_FILTER and not self.ADAPTIVE_LEVERAGE_ENABLED:
            return []
        pairs = self.dp.current_whitelist() if self.dp else []
        inf_pairs = []
        for pair in pairs:
            if self.USE_INFORMATIVE_15m:
                inf_pairs.append((pair, '15m'))
            if self.USE_INFORMATIVE_30m:
                inf_pairs.append((pair, '30m'))
            if self.USE_INFORMATIVE_1h:
                inf_pairs.append((pair, '1h'))
            if self.USE_INFORMATIVE_4h:
                inf_pairs.append((pair, '4h'))
        return inf_pairs

    def get_adaptive_dca_loss_level(self, current_dca_count: int) -> float:
        increment = self.DCA_MIN_LOSS_INCREMENT if self.ADAPTIVE_DCA_LOSS_ENABLED else 0.0
        return self.DCA_MIN_LOSS_START + (current_dca_count * increment)

    def populate_indicators_15m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = self._populate_trend_indicators(dataframe, '15m')
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        dataframe['ema_ultra_fast'] = ta.EMA(dataframe, timeperiod=9)
        return dataframe

    def populate_indicators_30m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = self._populate_trend_indicators(dataframe, '30m')
        dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=100)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        stoch_rsi = ta.STOCHRSI(dataframe)
        dataframe['stoch_rsi_k'] = stoch_rsi['fastk']
        dataframe['stoch_rsi_d'] = stoch_rsi['fastd']
        return dataframe

    def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = self._populate_trend_indicators(dataframe, '1h')
        dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=100)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        stoch_rsi = ta.STOCHRSI(dataframe)
        dataframe['stoch_rsi_k'] = stoch_rsi['fastk']
        dataframe['stoch_rsi_d'] = stoch_rsi['fastd']
        return dataframe

    def populate_indicators_4h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = self._populate_trend_indicators(dataframe, '4h')
        dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=100)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        stoch_rsi = ta.STOCHRSI(dataframe)
        dataframe['stoch_rsi_k'] = stoch_rsi['fastk']
        dataframe['stoch_rsi_d'] = stoch_rsi['fastd']
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        inf_timeframes = []
        if self.USE_INFORMATIVE_15m:
            inf_timeframes.append('15m')
        if self.USE_INFORMATIVE_30m:
            inf_timeframes.append('30m')
        if self.USE_INFORMATIVE_1h:
            inf_timeframes.append('1h')
        if self.USE_INFORMATIVE_4h:
            inf_timeframes.append('4h')

        for tf in inf_timeframes:
            inf_df = self.dp.get_pair_dataframe(
                pair=metadata['pair'], timeframe=tf)
            if tf == '15m':
                inf_df = self.populate_indicators_15m(inf_df, metadata)
            if tf == '30m':
                inf_df = self.populate_indicators_30m(inf_df, metadata)
            if tf == '1h':
                inf_df = self.populate_indicators_1h(inf_df, metadata)
            if tf == '4h':
                inf_df = self.populate_indicators_4h(inf_df, metadata)
            dataframe = merge_informative_pair(
                dataframe, inf_df, self.timeframe, tf, ffill=True)

        dataframe = self._populate_trend_indicators(dataframe)

        if self.USE_BOLLINGER_BANDS:
            if self.TRADING_MODE == 'scalping':
                lookback = 3
                dataframe['local_max'] = (dataframe['high'] == dataframe['high'].rolling(
                    window=lookback * 2 + 1, center=True).max())
                dataframe['local_min'] = (dataframe['low'] == dataframe['low'].rolling(
                    window=lookback * 2 + 1, center=True).min())
                dataframe['max_strength'] = dataframe['high'] / \
                    dataframe['high'].rolling(window=lookback).mean() - 1
                dataframe['min_strength'] = 1 - dataframe['low'] / \
                    dataframe['low'].rolling(window=lookback).mean()
            else:
                dataframe['local_max'], dataframe['local_min'], dataframe['max_strength'], dataframe['min_strength'] = False, False, 0, 0

            if self.USE_HTF_BOLLINGER_BANDS:
                bb_signals = {}

                if 'bb_upper' in dataframe.columns and 'bb_lower' in dataframe.columns:
                    bb_spread = dataframe['bb_upper'] - dataframe['bb_lower']
                    bb_pos = np.where(
                        bb_spread != 0, (dataframe['close'] - dataframe['bb_lower']) / bb_spread, 0.5)
                    bb_signals['bb_long_5m'] = np.where(bb_pos < 0.05, 1, 0)
                    bb_signals['bb_short_5m'] = np.where(bb_pos > 0.95, 1, 0)

                for tf in inf_timeframes:
                    if f'bb_upper_{tf}' in dataframe.columns and f'bb_lower_{tf}' in dataframe.columns:
                        bb_spread_tf = dataframe[f'bb_upper_{tf}'] - \
                            dataframe[f'bb_lower_{tf}']

                        htf_trail_bb_breakout_threshold_tf = getattr(
                            self, f'HTF_TRAIL_BB_BREAKOUT_THRESHOLD_{tf.upper()}', 0.05)

                        bb_signals[f'bb_long_{tf}'] = np.where(
                            dataframe['close'] <= dataframe[f'bb_lower_{tf}'] * (1 - htf_trail_bb_breakout_threshold_tf), 1, 0)
                        bb_signals[f'bb_short_{tf}'] = np.where(
                            dataframe['close'] >= dataframe[f'bb_upper_{tf}'] * (1 + htf_trail_bb_breakout_threshold_tf), 1, 0)

                for key, value in bb_signals.items():
                    dataframe[key] = value
        else:
            dataframe[['bb_lower', 'bb_middle', 'bb_upper']] = np.nan
            dataframe[['local_max', 'local_min', 'max_strength',
                       'min_strength']] = [False, False, 0, 0]

        if self.USE_HTF_ENTRY_FILTER:
            trend_data = {}
            if 'ema_fast' in dataframe.columns and 'ema_slow' in dataframe.columns:
                trend_data['htf_trend_5m'] = np.where(
                    dataframe['ema_fast'] > dataframe['ema_slow'], 1, -1)

            for tf in inf_timeframes:
                if f'ema_fast_{tf}' in dataframe.columns and f'ema_slow_{tf}' in dataframe.columns:
                    trend_data[f'htf_trend_{tf}'] = np.where(
                        dataframe[f'ema_fast_{tf}'] > dataframe[f'ema_slow_{tf}'], 1, -1)

            if 'ema_ultra_fast_15m' in dataframe.columns and 'ema_fast' in dataframe.columns:
                trend_data['htf_micro_trend'] = np.where(
                    dataframe['ema_ultra_fast_15m'] > dataframe['ema_fast'], 1, -1)

            for key, value in trend_data.items():
                dataframe[key] = value

        return dataframe

    def check_signals(self, dataframe: DataFrame, pair: str) -> dict:
        last_candle = dataframe.iloc[-1]
        signals = {'bb_long': False, 'bb_short': False,
                   'trend_long': False, 'trend_short': False}
        log_details: Dict[str, Any] = {}

        current_price = last_candle['close']
        if self.dp and self.dp.runmode.value in ('live', 'dry_run'):
            ticker = self.dp.ticker(pair)
            current_price = ticker.get('last', last_candle['close'])

        log_data: Dict[str, Any] = {
            'price': f"{current_price:.4f}",
            'rsi': f"{last_candle.get('rsi', 0):.2f}" if 'rsi' in last_candle else 'N/A',
            'ema_f': f"{last_candle.get('ema_fast', 0):.4f}" if 'ema_fast' in last_candle else 'N/A',
            'ema_s': f"{last_candle.get('ema_slow', 0):.4f}" if 'ema_slow' in last_candle else 'N/A',
            'sma_s': f"{last_candle.get('sma_short', 0):.4f}" if 'sma_short' in last_candle else 'N/A',
            'sma_l': f"{last_candle.get('sma_long', 0):.4f}" if 'sma_long' in last_candle else 'N/A',
            'adx': f"{last_candle.get('adx', 0):.2f}" if 'adx' in last_candle else 'N/A',
            'bbl': f"{last_candle.get('bb_lower', 0):.4f}" if 'bb_lower' in last_candle else 'N/A',
            'bbu': f"{last_candle.get('bb_upper', 0):.4f}" if 'bb_upper' in last_candle else 'N/A'
        }

        inf_tfs_to_log = []
        if self.USE_INFORMATIVE_15m:
            inf_tfs_to_log.append('15m')
        if self.USE_INFORMATIVE_30m:
            inf_tfs_to_log.append('30m')
        if self.USE_INFORMATIVE_1h:
            inf_tfs_to_log.append('1h')
        if self.USE_INFORMATIVE_4h:
            inf_tfs_to_log.append('4h')
        if inf_tfs_to_log:
            log_data['inf'] = {}
            for tf in inf_tfs_to_log:
                log_data['inf'][tf] = {
                    'ema_f': f"{last_candle.get(f'ema_fast_{tf}', 0):.4f}",
                    'ema_s': f"{last_candle.get(f'ema_slow_{tf}', 0):.4f}",
                    'bbl': f"{last_candle.get(f'bb_lower_{tf}', 0):.4f}",
                    'bbu': f"{last_candle.get(f'bb_upper_{tf}', 0):.4f}",
                }

        if self.USE_BOLLINGER_BANDS and 'bb_lower' in last_candle and pd.notna(last_candle['bb_lower']):
            bb_spread = last_candle['bb_upper'] - last_candle['bb_lower']
            if bb_spread > 0:
                long_trigger = last_candle['bb_lower'] * \
                    (1 - self.breakout_threshold)
                short_trigger = last_candle['bb_upper'] * \
                    (1 + self.breakout_threshold)
                signals['bb_long'] = (current_price <= long_trigger)
                signals['bb_short'] = (current_price >= short_trigger)
                log_details['bb_5m'] = {
                    'long': bool(signals['bb_long']),
                    'short': bool(signals['bb_short']),
                    'L': f"{current_price:.4f}<={long_trigger:.4f}",
                    'S': f"{current_price:.4f}>={short_trigger:.4f}"
                }
            else:
                signals['bb_long'], signals['bb_short'] = False, False
                log_details['bb_5m'] = 'no_spread'

            if self.TRADING_MODE == 'scalping':
                scalp_long_cond = (last_candle.get(
                    'local_min', False) or last_candle.get('min_strength', 0) > 0.002)
                scalp_short_cond = (last_candle.get(
                    'local_max', False) or last_candle.get('max_strength', 0) > 0.002)
                signals['bb_long'] &= scalp_long_cond
                signals['bb_short'] &= scalp_short_cond
                log_details['scalp'] = {
                    'long': bool(scalp_long_cond), 'short': bool(scalp_short_cond),
                    'loc_min': last_candle.get('local_min', False),
                    'min_str': f"{last_candle.get('min_strength', 0):.4f}",
                    'loc_max': last_candle.get('local_max', False),
                    'max_str': f"{last_candle.get('max_strength', 0):.4f}"
                }

            if self.USE_HTF_BOLLINGER_BANDS:
                enabled_inf_tfs = []
                if self.USE_INFORMATIVE_15m:
                    enabled_inf_tfs.append('15m')
                if self.USE_INFORMATIVE_30m:
                    enabled_inf_tfs.append('30m')
                if self.USE_INFORMATIVE_1h:
                    enabled_inf_tfs.append('1h')
                if self.USE_INFORMATIVE_4h:
                    enabled_inf_tfs.append('4h')

                if enabled_inf_tfs:
                    htf_long_confirmation_tfs = [
                        tf for tf in enabled_inf_tfs if last_candle.get(f'bb_long_{tf}', 0) > 0]
                    log_details['bb_htf_long'] = htf_long_confirmation_tfs
                    if signals['bb_long'] and not htf_long_confirmation_tfs:
                        signals['bb_long'] = False

                    htf_short_confirmation_tfs = [
                        tf for tf in enabled_inf_tfs if last_candle.get(f'bb_short_{tf}', 0) > 0]
                    log_details['bb_htf_short'] = htf_short_confirmation_tfs
                    if signals['bb_short'] and not htf_short_confirmation_tfs:
                        signals['bb_short'] = False
            else:
                log_details['bb_htf'] = 'disabled'
        else:
            signals['bb_long'], signals['bb_short'] = True, True
            log_details['bb'] = 'disabled'

        trend_confirmations_long = []
        trend_confirmations_short = []
        log_details['trend_filters'] = {}

        if self.USE_TREND_EMA_PULLBACK and not self.DISABLE_MAIN_EMA_PULLBACK:
            ema_fast = last_candle.get('ema_fast', np.nan)
            ema_slow = last_candle.get('ema_slow', np.nan)
            if pd.notna(ema_fast) and pd.notna(ema_slow):
                is_uptrend = ema_fast > ema_slow
                price_vs_ema_long = current_price < ema_fast
                long_cond = is_uptrend and price_vs_ema_long
                short_cond = (not is_uptrend) and (current_price > ema_fast)
                trend_confirmations_long.append(long_cond)
                trend_confirmations_short.append(short_cond)
                log_details['trend_filters']['ema_pullback'] = {
                    'long': long_cond, 'short': short_cond,
                    'L_details': f"up:{is_uptrend}, p<ema:{price_vs_ema_long}",
                    'S_details': f"down:{(not is_uptrend)}, p>ema:{(current_price > ema_fast)}"
                }
            else:
                log_details['trend_filters']['ema_pullback'] = 'data_missing'

        if self.USE_TREND_RSI_OVERSOLD and not self.DISABLE_MAIN_RSI_OVERSOLD:
            rsi = last_candle.get('rsi', np.nan)
            if pd.notna(rsi):
                long_cond = rsi <= self.rsi_oversold_soft
                short_cond = rsi >= self.rsi_overbought_soft
                trend_confirmations_long.append(long_cond)
                trend_confirmations_short.append(short_cond)
                log_details['trend_filters']['rsi_oversold'] = {
                    'long': long_cond, 'short': short_cond, 'val': f"{rsi:.2f}"}
            else:
                log_details['trend_filters']['rsi_oversold'] = 'data_missing'

        if self.USE_TREND_SMA_PULLBACK and not self.DISABLE_MAIN_SMA_PULLBACK:
            sma_short = last_candle.get('sma_short', np.nan)
            sma_long = last_candle.get('sma_long', np.nan)
            if pd.notna(sma_short) and pd.notna(sma_long):
                is_uptrend = sma_short > sma_long
                price_vs_sma_long = current_price < sma_short
                long_cond = is_uptrend and price_vs_sma_long
                short_cond = (not is_uptrend) and (
                    current_price > sma_short)
                trend_confirmations_long.append(long_cond)
                trend_confirmations_short.append(short_cond)
                log_details['trend_filters']['sma_pullback'] = {
                    'long': long_cond, 'short': short_cond,
                    'L_details': f"up:{is_uptrend}, p<sma:{price_vs_sma_long}",
                    'S_details': f"down:{(not is_uptrend)}, p>sma:{(current_price > sma_short)}"
                }
            else:
                log_details['trend_filters']['sma_pullback'] = 'data_missing'

        if self.USE_TREND_ADX_STRONG and not self.DISABLE_MAIN_ADX_STRONG:
            adx = last_candle.get('adx', np.nan)
            if pd.notna(adx):
                long_cond = adx > self.adx_threshold
                short_cond = adx > self.adx_threshold
                trend_confirmations_long.append(long_cond)
                trend_confirmations_short.append(short_cond)
                log_details['trend_filters']['adx_strong'] = {
                    'long': long_cond, 'short': short_cond, 'val': f"{adx:.2f}"}
            else:
                log_details['trend_filters']['adx_strong'] = 'data_missing'

        if not trend_confirmations_long and not trend_confirmations_short:
            log_details['trend_filters']['status'] = 'All main TF trend filters disabled or no data, defaulting to PASS'

        calculated_trend_long = all(
            trend_confirmations_long) if trend_confirmations_long else True
        calculated_trend_short = all(
            trend_confirmations_short) if trend_confirmations_short else True

        if not self.USE_TREND_FILTER:
            signals['trend_long'] = True
            signals['trend_short'] = True
            log_details['trend_master_switch'] = 'OFF (Result Ignored)'
        else:
            signals['trend_long'] = calculated_trend_long
            signals['trend_short'] = calculated_trend_short
            log_details['trend_master_switch'] = 'ON'

        log_details['trend_calculated_result'] = {
            'long': calculated_trend_long, 'short': calculated_trend_short}

        if self.USE_HTF_ENTRY_FILTER:
            enabled_inf_tfs = []
            if self.USE_INFORMATIVE_15m:
                enabled_inf_tfs.append('15m')
            if self.USE_INFORMATIVE_30m:
                enabled_inf_tfs.append('30m')
            if self.USE_INFORMATIVE_1h:
                enabled_inf_tfs.append('1h')
            if self.USE_INFORMATIVE_4h:
                enabled_inf_tfs.append('4h')

            if enabled_inf_tfs:
                htf_confirmations_long = []
                htf_confirmations_short = []
                log_details['trend_htf_details'] = {}

                for tf in enabled_inf_tfs:
                    htf_checks_long = []
                    htf_checks_short = []
                    htf_log = {}

                    if self.USE_TREND_EMA_PULLBACK:
                        ema_fast_tf = last_candle.get(f'ema_fast_{tf}', np.nan)
                        ema_slow_tf = last_candle.get(f'ema_slow_{tf}', np.nan)
                        if pd.notna(ema_fast_tf) and pd.notna(ema_slow_tf):
                            # Используем специфичные для HTF параметры EMA
                            ema_fast_period_tf = getattr(
                                self, f'EMA_FAST_PERIOD_{tf.upper()}', self.ema_fast_period)
                            ema_slow_period_tf = getattr(
                                self, f'EMA_SLOW_PERIOD_{tf.upper()}', self.ema_slow_period)

                            long_cond = ema_fast_tf > ema_slow_tf
                            short_cond = ema_fast_tf < ema_slow_tf
                            htf_checks_long.append(long_cond)
                            htf_checks_short.append(short_cond)
                            htf_log['ema'] = {
                                'long': long_cond, 'short': short_cond}
                        else:
                            htf_log['ema'] = 'data_missing'

                    if self.USE_TREND_RSI_OVERSOLD:
                        rsi_tf = last_candle.get(f'rsi_{tf}', np.nan)
                        if pd.notna(rsi_tf):
                            rsi_oversold_soft_tf = getattr(
                                self, f'RSI_OVERSOLD_SOFT_{tf.upper()}', self.rsi_oversold_soft)
                            rsi_overbought_soft_tf = getattr(
                                self, f'RSI_OVERBOUGHT_SOFT_{tf.upper()}', self.rsi_overbought_soft)
                            long_cond = rsi_tf <= rsi_oversold_soft_tf
                            short_cond = rsi_tf >= rsi_overbought_soft_tf
                            htf_checks_long.append(long_cond)
                            htf_checks_short.append(short_cond)
                            htf_log['rsi'] = {
                                'long': long_cond, 'short': short_cond, 'val': f"{rsi_tf:.2f}"}
                        else:
                            htf_log['rsi'] = 'data_missing'

                    if self.USE_TREND_SMA_PULLBACK:
                        sma_short_tf = last_candle.get(
                            f'sma_short_{tf}', np.nan)
                        sma_long_tf = last_candle.get(f'sma_long_{tf}', np.nan)
                        if pd.notna(sma_short_tf) and pd.notna(sma_long_tf):
                            # Используем специфичные для HTF параметры SMA
                            sma_short_period_tf = getattr(
                                self, f'SMA_SHORT_PERIOD_{tf.upper()}', self.sma_short_period)
                            sma_long_period_tf = getattr(
                                self, f'SMA_LONG_PERIOD_{tf.upper()}', self.sma_long_period)

                            long_cond = sma_short_tf > sma_long_tf
                            short_cond = sma_short_tf < sma_long_tf
                            htf_checks_long.append(long_cond)
                            htf_checks_short.append(short_cond)
                            htf_log['sma'] = {
                                'long': long_cond, 'short': short_cond}
                        else:
                            htf_log['sma'] = 'data_missing'

                    if self.USE_TREND_ADX_STRONG:
                        adx_tf = last_candle.get(f'adx_{tf}', np.nan)
                        if pd.notna(adx_tf):
                            # Используем специфичные для HTF параметры ADX
                            adx_threshold_tf = getattr(
                                self, f'ADX_THRESHOLD_{tf.upper()}', self.adx_threshold)
                            long_cond = adx_tf > adx_threshold_tf
                            short_cond = adx_tf > adx_threshold_tf
                            htf_checks_long.append(long_cond)
                            htf_checks_short.append(short_cond)
                            htf_log['adx'] = {
                                'long': long_cond, 'short': short_cond, 'val': f"{adx_tf:.2f}"}
                        else:
                            htf_log['adx'] = 'data_missing'

                    this_htf_confirms_long = all(
                        htf_checks_long) if htf_checks_long else False
                    this_htf_confirms_short = all(
                        htf_checks_short) if htf_checks_short else False

                    htf_confirmations_long.append(this_htf_confirms_long)
                    htf_confirmations_short.append(this_htf_confirms_short)
                    log_details['trend_htf_details'][tf] = {
                        'result': {'long': this_htf_confirms_long, 'short': this_htf_confirms_short},
                        'checks': htf_log
                    }

                final_htf_long_confirmation = any(htf_confirmations_long)
                final_htf_short_confirmation = any(htf_confirmations_short)

                log_details['trend_htf_final_result'] = {
                    'long': final_htf_long_confirmation, 'short': final_htf_short_confirmation
                }

                if signals['trend_long'] and not final_htf_long_confirmation:
                    signals['trend_long'] = False
                if signals['trend_short'] and not final_htf_short_confirmation:
                    signals['trend_short'] = False
        else:
            log_details['trend_htf'] = 'disabled'

        final_signals = {
            'long': bool(signals.get('bb_long', False) and signals.get('trend_long', False)),
            'short': bool(signals.get('bb_short', False) and signals.get('trend_short', False))
        }

        log_lines = [f"{pair} - Signal Analysis"]
        log_lines.append(
            f"  Data    : Price={log_data['price']}, RSI={log_data['rsi']}, "
            f"EMA(F/S)={log_data['ema_f']}/{log_data['ema_s']}, "
            f"SMA(S/L)={log_data['sma_s']}/{log_data['sma_l']}, "
            f"ADX={log_data['adx']}, BBL/BBU={log_data['bbl']}/{log_data['bbu']}"
        )

        if 'inf' in log_data:
            inf_parts = []
            for tf, data in log_data['inf'].items():
                log_inf_data = {
                    'ema_f': f"{last_candle.get(f'ema_fast_{tf}', 0):.4f}" if f'ema_fast_{tf}' in last_candle else 'N/A',
                    'ema_s': f"{last_candle.get(f'ema_slow_{tf}', 0):.4f}" if f'ema_slow_{tf}' in last_candle else 'N/A',
                    'sma_s': f"{last_candle.get(f'sma_short_{tf}', 0):.4f}" if f'sma_short_{tf}' in last_candle else 'N/A',
                    'sma_l': f"{last_candle.get(f'sma_long_{tf}', 0):.4f}" if f'sma_long_{tf}' in last_candle else 'N/A',
                    'adx': f"{last_candle.get(f'adx_{tf}', 0):.2f}" if f'adx_{tf}' in last_candle else 'N/A',
                    'bbl': f"{last_candle.get(f'bb_lower_{tf}', 0):.4f}" if f'bb_lower_{tf}' in last_candle else 'N/A',
                    'bbu': f"{last_candle.get(f'bb_upper_{tf}', 0):.4f}" if f'bb_upper_{tf}' in last_candle else 'N/A'
                }
                inf_parts.append(
                    f"{tf}:[E_F/S:{log_inf_data['ema_f']}/{log_inf_data['ema_s']}, "
                    f"S_S/L:{log_inf_data['sma_s']}/{log_inf_data['sma_l']}, "
                    f"ADX:{log_inf_data['adx']}, "
                    f"BB_L/U:{log_inf_data['bbl']}/{log_inf_data['bbu']}]"
                )
            log_lines.append(f"  HTF Data: {' '.join(inf_parts)}")

        bb_5m_details = log_details.get('bb_5m', {})
        if isinstance(bb_5m_details, dict):
            bb_long_status = "[PASS]" if bb_5m_details.get(
                'long') else "[FAIL]"
            bb_short_status = "[PASS]" if bb_5m_details.get(
                'short') else "[FAIL]"
            log_lines.append(
                f"  BB (5m) : LONG: {bb_long_status} ({bb_5m_details.get('L', '')}) | "
                f"SHORT: {bb_short_status} ({bb_5m_details.get('S', '')})"
            )
        else:
            log_lines.append(f"  BB (5m) : {bb_5m_details}")

        trend_master_switch = log_details.get('trend_master_switch', 'N/A')
        trend_final_long = "[PASS]" if calculated_trend_long else "[FAIL]"
        trend_final_short = "[PASS]" if calculated_trend_short else "[FAIL]"
        log_lines.append(
            f"  Trend   : MASTER_SWITCH=[{trend_master_switch}] | Final Trend Result: LONG:{trend_final_long} SHORT:{trend_final_short}")

        trend_filters = log_details.get('trend_filters', {})
        if trend_filters:
            for name, details in trend_filters.items():
                name_str = f"- {name:<12}"
                if isinstance(details, dict):
                    long_status = "[PASS]" if details.get('long') else "[FAIL]"
                    short_status = "[PASS]" if details.get(
                        'short') else "[FAIL]"
                    long_details_str = f"({details.get('L_details', '')})" if 'L_details' in details else f"(val:{details.get('val')})" if 'val' in details else ""
                    short_details_str = f"({details.get('S_details', '')})" if 'S_details' in details else f"(val:{details.get('val')})" if 'val' in details else ""
                    log_lines.append(
                        f"    {name_str}: LONG: {long_status} {long_details_str} | SHORT: {short_status} {short_details_str}")
                else:
                    log_lines.append(f"    {name_str}: {details}")

        log_lines.append("  HTF Confirmation:")
        htf_bb_l = log_details.get('bb_htf_long', [])
        htf_bb_s = log_details.get('bb_htf_short', [])
        log_lines.append(
            f"    - BB Bands: LONG: {htf_bb_l or '[]'} | SHORT: {htf_bb_s or '[]'}")

        if log_details.get('trend_htf') == 'disabled':
            log_lines.append("    - Trend: [DISABLED BY STRATEGY PARAMETER]")
        else:
            htf_details = log_details.get('trend_htf_details', {})
            htf_final_result = log_details.get('trend_htf_final_result', {})
            htf_final_long_status = "[PASS]" if htf_final_result.get(
                'long') else "[FAIL]"
            htf_final_short_status = "[PASS]" if htf_final_result.get(
                'short') else "[FAIL]"
            log_lines.append(
                f"    - Trend: Final: LONG:{htf_final_long_status} SHORT:{htf_final_short_status}")

            if htf_details:
                for tf, details in htf_details.items():
                    tf_result = details.get('result', {})
                    tf_long_status = "[PASS]" if tf_result.get(
                        'long') else "[FAIL]"
                    tf_short_status = "[PASS]" if tf_result.get(
                        'short') else "[FAIL]"
                    checks_str_parts = []
                    for name, check in details.get('checks', {}).items():
                        if isinstance(check, dict):
                            l_s = 'P' if check.get('long') else 'F'
                            s_s = 'P' if check.get('short') else 'F'
                            checks_str_parts.append(
                                f"{name}[L:{l_s}/S:{s_s}]")
                    checks_str = ", ".join(checks_str_parts)
                    log_lines.append(
                        f"        - TF {tf:<3}: LONG:{tf_long_status} SHORT:{tf_short_status} | Checks: {checks_str}")

        final_long_status = "[PASS]" if final_signals.get('long') else "[FAIL]"
        final_short_status = "[PASS]" if final_signals.get(
            'short') else "[FAIL]"
        log_lines.append(
            f"  Result  : Long Signal: {final_long_status}, Short Signal: {final_short_status}")

        log_msg = "\n".join(log_lines)
        logger.debug(log_msg)

        return signals

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        pair = metadata['pair']
        dataframe['enter_long'], dataframe['enter_short'], dataframe['enter_tag'] = 0, 0, ''

        if len(dataframe) > 1:
            last_candle = dataframe.iloc[-1]
            if last_candle['low'] <= 0:
                return dataframe
            candle_size = (last_candle['high'] -
                           last_candle['low']) / last_candle['low']
            if candle_size < self.min_candle_size:
                return dataframe

            signals = self.check_signals(dataframe, pair)
            self.stats['signals_checked'] += 1

            if not ((signals.get('bb_long', False) and signals.get('trend_long', False)) or
                    (signals.get('bb_short', False) and signals.get('trend_short', False))):
                return dataframe

            trailing_signal = self.get_trailing_entry_signal(
                pair, last_candle, signals)
            if trailing_signal:
                side = trailing_signal['side']
                side_rus = 'Лонг' if side == 'long' else 'Шорт'
                tag_prefix = 'Мгновенный' if trailing_signal.get(
                    'direct_entry', False) else 'Трейлинг'
                tag = f"{tag_prefix} {side_rus}"
                if side == 'long':
                    dataframe.loc[dataframe.index[-1], 'enter_long'] = 1
                else:
                    dataframe.loc[dataframe.index[-1], 'enter_short'] = 1
                dataframe.loc[dataframe.index[-1], 'enter_tag'] = tag
                if 'entry_price' in trailing_signal:
                    self.custom_entry_prices[pair] = trailing_signal['entry_price']
                self.stats['total_entries'] += 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['exit_long'], dataframe['exit_short'] = 0, 0
        return dataframe

    def get_safe_trade_key(self, trade: Trade) -> str:
        return f"{trade.pair}_{trade.open_date_utc.timestamp()}"

    def create_dca_plan(self, trade: Trade) -> dict:
        if not self.AUTO_DCA_PLANNING_ENABLED:
            logger.debug(
                f"[{trade.pair}] create_dca_plan: Автоматическое планирование DCA отключено.")
            return {}

        total_balance, _ = self._get_balance_info()
        if total_balance is None or total_balance <= 0:
            logger.warning(
                f"[{trade.pair}] create_dca_plan: Не удалось получить баланс для расчета DCA плана, используется запасной вариант (0.0)...")
            total_balance = 0.0

        max_trade_budget = total_balance * self.POSITION_SIZE_BALANCE_RATIO
        logger.debug(
            f"[{trade.pair}] create_dca_plan: Общий баланс: {total_balance:.2f}, Макс. бюджет сделки (POSITION_SIZE_BALANCE_RATIO): {max_trade_budget:.2f}")

        initial_stake_for_plan = trade.open_initial_stake_amount if hasattr(
            trade, 'open_initial_stake_amount') and trade.open_initial_stake_amount is not None else (max_trade_budget * self.FIRST_ENTRY_RATIO)
        logger.debug(
            f"[{trade.pair}] create_dca_plan: Начальный объем для плана: {initial_stake_for_plan:.4f}")

        dca_budget = max_trade_budget * self.DCA_BUDGET_RATIO
        logger.debug(
            f"[{trade.pair}] create_dca_plan: Бюджет DCA (DCA_BUDGET_RATIO): {dca_budget:.2f}")

        dca_config = self.get_adaptive_dca_config()
        regular_dca_count_cfg, emergency_dca_count_cfg = dca_config[
            'regular_dca'], dca_config['emergency_dca']
        logger.debug(
            f"[{trade.pair}] create_dca_plan: Настройки DCA: Регулярных: {regular_dca_count_cfg}, Аварийных: {emergency_dca_count_cfg}")

        regular_dca_sizes = []
        dca_sum = 0.0
        current_dca_size = initial_stake_for_plan
        min_dca = current_dca_size * self.DCA_SIZE_MULTIPLIER
        for i in range(regular_dca_count_cfg):
            next_dca = current_dca_size * self.DCA_SIZE_MULTIPLIER
            if dca_sum + next_dca > dca_budget:
                logger.debug(
                    f"[{trade.pair}] create_dca_plan: Превышен бюджет DCA при расчете регулярных докупок. Доступно: {dca_budget - dca_sum:.2f}")
                left = dca_budget - dca_sum
                if left >= min_dca:
                    regular_dca_sizes.append(left)
                break
            regular_dca_sizes.append(next_dca)
            dca_sum += next_dca
            current_dca_size = next_dca
        logger.debug(
            f"[{trade.pair}] create_dca_plan: Размеры регулярных DCA: {regular_dca_sizes}")

        regular_dca_count = len(regular_dca_sizes)

        # Экстримальные докупки не рассчитываем, буфер просто остается
        emergency_dca_sizes = []
        emergency_dca_count = emergency_dca_count_cfg

        all_dca_sizes = regular_dca_sizes + emergency_dca_sizes

        plan = {
            'trade_id': trade.id,
            'trade_key': self.get_safe_trade_key(trade),
            'first_entry_cost': initial_stake_for_plan,
            'regular_dca_sizes': regular_dca_sizes,
            'emergency_dca_sizes': emergency_dca_sizes,
            'all_dca_sizes': all_dca_sizes,
            'regular_dca_count': regular_dca_count,
            'emergency_dca_count': emergency_dca_count,
            'planned_total_position': initial_stake_for_plan + sum(all_dca_sizes),
            'created_at': datetime.now(timezone.utc),
            'base_dca_unit_size_val': initial_stake_for_plan
        }
        logger.info(f"[{trade.pair}] create_dca_plan: Создан план DCA: {plan}")
        self.dca_plans[plan['trade_key']] = plan
        return plan

    def _get_int_from_custom_data(self, trade: Trade, key: str, default: int) -> int:
        try:
            value = trade.get_custom_data(key, default)
            if value is None:
                return default
            return int(value)
        except (ValueError, TypeError):
            logger.info(
                f"Не удалось прочитать '{key}' для сделки {trade.id}. "
                f"Значение в базе данных повреждено (вероятно, 'null' вместо числа). "
                f"Используется значение по умолчанию: {default}."
            )
            return default

    def get_leverage_adjusted_thresholds(self, leverage: float) -> dict:
        if leverage <= 1.0:
            leverage = 1.0
        return {
            'danger': self.LIQUIDATION_DANGER_THRESHOLD,
            'emergency': self.LIQUIDATION_EMERGENCY_THRESHOLD,
            'last_chance': self.LIQUIDATION_LAST_CHANCE_THRESHOLD
        }

    def calculate_distance_to_liquidation(self, trade: Trade, mark_price: float) -> dict:
        result = {'liquidation_price': None, 'distance_percent': None,
                  'is_danger': False, 'is_emergency': False, 'source': 'none'}
        if not self.LIQUIDATION_PROTECTION_ENABLED:
            return result

        try:
            liquidation_price = getattr(trade, 'liquidation_price', None)
            if liquidation_price and liquidation_price > 0:
                result.update(
                    {'liquidation_price': liquidation_price, 'source': 'freqtrade'})
            else:
                leverage = getattr(trade, 'leverage', 1.0) or 1.0
                pair_clean = trade.pair.replace('/', '')
                mmr = self.bybit_maintenance_margin_rates.get('default', 0.004)
                position_value = trade.stake_amount * leverage
                if hasattr(self, 'bybit_leverage_tiers') and pair_clean in self.bybit_leverage_tiers:
                    for tier in self.bybit_leverage_tiers[pair_clean]:
                        if position_value <= float(tier['max_position_value']):
                            mmr = float(tier['maintenance_margin_rate'])
                            break

                imr = 1 / leverage
                if trade.is_short:
                    estimated_liq = trade.open_rate * (1 + imr - mmr)
                else:
                    estimated_liq = trade.open_rate * (1 - imr + mmr)

                result.update({'liquidation_price': max(
                    estimated_liq, 0), 'source': 'bybit_formula'})

            if result['liquidation_price'] and result['liquidation_price'] > 0:
                logger.debug(
                    f"{trade.pair} - Цена ликвидации: {result['liquidation_price']:.4f} (цена маркировки: {mark_price:.4f}, источник: {result['source']}, сторона: {'SHORT' if trade.is_short else 'LONG'})")
                if trade.is_short:
                    distance = (result['liquidation_price'] - mark_price) / \
                        mark_price if mark_price > 0 else 0.0
                else:
                    distance = (
                        mark_price - result['liquidation_price']) / mark_price if mark_price > 0 else 0.0
                result['distance_percent'] = max(distance, 0.0)
                leverage = getattr(trade, 'leverage', 1.0) or 1.0
                thresholds = self.get_leverage_adjusted_thresholds(leverage)
                result['is_danger'] = result['distance_percent'] <= thresholds['danger']
                result['is_emergency'] = result['distance_percent'] <= thresholds['emergency']
                logger.info(
                    f"{trade.pair} - 📊 Ликвидация: Расстояние={result['distance_percent']:.4%} ({result['distance_percent']*100:.2f}%), "
                    f"Пороги: danger≤{thresholds['danger']:.2%}, emergency≤{thresholds['emergency']:.2%}, last_chance≤{thresholds['last_chance']:.2%}, "
                    f"Цена маркировки={mark_price:.4f}, Цена ликвидации={result['liquidation_price']:.4f}")
            else:
                logger.warning(
                    f"{trade.pair} - Не удалось рассчитать цену ликвидации или она равна нулю. Результат: {result}")
        except Exception as e:
            logger.error(
                f"{trade.pair} - Ошибка при расчете расстояния до ликвидации: {e}", exc_info=True)
        return result

    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: Any) -> float:
        try:
            total_balance, free_balance = self._get_balance_info()
            if total_balance is None or total_balance <= 0:
                logger.warning(
                    f"[{pair}] custom_stake_amount: Не удалось получить баланс. Возврат мин. ордера.")
                return max(min(proposed_stake, min_stake or self.ABSOLUTE_MIN_ORDER), self.ABSOLUTE_MIN_ORDER)

            logger.debug(
                f"[{pair}] custom_stake_amount: Общий баланс: {total_balance:.2f}, Свободный баланс: {free_balance:.2f}")

            dynamic_min_order = self.get_dynamic_min_order_size(total_balance)
            dynamic_max_order = self.get_dynamic_max_order_size(total_balance)

            logger.debug(
                f"[{pair}] custom_stake_amount: Динамический мин. ордер: {dynamic_min_order:.2f}, Динамический макс. ордер: {dynamic_max_order:.2f}")

            open_trades_info = self._analyze_open_trades()
            optimal_positions = self.get_adaptive_max_open_trades(
                open_trades_info)

            logger.debug(
                f"[{pair}] custom_stake_amount: Открытых сделок: {open_trades_info['count']}, Оптимальных позиций: {optimal_positions}")

            if open_trades_info['count'] >= optimal_positions:
                logger.info(
                    f"[{pair}] custom_stake_amount: Отмена входа - достигнут лимит открытых сделок ({open_trades_info['count']}/{optimal_positions}).")
                return 0.0
            if free_balance is not None and free_balance <= 0:
                logger.info(
                    f"[{pair}] custom_stake_amount: Отмена входа - свободный баланс равен или меньше нуля ({free_balance:.2f}).")
                return 0.0

            available_for_new = free_balance or total_balance
            position_budget = min(
                total_balance * self.POSITION_SIZE_BALANCE_RATIO, available_for_new)
            position_budget = max(position_budget, max(
                dynamic_min_order, total_balance * 0.01))

            logger.debug(
                f"[{pair}] custom_stake_amount: Доступно для новых: {available_for_new:.2f}, Бюджет позиции: {position_budget:.2f}")

            first_entry_size = position_budget * self.FIRST_ENTRY_RATIO
            actual_min_for_entry = max(
                dynamic_min_order, min_stake if min_stake is not None else 0.0)
            final_stake = max(first_entry_size, actual_min_for_entry)
            final_stake = min(final_stake, max_stake, dynamic_max_order)

            logger.info(
                f"[{pair}] custom_stake_amount: Расчетный размер первой позиции: {final_stake:.4f} (Исходный: {proposed_stake:.4f})")
            return final_stake
        except Exception as e:
            logger.error(
                f"Ошибка при расчете custom_stake_amount для {pair}: {e}", exc_info=True)
            min_stake_val = min_stake or self.get_dynamic_min_order_size(100)
            logger.warning(
                f"[{pair}] custom_stake_amount: Использование запасного мин. размера ставки: {min_stake_val:.4f}")
            return min_stake_val

    def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
                              current_profit: float, min_stake: Optional[float], max_stake: float, **kwargs: Any) -> Optional[float]:

        logger.debug(
            f"[{trade.pair}] adjust_trade_position: Начало проверки DCA. Текущая прибыль: {current_profit:.2%}.")
        if Trade.use_db:
            try:
                reloaded_trade = Trade.get_trades(
                    [Trade.id == trade.id]).first()
                if not reloaded_trade:
                    logger.error(
                        f"[{trade.pair}] Сделка {trade.id} не найдена в БД при перезагрузке. Отмена докупки.")
                    return None
                trade = reloaded_trade
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Сделка успешно перезагружена из БД.")
            except Exception as e:
                logger.error(
                    f"[{trade.pair}] Критическая ошибка при перезагрузке сделки {trade.id} из БД: {e}. Отмена докупки.", exc_info=True)
                return None

        funding_fees = self._get_trade_funding_fees(trade)
        stake_amount = trade.stake_amount
        real_profit = current_profit
        if stake_amount > 0:
            funding_fee_ratio = funding_fees / stake_amount
            real_profit -= funding_fee_ratio
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Комиссия за финансирование: {funding_fees:.4f} ({funding_fee_ratio:.2%}), Реальная прибыль: {real_profit:.2%}")

        logger.debug(
            f"[{trade.pair}] adjust_trade_position: Расчет прибыли/убытка: {current_profit:.2%}, Комиссия за финансирование: {funding_fees:.4f}, Итоговый результат: {real_profit:.2%}"
        )

        if not self.DCA_LEVELS_ENABLED or real_profit >= 0:
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Усреднение не требуется: DCA отключено или позиция в прибыли ({real_profit:.2%}). Отмена."
            )
            return None

        trade_key = self.get_safe_trade_key(trade)
        if trade_key not in self.dca_plans:
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: План DCA не найден, создаю новый.")
            self.create_dca_plan(trade)
        plan = self.dca_plans.get(trade_key)
        if not plan:
            logger.error(
                f"[{trade.pair}] adjust_trade_position: План усреднения не найден и не удалось создать новый. Отмена докупки."
            )
            return None

        trade_id = trade.id if trade.id is not None else -1

        regular_dca_count_plan = plan.get('regular_dca_count')
        emergency_dca_count_plan = plan.get('emergency_dca_count')
        planned_total_position_plan = plan.get('planned_total_position')

        if any(v is None for v in [regular_dca_count_plan, emergency_dca_count_plan, planned_total_position_plan]):
            logger.error(
                f"[{trade.pair}] adjust_trade_position: План DCA поврежден (содержит null), будет пересоздан. План: {plan}")
            plan = self.create_dca_plan(trade)
            if not plan:
                logger.error(
                    f"[{trade.pair}] adjust_trade_position: Не удалось пересоздать план DCA. Отмена докупки.")
                return None
            regular_dca_count_plan = plan.get('regular_dca_count', 0)
            emergency_dca_count_plan = plan.get(
                'emergency_dca_count', 0)
            planned_total_position_plan = plan.get(
                'planned_total_position', 0.0)

        plan_summary = (
            f"ID:{trade_id}, План: {regular_dca_count_plan} плановых + {emergency_dca_count_plan} аварийных, Объем: {planned_total_position_plan:.2f}")
        logger.debug(
            f"[{trade.pair}] adjust_trade_position: Открытых входов по сделке: {trade.nr_of_successful_entries}. {plan_summary}")

        dca_size = None
        is_emergency_dca = False
        current_dca_count = trade.nr_of_successful_entries - 1
        emergency_dca_count_in_trade = self._get_int_from_custom_data(
            trade, 'emergency_dca_count', 0)
        regular_dca_count_in_trade = current_dca_count - emergency_dca_count_in_trade

        if regular_dca_count_plan > 0 and current_dca_count < regular_dca_count_plan:
            loss_level_for_dca = self.get_adaptive_dca_loss_level(
                current_dca_count)
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Проверка планового усреднения. Убыток: {abs(real_profit):.2%}, Порог докупки: {loss_level_for_dca:.2%}. От текущего: {abs(real_profit) - loss_level_for_dca:.2%}"
            )
            if abs(real_profit) < loss_level_for_dca:
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Плановое усреднение отклонено: убыток ({real_profit:.2%}) меньше порога ({loss_level_for_dca:.2%}).")
                return None

            leverage = getattr(trade, 'leverage', 1.0) or 1.0
            min_time = self.MIN_DCA_TIME_SECONDS * \
                (self.LEVERAGE_TIME_MULTIPLIER if leverage > 2.0 else 1.0)

            time_since_last_filled = (
                current_time - trade.date_last_filled_utc).total_seconds()
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Время с последнего исполненного ордера: {time_since_last_filled:.0f} сек, Мин. интервал: {min_time:.0f} сек.")

            if time_since_last_filled < min_time:
                remaining_time = min_time - time_since_last_filled
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Плановое усреднение отклонено: не прошел минимальный интервал с момента последнего исполненного ордера. Осталось {remaining_time:.0f} сек."
                )
                return None

            last_entry_price = trade.open_rate
            if trade.nr_of_successful_entries > 1:
                filled_entries = trade.select_filled_orders(
                    trade.entry_side)
                if filled_entries:
                    last_entry_price = filled_entries[-1].price
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Последняя цена входа: {last_entry_price:.4f}")

            price_worsened_since_last_dca = (
                (not trade.is_short and current_rate < last_entry_price * (1 - self.DCA_PRICE_BUFFER)) or
                (trade.is_short and current_rate >
                 last_entry_price * (1 + self.DCA_PRICE_BUFFER))
            )
            logger.debug(f"[{trade.pair}] adjust_trade_position: Цена ухудшилась с последнего DCA: {price_worsened_since_last_dca} (Текущая: {current_rate:.4f}, Последняя: {last_entry_price:.4f}, Буфер: {self.DCA_PRICE_BUFFER:.4f})")

            if trade.nr_of_successful_entries > 1 and not price_worsened_since_last_dca:
                price_diff = (current_rate - last_entry_price) / \
                    last_entry_price if last_entry_price > 0 else 0
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Плановое усреднение отклонено: цена не ухудшилась с прошлого раза. Изменение: {price_diff:.2%}, Порог буфера: {self.DCA_PRICE_BUFFER:.2%}"
                )
                return None

            try:
                dataframe, _ = self.dp.get_analyzed_dataframe(
                    trade.pair, self.timeframe)
                if dataframe is not None and not dataframe.empty and len(dataframe) >= self.DCA_CONFIRMATION_LOOKBACK:
                    recent_candles = dataframe.tail(
                        self.DCA_CONFIRMATION_LOOKBACK)
                    is_local_extremum = False
                    if not trade.is_short:
                        if current_rate <= recent_candles['low'].min():
                            is_local_extremum = True
                    else:
                        if current_rate >= recent_candles['high'].max():
                            is_local_extremum = True
                    logger.debug(
                        f"[{trade.pair}] adjust_trade_position: Проверка локального экстремума: {is_local_extremum} (за {self.DCA_CONFIRMATION_LOOKBACK} свечей).")

                    if not is_local_extremum:
                        logger.debug(
                            f"[{trade.pair}] adjust_trade_position: Плановое усреднение отклонено: цена не является локальным экстремумом за последние {self.DCA_CONFIRMATION_LOOKBACK} свечей."
                        )
                        return None
                else:
                    logger.warning(
                        f"[{trade.pair}] adjust_trade_position: Не удалось получить датафрейм или недостаточно данных для проверки локального экстремума DCA. Проверка пропущена."
                    )
            except Exception as e:
                logger.error(
                    f"[{trade.pair}] Ошибка при проверке локального экстремума для DCA: {e}", exc_info=True)

            time_since_last = (
                current_time - trade.date_last_filled_utc).total_seconds()
            leverage = getattr(trade, 'leverage', 1.0) or 1.0
            min_time = self.MIN_DCA_TIME_SECONDS * \
                (self.LEVERAGE_TIME_MULTIPLIER if leverage > 2.0 else 1.0)
            if time_since_last < min_time:
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Повторная проверка времени, докупка отклонена: не прошел мин. интервал ({time_since_last:.0f} < {min_time:.0f} сек).")
                return None

            if current_dca_count < len(plan['all_dca_sizes']):
                dca_size = plan['all_dca_sizes'][current_dca_count]
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Плановое усреднение: размер = {dca_size:.6f}")
        else:
            if plan['emergency_dca_count'] == 0:
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Аварийное усреднение отключено параметром EMERGENCY_DCA_COUNT = 0.")
                return None

            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Проверка экстренного усреднения (плановых: {regular_dca_count_plan}, текущих: {current_dca_count})...")

            mark_price = current_rate  # Фолбэк на текущую цену, если цена маркировки не получена
            try:
                if self.dp and self.dp.runmode.value in ('live', 'dry_run'):
                    ticker = self.dp.ticker(trade.pair)
                    mark_price = float(ticker.get(
                        'mark', ticker.get('last', current_rate)))
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Получена цена маркировки: {mark_price:.4f}")
            except Exception as e:
                logger.warning(
                    f"[{trade.pair}] adjust_trade_position: Не удалось получить цену маркировки: {e}. Используется текущая цена ({mark_price:.4f}).")

            liquidation_info = self.calculate_distance_to_liquidation(
                trade, mark_price)
            leverage_for_thresholds = getattr(
                trade, 'leverage', 1.0) or 1.0
            thresholds = self.get_leverage_adjusted_thresholds(
                leverage_for_thresholds)
            distance_percent = liquidation_info.get('distance_percent')
            if distance_percent is None:
                logger.error(
                    f"[{trade.pair}] adjust_trade_position: Не удалось получить distance_percent из liquidation_info. Отмена экстренной докупки.")
                return None

            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Информация о ликвидации: {liquidation_info}, Дистанция до ликвидации: {distance_percent:.2%}, Пороги: {thresholds}")

            threshold_log_parts = []
            next_threshold_name = None
            next_threshold_value = 0

            if distance_percent > thresholds['danger']:
                next_threshold_name = "Опасность"
                next_threshold_value = thresholds['danger']
            elif distance_percent > thresholds['emergency']:
                next_threshold_name = "Авария"
                next_threshold_value = thresholds['emergency']
            elif distance_percent > thresholds['last_chance']:
                next_threshold_name = "Последний шанс"
                next_threshold_value = thresholds['last_chance']

            if next_threshold_name:
                diff_to_threshold = distance_percent - next_threshold_value
                profit_at_threshold_str = ""
                liquidation_price = liquidation_info.get(
                    'liquidation_price')
                if liquidation_price is not None and liquidation_price > 0:
                    target_rate_at_threshold = 0
                    if trade.is_short:
                        if (1 + next_threshold_value) > 0:
                            target_rate_at_threshold = liquidation_price / \
                                (1 + next_threshold_value)
                    else:
                        if (1 - next_threshold_value) > 0:
                            target_rate_at_threshold = liquidation_price / \
                                (1 - next_threshold_value)

                    if target_rate_at_threshold > 0:
                        profit_at_threshold = trade.calc_profit_ratio(
                            target_rate_at_threshold)
                        funding_fee_ratio = funding_fees / \
                            trade.stake_amount if trade.stake_amount > 0 else 0
                        real_profit_at_threshold = profit_at_threshold - funding_fee_ratio
                        profit_at_threshold_str = f" (общий убыток ~{real_profit_at_threshold:.2%})"

                threshold_log_parts.append(
                    f'До порога "{next_threshold_name}" ({next_threshold_value:.2%}) осталось {diff_to_threshold:.2%}{profit_at_threshold_str}.'
                )

            threshold_log_parts.append(
                f"Все пороги: Опасность={thresholds['danger']:.2%}, Авария={thresholds['emergency']:.2%}, Шанс={thresholds['last_chance']:.2%}")

            threshold_log_str = " ".join(threshold_log_parts)

            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Запас до ликвидации: {distance_percent:.2%}. Плечо: {leverage_for_thresholds:.2f}x. {threshold_log_str}")

            emergency_dca_level = 0
            if distance_percent <= thresholds['last_chance']:
                emergency_dca_level = 3
                logger.warning(
                    f"[{trade.pair}] ⚠️ КРИТИЧЕСКИЙ УРОВЕНЬ! Расстояние до ликвидации: {distance_percent:.2%} <= {thresholds['last_chance']:.2%}")
            elif distance_percent <= thresholds['emergency']:
                emergency_dca_level = 2
                logger.warning(
                    f"[{trade.pair}] ⚠️ АВАРИЙНЫЙ УРОВЕНЬ! Расстояние до ликвидации: {distance_percent:.2%} <= {thresholds['emergency']:.2%}")
            elif distance_percent <= thresholds['danger']:
                emergency_dca_level = 1
                logger.info(
                    f"[{trade.pair}] ⚠️ УРОВЕНЬ ОПАСНОСТИ! Расстояние до ликвидации: {distance_percent:.2%} <= {thresholds['danger']:.2%}")
            else:
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Аварийное усреднение не требуется: запас до ликвидации ({distance_percent:.2%}) достаточный (пороги: danger={thresholds['danger']:.2%}, emergency={thresholds['emergency']:.2%}, last_chance={thresholds['last_chance']:.2%}).")

            level_map = {0: 'Нет угрозы', 1: 'Опасность',
                         2: 'Авария', 3: 'Последний шанс'}
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Уровень угрозы: {emergency_dca_level} ({level_map.get(emergency_dca_level, 'N/A')})")

            if emergency_dca_level > 0:
                emergency_dca_used = self._get_int_from_custom_data(
                    trade, 'emergency_dca_count', 0)
                emergency_dca_available = plan['emergency_dca_count'] == - \
                    1 or emergency_dca_used < plan['emergency_dca_count']

                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Использовано аварийных усреднений: {emergency_dca_used}, доступно: {'бесконечно' if plan['emergency_dca_count'] == -1 else plan['emergency_dca_count']}. Возможность докупки: {emergency_dca_available}")

                if emergency_dca_available:
                    emergency_key = f"{trade_key}_emergency"
                    last_emergency_time = self.last_emergency_dca_time.get(
                        emergency_key)
                    logger.debug(
                        f"[{trade.pair}] adjust_trade_position: Последнее аварийное усреднение: {last_emergency_time}")

                    time_since_last_emergency = (
                        current_time - last_emergency_time).total_seconds() if last_emergency_time else float('inf')

                    if emergency_dca_level >= 3:
                        min_time_required = self.EMERGENCY_DCA_TIME_LEVEL_3_SECONDS
                    elif emergency_dca_level >= 2:
                        min_time_required = self.EMERGENCY_DCA_TIME_LEVEL_2_SECONDS
                    else:
                        min_time_required = self.EMERGENCY_DCA_TIME_LEVEL_1_SECONDS

                    logger.debug(
                        f"[{trade.pair}] adjust_trade_position: Прошло {time_since_last_emergency:.2f} сек. с прошлого аварийного усреднения (мин. {min_time_required} сек., уровень угрозы: {emergency_dca_level})")

                    if not last_emergency_time or time_since_last_emergency >= min_time_required:
                        base_dca_size = 0
                        if plan['regular_dca_sizes'] and len(plan['regular_dca_sizes']) > 0:
                            base_dca_size = plan['regular_dca_sizes'][-1]
                            logger.debug(
                                f"[{trade.pair}] adjust_trade_position: Базовый размер для экстренной DCA (из последней регулярной): {base_dca_size:.4f}")
                        else:
                            if self.REGULAR_DCA_COUNT == 0:
                                base_dca_size = trade.stake_amount
                                logger.debug(
                                    f"[{trade.pair}] adjust_trade_position: Базовый размер для экстренной DCA (нет регулярных, от текущего стейка): {base_dca_size:.4f}")
                            else:
                                base_dca_size = plan['base_dca_unit_size_val']
                                logger.debug(
                                    f"[{trade.pair}] adjust_trade_position: Базовый размер для экстренной DCA (из base_dca_unit_size_val): {base_dca_size:.4f}")

                        emergency_multiplier = self.EMERGENCY_DCA_MULTIPLIERS[min(
                            emergency_dca_level - 1, len(self.EMERGENCY_DCA_MULTIPLIERS) - 1)]
                        logger.debug(
                            f"[{trade.pair}] adjust_trade_position: Расчет размера аварийного усреднения. Базовый размер: {base_dca_size:.4f}, Множитель (уровень {emergency_dca_level}): {emergency_multiplier}x"
                        )
                        dca_size = base_dca_size * emergency_multiplier

                        total_balance_avail, free_balance_avail = self._get_balance_info()
                        if free_balance_avail is not None:
                            max_possible_dca_size_from_free = min(
                                free_balance_avail, self.ABSOLUTE_MAX_ORDER)
                            dca_size = min(
                                dca_size, max_possible_dca_size_from_free)
                            logger.debug(
                                f"[{trade.pair}] adjust_trade_position: Экстренное DCA скорректировано по свободному балансу: {dca_size:.4f} (доступно: {free_balance_avail:.2f}).")
                        else:
                            logger.warning(
                                f"[{trade.pair}] adjust_trade_position: Не удалось получить свободный баланс для ограничения экстренного DCA.")

                        is_emergency_dca = True
                        logger.info(
                            f"[{trade.pair}] ✅ АВАРИЙНОЕ УСРЕДНЕНИЕ! Уровень: {emergency_dca_level}, Размер: {dca_size:.4f}, Множитель: {emergency_multiplier}x")
                    else:
                        logger.warning(
                            f"[{trade.pair}] ⚠️ Аварийное усреднение отклонено: не прошел минимальный интервал ({time_since_last_emergency:.2f} < {min_time_required} сек, уровень угрозы: {emergency_dca_level}).")
                else:
                    logger.debug(
                        f"[{trade.pair}] adjust_trade_position: Аварийное усреднение отклонено: лимит исчерпан ({emergency_dca_used}/{plan['emergency_dca_count']}).")
            else:
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Аварийное усреднение отклонено: уровень угрозы не активирован (текущий: {emergency_dca_level}).")

        logger.debug(
            f"[{trade.pair}] adjust_trade_position: Предварительный размер усреднения: {dca_size}, тип: {'аварийное' if is_emergency_dca else 'плановое'}.")

        if dca_size is None or dca_size <= 0:
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Ордер на усреднение не создан (размер не определен или равен нулю). Отмена.")
            self.emergency_dca_flags.pop(
                trade.pair, None)
            return None

        if not is_emergency_dca and not self.confirm_trade_entry(trade.pair, self.custom_dca_order_type(trade), dca_size, current_rate, 'gtc', current_time, trade.enter_tag, trade.entry_side):
            logger.info(
                f"[{trade.pair}] adjust_trade_position: Ордер DCA отклонен из-за фильтрации ставки финансирования. Отмена.")
            self.emergency_dca_flags.pop(trade.pair, None)
            return None

        balance = self._get_balance_info()[0] or 100.0
        dyn_min_stake = self.get_dynamic_min_order_size(balance)
        dyn_max_stake = self.get_dynamic_max_order_size(balance)
        logger.debug(
            f"[{trade.pair}] adjust_trade_position: Корректировка размера усреднения. Предварительный: {dca_size:.4f}, Мин.стейк (стратегия): {min_stake}, Динамический мин: {dyn_min_stake:.4f}, Динамический макс: {dyn_max_stake:.4f}, Макс.стейк (общий): {max_stake:.4f}"
        )
        final_dca_size = max(
            dca_size, min_stake or dyn_min_stake)
        final_dca_size = min(final_dca_size, max_stake,
                             dyn_max_stake)
        logger.info(
            f"[{trade.pair}] adjust_trade_position: Итоговый размер для усреднения: {final_dca_size:.4f}")

        if final_dca_size > 0:
            if is_emergency_dca:
                emergency_dca_used = self._get_int_from_custom_data(
                    trade, 'emergency_dca_count', 0)
                trade.set_custom_data(
                    'emergency_dca_count', emergency_dca_used + 1)
                emergency_key = f"{trade_key}_emergency"
                self.last_emergency_dca_time[emergency_key] = current_time
                trade.set_custom_data(
                    'status_tag', f'Аварийная докупка №{emergency_dca_used + 1}')
                self.emergency_dca_flags[trade.pair] = True
                self.stats['emergency_dca'] += 1
                logger.info(
                    f"[{trade.pair}] ✅ Аварийная докупка подтверждена! Номер: {emergency_dca_used + 1}, Размер: {final_dca_size:.4f}")
            else:
                trade.set_custom_data(
                    'status_tag', f'Плановая докупка №{trade.nr_of_successful_entries}')
                self.emergency_dca_flags.pop(trade.pair, None)
                self.stats['total_dca'] += 1
                logger.debug(
                    f"[{trade.pair}] adjust_trade_position: Установлен статус: Плановая докупка.")

            self.cleanup_trailing_entry(trade.pair)
            return final_dca_size
        else:
            logger.debug(
                f"[{trade.pair}] adjust_trade_position: Итоговый размер для усреднения <= 0. Ордер не будет создан. Отмена.")
            self.emergency_dca_flags.pop(trade.pair, None)
            return None

    def custom_dca_order_type(self, trade: Trade, **kwargs: Any) -> str:
        is_emergency = self.emergency_dca_flags.get(trade.pair)
        logger.debug(
            f"{trade.pair} - Тип ордера: {'MARKET (аварийный)' if is_emergency else 'LIMIT (плановый)'}. "
            f"Флаги: {self.emergency_dca_flags}"
        )
        if is_emergency:
            return 'market'
        return 'limit'

    def adjust_entry_price(self, trade: Trade, order: dict, pair: str,
                           current_time: datetime, proposed_rate: float, current_order_rate: float,
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
        return proposed_rate * (1 - self.DCA_PRICE_BUFFER if side == 'long' else 1 + self.DCA_PRICE_BUFFER)

    def _get_funding_rate_info(self, pair: str) -> Optional[float]:
        if not self.USE_FUNDING_RATE_FILTER:
            logger.debug(
                f"[{pair}] _get_funding_rate_info: Фильтр ставки финансирования отключен.")
            return None
        try:
            exchange = getattr(self.dp, 'exchange', None) or getattr(
                self.dp, '_exchange', None)
            if exchange:
                ticker = exchange.fetch_ticker(pair)
                info = ticker.get('info', {})
                funding_rate = float(info.get('fundingRate', 0.0))
                logger.debug(
                    f"[{pair}] _get_funding_rate_info: Получена ставка финансирования: {funding_rate:.6f}")
                return funding_rate
        except Exception as e:
            logger.warning(
                f"Не удалось получить ставку финансирования для {pair}: {e}")
            return None

    def _get_trade_funding_fees(self, trade: Trade) -> float:
        exchange = getattr(self.dp, 'exchange',
                           None) or getattr(self.dp, '_exchange', None)

        if self.config.get('dry_run', False) or not exchange:
            logger.debug(
                f"[{trade.pair}] _get_trade_funding_fees: Сухой прогон или нет биржи. Комиссия за финансирование = 0.")
            return 0.0
        try:
            fees = exchange.get_funding_fees(
                pair=trade.pair,
                amount=trade.amount,
                is_short=trade.is_short,
                open_date=trade.open_date_utc
            )
            logger.debug(
                f"[{trade.pair}] _get_trade_funding_fees: Рассчитана комиссия за финансирование: {fees:.4f}")
            return fees
        except Exception as e:
            logger.warning(
                f"Не удалось рассчитать комиссию за финансирование для {trade.pair}: {e}", exc_info=True)
            return 0.0

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
                 side: str, **kwargs: Any) -> float:
        max_safe_leverage = float(min(self.MAX_LEVERAGE, max_leverage))
        min_leverage = 1.0

        if self.TRADING_MODE == 'scalping':
            default_leverage = 2.0
        else:
            default_leverage = max(1.0, math.floor(max_safe_leverage / 2))

        if not self.ADAPTIVE_LEVERAGE_ENABLED:
            return float(min(default_leverage, max_safe_leverage))

        try:
            leverage_factors = [
                self._analyze_multi_timeframe_volatility(pair),
                self._analyze_multi_timeframe_trend(pair),
                self._analyze_market_conditions(pair),
                self._analyze_strategy_performance(),
                self._analyze_candle_size_risk(pair)
            ]
            leverage_factors = [f for f in leverage_factors if f]

            if not leverage_factors:
                logger.debug(f"[{pair}] Не удалось рассчитать факторы плеча. "
                             f"Используется плечо по умолчанию: {default_leverage:.2f}x")
                return float(min(default_leverage, max_safe_leverage))

            log_factors = {
                item['name']: f"{item['factor']:.2f} (w: {item['weight']})"
                for item in leverage_factors if 'name' in item
            }
            logger.debug(f"[{pair}] Факторы для расчета плеча: {log_factors}")

            total_weight = float(sum(f['weight'] for f in leverage_factors))
            weighted_factor = float(sum(f['factor'] * f['weight']
                                        for f in leverage_factors) / total_weight) if total_weight != 0 else float(0.0)

            calculated_leverage = float(max_safe_leverage -
                                        (max_safe_leverage - min_leverage) * weighted_factor)
            final_leverage = float(
                max(min_leverage, min(calculated_leverage, max_safe_leverage)))
            logger.info(
                f"[{pair}] Адаптивное плечо: {final_leverage:.2f}x. "
                f"(Макс: {max_safe_leverage:.2f}x, Фактор риска: {weighted_factor:.2f})"
            )
            return final_leverage
        except Exception as e:
            logger.error(
                f"Ошибка при расчете адаптивного плеча для {pair}: {e}", exc_info=True)
            logger.warning(
                f"Используется аварийное плечо по умолчанию: {default_leverage:.2f}x")
            return float(min(default_leverage, max_safe_leverage))

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs: Any) -> float:
        return self.stoploss

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                            time_in_force: str, current_time: datetime,
                            entry_tag: Optional[str], side: str, **kwargs: Any) -> bool:
        """
        Проверка перед входом в сделку. Отменяет вход, если ставка финансирования превышает порог.
        """
        if not self.USE_FUNDING_RATE_FILTER:
            return True

        funding_rate = self._get_funding_rate_info(pair)

        if funding_rate is None:
            logger.warning(
                f"[{pair}] Не удалось получить ставку финансирования. Вход в сделку отменен для безопасности."
            )
            self.cleanup_trailing_entry(pair)
            return False

        if funding_rate is not None:
            logger.debug(f"[{pair}] {side.upper()} - Ставка финансирования: {funding_rate:.4%}, "
                         f"порог: ±{self.MAX_FUNDING_RATE_THRESHOLD:.4%}")

            if side == 'long' and funding_rate > self.MAX_FUNDING_RATE_THRESHOLD:
                logger.info(
                    f"[{pair}] Вход в LONG отменен: высокая ставка финансирования ({funding_rate:.4%} > {self.MAX_FUNDING_RATE_THRESHOLD:.4%})")
                self.cleanup_trailing_entry(pair)
                return False

            if side == 'short' and funding_rate < -self.MAX_FUNDING_RATE_THRESHOLD:
                logger.info(
                    f"[{pair}] Вход в SHORT отменен: высокая ставка финансирования ({funding_rate:.4%} < {-self.MAX_FUNDING_RATE_THRESHOLD:.4%})")
                self.cleanup_trailing_entry(pair)
                return False

        return True

    def get_dynamic_min_order_size(self, total_balance: float) -> float:
        return max(total_balance * self.MIN_ORDER_SIZE_PERCENT, self.ABSOLUTE_MIN_ORDER)

    def get_dynamic_max_order_size(self, total_balance: float) -> float:
        return min(total_balance * self.MAX_ORDER_SIZE_PERCENT, self.ABSOLUTE_MAX_ORDER)

    def get_adaptive_max_open_trades(self, open_trades_info: dict) -> int:
        if not self.ADAPTIVE_MAX_TRADES_ENABLED:
            return self.MAX_OPEN_TRADES_DEFAULT

        total_balance, _ = self._get_balance_info()
        if total_balance is None or total_balance <= 0:
            logger.warning(
                "Не удалось получить баланс для адаптивного расчета MAX_OPEN_TRADES. Возврат MAX_OPEN_TRADES_DEFAULT.")
            return self.MAX_OPEN_TRADES_DEFAULT

        current_stake_in_use = open_trades_info['total_stake'] + \
            open_trades_info['reserved_for_dca']

        capital_usage_ratio = current_stake_in_use / \
            total_balance if total_balance > 0 else 0

        if capital_usage_ratio >= self.CAPITAL_USAGE_CRITICAL_THRESHOLD:
            optimal_trades = self.MIN_TRADES_FOR_CRITICAL_USAGE
            logger.debug(
                f"Критическое использование капитала ({capital_usage_ratio:.2%}). Оптимальное количество сделок: {optimal_trades}")
        elif capital_usage_ratio >= self.CAPITAL_USAGE_VERY_HIGH_THRESHOLD:
            optimal_trades = self.MIN_TRADES_FOR_VERY_HIGH_USAGE
            logger.debug(
                f"Очень высокое использование капитала ({capital_usage_ratio:.2%}). Оптимальное количество сделок: {optimal_trades}")
        elif capital_usage_ratio >= self.CAPITAL_USAGE_HIGH_THRESHOLD:
            optimal_trades = self.MIN_TRADES_FOR_HIGH_USAGE
            logger.debug(
                f"Высокое использование капитала ({capital_usage_ratio:.2%}). Оптимальное количество сделок: {optimal_trades}")
        elif capital_usage_ratio >= self.CAPITAL_USAGE_MEDIUM_THRESHOLD:
            optimal_trades = self.MIN_TRADES_FOR_MEDIUM_USAGE
            logger.debug(
                f"Среднее использование капитала ({capital_usage_ratio:.2%}). Оптимальное количество сделок: {optimal_trades}")
        elif capital_usage_ratio >= self.CAPITAL_USAGE_LOW_THRESHOLD:
            optimal_trades = self.MIN_TRADES_FOR_LOW_USAGE
            logger.debug(
                f"Низкое использование капитала ({capital_usage_ratio:.2%}). Оптимальное количество сделок: {optimal_trades}")
        else:
            optimal_trades = self.MAX_OPEN_TRADES_DEFAULT
            logger.debug(
                f"Очень низкое использование капитала ({capital_usage_ratio:.2%}). Оптимальное количество сделок: {optimal_trades}")

        return optimal_trades

    def _get_balance_info(self) -> Tuple[Optional[float], Optional[float]]:
        stake_currency = self.config.get('stake_currency', 'USDT')
        try:
            if hasattr(self, 'wallets') and self.wallets:
                total = self.wallets.get_total(stake_currency)
                free = self.wallets.get_free(stake_currency)
                if total and free:
                    logger.debug(
                        f"Баланс кошелька: Общий={total:.2f} {stake_currency}, Свободно={free:.2f} {stake_currency} (через self.wallets)")
                    return float(total), float(free)
            if self.config.get('dry_run', True):
                wallet = self.config.get('dry_run_wallet', 100)
                logger.debug(
                    f"Сухой прогон: Баланс кошелька: Общий={wallet:.2f} {stake_currency}, Свободно={wallet * 0.9:.2f} {stake_currency} (сухой прогон)")
                return float(wallet), float(wallet * 0.9)
        except Exception as e:
            logger.error(
                f"Ошибка при получении информации о балансе: {e}", exc_info=True)
            pass
        logger.warning(
            "Не удалось получить информацию о балансе. Возвращены None.")
        return None, None

    def _analyze_open_trades(self) -> dict:
        analysis = {'count': 0, 'total_stake': 0.0,
                    'reserved_for_dca': 0.0, 'positions_in_loss': 0}
        try:
            open_trades = Trade.get_open_trades() if hasattr(Trade, 'get_open_trades') else []
            if not open_trades:
                logger.debug("Нет открытых сделок для анализа.")
                return analysis

            for trade in open_trades:
                analysis['total_stake'] += trade.stake_amount
                trade_key = self.get_safe_trade_key(trade)
                dca_plan = self.dca_plans.get(trade_key)

                used_entries = max(0, trade.nr_of_successful_entries - 1)
                if dca_plan:
                    remaining_sizes = dca_plan['all_dca_sizes'][used_entries:]
                    analysis['reserved_for_dca'] += sum(remaining_sizes)
                else:
                    self.create_dca_plan(trade)
                    dca_plan = self.dca_plans.get(trade_key)
                    if dca_plan:
                        remaining_sizes = dca_plan['all_dca_sizes'][used_entries:]
                        analysis['reserved_for_dca'] += sum(remaining_sizes)

                if trade.is_open and self.dp:
                    current_rate = self.dp.ticker(
                        trade.pair).get('last', trade.open_rate)
                    if trade.calc_profit_ratio(current_rate) < 0:
                        analysis['positions_in_loss'] += 1

            analysis['count'] = len(open_trades)
            logger.debug(f"Анализ открытых сделок: {analysis}")
        except Exception as e:
            logger.error(
                f"Ошибка при анализе открытых сделок: {e}", exc_info=True)
            pass
        return analysis

    def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
        if pair in self.custom_entry_prices:
            return self.custom_entry_prices.pop(pair)
        return proposed_rate * (1 - self.ENTRY_PRICE_BUFFER if side == 'long' else 1 + self.ENTRY_PRICE_BUFFER)

    def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime,
                          proposed_rate: float, current_profit: float, **kwargs: Any) -> float:
        return proposed_rate * (1 + self.EXIT_PRICE_BUFFER if not trade.is_short else 1 - self.EXIT_PRICE_BUFFER)

    def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
                    current_profit: float, **kwargs: Any) -> Optional[str]:
        return None

    def _get_volatility_based_risk_multiplier(self, pair: str) -> float:
        try:
            volatility_scores, weights = [], []

            inf_timeframes = []
            if self.USE_INFORMATIVE_15m:
                inf_timeframes.append('15m')
            if self.USE_INFORMATIVE_30m:
                inf_timeframes.append('30m')
            if self.USE_INFORMATIVE_1h:
                inf_timeframes.append('1h')
            if self.USE_INFORMATIVE_4h:
                inf_timeframes.append('4h')

            all_tfs = ['5m'] + inf_timeframes
            tf_weights = {'5m': float(0.4), '15m': float(0.3),
                          '30m': float(0.15), '1h': float(0.1), '4h': float(0.05)}

            for tf in all_tfs:
                try:
                    dataframe, _ = self.dp.get_analyzed_dataframe(pair, tf)
                    if dataframe is not None and not dataframe.empty and len(dataframe) >= 20:
                        recent_candles = dataframe.tail(20)
                        atr_val = ta.ATR(recent_candles, timeperiod=14)
                        if atr_val is not None and not atr_val.empty and pd.notna(atr_val.iloc[-1]) and atr_val.iloc[-1] > 0:
                            atr_percent = atr_val.iloc[-1] / \
                                recent_candles['close'].iloc[-1]
                            volatility_scores.append(atr_percent)
                            weights.append(tf_weights.get(tf, float(0.1)))
                except Exception:
                    continue

            if not volatility_scores or not weights or sum(weights) == 0:
                return float(2.0)

            weighted_volatility = float(np.average(
                volatility_scores, weights=weights))
            leverage_factor = float(np.interp(weighted_volatility, [0.005, 0.015, 0.03, 0.05],
                                              [0.1, 0.3, 0.6, 0.9]) if weighted_volatility <= 0.05 else 1.0)
            return leverage_factor
        except Exception:
            return float(0.5)

    def _analyze_multi_timeframe_volatility(self, pair: str) -> Optional[dict]:
        try:
            volatility_scores, weights = [], []
            inf_timeframes = []
            if self.USE_INFORMATIVE_15m:
                inf_timeframes.append('15m')
            if self.USE_INFORMATIVE_30m:
                inf_timeframes.append('30m')
            if self.USE_INFORMATIVE_1h:
                inf_timeframes.append('1h')
            if self.USE_INFORMATIVE_4h:
                inf_timeframes.append('4h')

            all_tfs = ['5m'] + inf_timeframes
            tf_weights = {'5m': float(0.2), '15m': float(0.2),
                          '30m': float(0.3), '1h': float(0.2), '4h': float(0.1)}

            for tf in all_tfs:
                df, _ = self.dp.get_analyzed_dataframe(pair, tf)
                if len(df) >= 50:
                    recent = df.tail(50)
                    price_vol = recent['close'].pct_change().std()
                    hl_vol = ((recent['high'] - recent['low']
                               ) / recent['close']).mean()
                    volatility_scores.append(price_vol * 0.7 + hl_vol * 0.3)
                    weights.append(tf_weights.get(tf, float(0.1)))

            if not volatility_scores:
                return None

            total_weight = sum(weights)
            if total_weight == 0:
                return None

            weighted_volatility = float(np.average(
                volatility_scores, weights=weights))
            logger.debug(
                f"[{pair}] Волатильность: Взвешенная волатильность: {weighted_volatility:.4f}")

            leverage_factor = float(np.interp(weighted_volatility, [0.005, 0.015, 0.03, 0.05],
                                              [0.1, 0.3, 0.6, 0.9]) if weighted_volatility <= 0.05 else 1.0)
            logger.debug(
                f"[{pair}] Волатильность: Фактор плеча: {leverage_factor:.2f}")
            return {'factor': leverage_factor, 'weight': float(0.4), 'name': 'volatility'}
        except Exception:
            logger.exception(
                f"[{pair}] Ошибка в _analyze_multi_timeframe_volatility. Возврат дефолтного фактора.")
            return {'factor': float(0.5), 'weight': float(0.4), 'name': 'volatility_fail'}

    def _analyze_multi_timeframe_trend(self, pair: str) -> Optional[dict]:
        try:
            trend_scores, weights = [], []
            inf_timeframes = []
            if self.USE_INFORMATIVE_15m:
                inf_timeframes.append('15m')
            if self.USE_INFORMATIVE_30m:
                inf_timeframes.append('30m')
            if self.USE_INFORMATIVE_1h:
                inf_timeframes.append('1h')
            if self.USE_INFORMATIVE_4h:
                inf_timeframes.append('4h')

            all_tfs = ['5m'] + inf_timeframes
            tf_weights = {'5m': float(0.2), '15m': float(0.2),
                          '30m': float(0.3), '1h': float(0.2), '4h': float(0.1)}

            for tf in all_tfs:
                df, _ = self.dp.get_analyzed_dataframe(pair, tf)
                if len(df) >= 100:
                    recent = df.tail(100)
                    ema21, ema50, ema100 = ta.EMA(recent, 21), ta.EMA(
                        recent, 50), ta.EMA(recent, 100)
                    strength = float(0)
                    if ema21 is not None and ema50 is not None and ema100 is not None and not ema21.empty and not ema50.empty and not ema100.empty:
                        is_uptrend = ema21.iloc[-1] > ema50.iloc[-1] and ema50.iloc[-1] > ema100.iloc[-1]
                        is_downtrend = ema21.iloc[-1] < ema50.iloc[-1] and ema50.iloc[-1] < ema100.iloc[-1]
                        if is_uptrend or is_downtrend:
                            strength += float(0.4)
                        if not recent.empty and not ema21.empty and recent['close'].iloc[-1] > ema21.iloc[-1]:
                            strength += float(0.3)
                        if len(ema21) > 5 and ema21.iloc[-5] > 0 and abs((ema21.iloc[-1] - ema21.iloc[-5]) / ema21.iloc[-5]) > 0.01:
                            strength += float(0.3)
                    trend_scores.append(strength)
                    weights.append(tf_weights.get(tf, float(0.1)))

            if not trend_scores:
                return None

            total_weight = sum(weights)
            if total_weight == 0:
                return None

            trend_strength = float(min(
                float(np.average(trend_scores, weights=weights)), float(1.0)))
            logger.debug(
                f"[{pair}] Тренд: Взвешенная сила тренда: {trend_strength:.4f}")

            leverage_factor = 1.0 - trend_strength
            logger.debug(
                f"[{pair}] Тренд: Фактор плеча: {leverage_factor:.2f}")
            return {'factor': leverage_factor, 'weight': float(0.3), 'name': 'trend'}
        except Exception:
            logger.exception(
                f"[{pair}] Ошибка в _analyze_multi_timeframe_trend. Возврат дефолтного фактора.")
            return {'factor': float(0.5), 'weight': float(0.3), 'name': 'trend_fail'}

    def _analyze_market_conditions(self, pair: str) -> dict:
        now = datetime.now(timezone.utc)
        liquidity_factor = float(0.6) if 8 <= now.hour <= 22 else float(0.2)
        weekday_factor = float(0.6) if 0 <= now.weekday() <= 4 else float(0.3)
        market_factor = liquidity_factor * \
            float(0.6) + weekday_factor * float(0.4)
        logger.debug(
            f"[{pair}] Рыночные условия: Фактор ликвидности: {liquidity_factor:.2f}, Фактор будней: {weekday_factor:.2f}, Итоговый рыночный фактор: {market_factor:.2f}")
        return {'factor': market_factor, 'weight': float(0.2), 'name': 'market'}

    def _analyze_strategy_performance(self) -> dict:
        total = self.stats.get('total_entries', 0)
        emergency = self.stats.get('emergency_dca', 0)
        if total == 0:
            logger.debug(
                f"Стратегия: Нет записей о входах. Возврат дефолтного фактора производительности.")
            return {'factor': float(0.3), 'weight': float(0.1), 'name': 'performance'}
        emergency_ratio = float(
            emergency / total) if total != 0 else float(0.0)
        performance_factor = float(np.interp(emergency_ratio, [0.0, 0.1, 0.2, 0.3], [
            0.0, 0.3, 0.6, 0.9]) if emergency_ratio <= 0.3 else 1.0)
        logger.debug(
            f"Стратегия: Всего входов: {total}, Аварийных DCA: {emergency}, Коэффициент аварийных DCA: {emergency_ratio:.4f}, Фактор производительности: {performance_factor:.2f}")
        return {'factor': performance_factor, 'weight': float(0.1), 'name': 'performance'}

    def _analyze_candle_size_risk(self, pair: str) -> dict:
        try:
            df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            if len(df) >= 5:
                candle_sizes = ((df['high'] - df['low']) / df['low']) * 100
                if not candle_sizes.empty:
                    max_recent_size = float(candle_sizes.tail(3).max())
                    logger.debug(
                        f"[{pair}] Риск свечи: Максимальный размер свечи за последние 3: {max_recent_size:.2f}%")

                    leverage_factor = float(np.interp(max_recent_size, [1.0, 2.0, 3.5, 5.0], [
                        0.1, 0.3, 0.6, 0.9]) if max_recent_size <= 5.0 else 1.0)
                    logger.debug(
                        f"[{pair}] Риск свечи: Фактор плеча: {leverage_factor:.2f}")
                    return {'factor': leverage_factor, 'weight': float(0.3), 'name': 'candle_risk'}
        except Exception:
            logger.exception(
                f"[{pair}] Ошибка в _analyze_candle_size_risk. Возврат дефолтного фактора.")
            pass
        return {'factor': float(0.5), 'weight': float(0.3), 'name': 'candle_risk_fail'}

    def should_start_trailing(self, pair: str, signals: dict, last_candle: pd.Series) -> bool:
        if not self.TRAIL_ENTRY_ENABLED or pair in self.trailing_entry_data:
            return False

        candle_size_percent = (
            (last_candle['high'] - last_candle['low']) / last_candle['low']) * 100
        if candle_size_percent > self.MAX_SIGNAL_CANDLE_SIZE_PERCENT:
            return False

        return (signals.get('bb_long', False) and signals.get('trend_long', False)) or \
               (signals.get('bb_short', False) and signals.get('trend_short', False))

    def init_trailing_entry(self, pair: str, signals: dict, last_candle: pd.Series) -> None:
        side = 'long' if (signals.get('bb_long', False)
                          and signals.get('trend_long', False)) else 'short'
        candle_size_percent = (
            (last_candle['high'] - last_candle['low']) / last_candle['low']) * 100
        is_high_volatility = candle_size_percent > self.MAX_SIGNAL_CANDLE_SIZE_PERCENT

        rsi = last_candle.get('rsi')
        is_extreme_rsi = (rsi is not None) and (
            rsi <= self.rsi_oversold or rsi >= self.rsi_overbought)

        bb_lower = last_candle.get('bb_lower')
        bb_upper = last_candle.get('bb_upper')

        if bb_lower is not None and bb_upper is not None and pd.notna(bb_lower) and pd.notna(bb_upper) and bb_upper > bb_lower:
            bb_pos = (last_candle['close'] - bb_lower) / (bb_upper - bb_lower)
        else:
            bb_pos = 0.5

        is_bb_breakout = bb_pos < 0.05 or bb_pos > 0.95

        is_htf_extreme = False
        htf_extreme_reason_parts = []
        if self.USE_ADAPTIVE_TRAILING_ENTRY:
            enabled_inf_tfs = []
            if self.USE_INFORMATIVE_15m:
                enabled_inf_tfs.append('15m')
            if self.USE_INFORMATIVE_30m:
                enabled_inf_tfs.append('30m')
            if self.USE_INFORMATIVE_1h:
                enabled_inf_tfs.append('1h')
            if self.USE_INFORMATIVE_4h:
                enabled_inf_tfs.append('4h')

            for tf in enabled_inf_tfs:
                rsi_tf = last_candle.get(f'rsi_{tf}')
                if rsi_tf and pd.notna(rsi_tf):
                    htf_trail_rsi_oversold_tf = getattr(
                        self, f'HTF_TRAIL_RSI_OVERSOLD_{tf.upper()}', self.rsi_oversold)
                    htf_trail_rsi_overbought_tf = getattr(
                        self, f'HTF_TRAIL_RSI_OVERBOUGHT_{tf.upper()}', self.rsi_overbought)
                    if (side == 'long' and rsi_tf <= htf_trail_rsi_oversold_tf) or \
                       (side == 'short' and rsi_tf >= htf_trail_rsi_overbought_tf):
                        is_htf_extreme = True
                        htf_extreme_reason_parts.append(f"RSI on {tf}")

                bb_lower_tf = last_candle.get(f'bb_lower_{tf}')
                bb_upper_tf = last_candle.get(f'bb_upper_{tf}')
                if bb_lower_tf and pd.notna(bb_lower_tf) and bb_upper_tf and pd.notna(bb_upper_tf) and bb_upper_tf > bb_lower_tf:
                    htf_trail_bb_breakout_threshold_tf = getattr(
                        self, f'HTF_TRAIL_BB_BREAKOUT_THRESHOLD_{tf.upper()}', 0.05)
                    if (side == 'long' and last_candle['close'] <= bb_lower_tf * (1 - htf_trail_bb_breakout_threshold_tf)) or \
                       (side == 'short' and last_candle['close'] >= bb_upper_tf * (1 + htf_trail_bb_breakout_threshold_tf)):
                        is_htf_extreme = True
                        htf_extreme_reason_parts.append(f"BB Breakout on {tf}")

        htf_extreme_reason = ", ".join(
            htf_extreme_reason_parts) if htf_extreme_reason_parts else ""

        reason = ""
        if is_htf_extreme:
            trigger_percent, conf_ticks = self.HTF_EXTREME_TRAIL_TRIGGER_PERCENT, self.HTF_EXTREME_TRAIL_CONFIRMATION_TICKS
            reason = f"HTF Extreme ({htf_extreme_reason})"
        elif is_extreme_rsi or is_bb_breakout:
            trigger_percent, conf_ticks = self.TRAIL_ENTRY_TRIGGER_PERCENT * \
                0.75, self.EXTREME_TRAIL_ENTRY_CONFIRMATION_TICKS
            reason = "Base TF Extreme"
        elif is_high_volatility:
            trigger_percent, conf_ticks = self.HV_TRAIL_ENTRY_TRIGGER_PERCENT, self.HV_TRAIL_ENTRY_CONFIRMATION_TICKS
            reason = "High Volatility"
        else:
            trigger_percent, conf_ticks = self.TRAIL_ENTRY_TRIGGER_PERCENT, self.TRAIL_ENTRY_CONFIRMATION_TICKS
            reason = "Normal"

        self.trailing_entry_data[pair] = {
            'side': side, 'start_time': datetime.now(timezone.utc),
            'best_price': last_candle['close'], 'last_update_time': datetime.now(timezone.utc),
            'confirmation_count': 0, 'is_high_volatility': is_high_volatility,
            'trigger_percent': trigger_percent, 'confirmation_ticks': conf_ticks,
            'is_htf_extreme': is_htf_extreme
        }

        logger.info(
            f"[{pair}] Trailing Entry Initialized ({side.upper()}). "
            f"Reason: {reason}. Trigger: {trigger_percent:.3%}, Ticks: {conf_ticks}"
        )

    def update_trailing_entry(self, pair: str, last_candle: pd.Series) -> Optional[dict]:
        if pair not in self.trailing_entry_data:
            return None
        trail_data = self.trailing_entry_data[pair]
        now = datetime.now(timezone.utc)

        if (now - trail_data['start_time']).total_seconds() > self.TRAIL_ENTRY_MAX_TIME:
            self.cleanup_trailing_entry(pair)
            return None

        current_price = float(last_candle['close'])

        if (trail_data['side'] == 'long' and current_price < trail_data['best_price']) or \
           (trail_data['side'] == 'short' and current_price > trail_data['best_price']):
            trail_data['best_price'] = current_price
            trail_data['last_update_time'] = now
            trail_data['confirmation_count'] = 0
            return None

        trigger_price = trail_data['best_price'] * (1 + trail_data['trigger_percent']
                                                    if trail_data['side'] == 'long' else 1 - trail_data['trigger_percent'])

        trigger_cond = (trail_data['side'] == 'long' and current_price >= trigger_price) or \
                       (trail_data['side'] ==
                        'short' and current_price <= trigger_price)

        if trigger_cond:
            trail_data['confirmation_count'] += 1
            if trail_data['confirmation_count'] >= trail_data['confirmation_ticks']:
                entry_price = self.calculate_trailing_entry_price(
                    current_price, trail_data)
                entry_signal = {
                    'pair': pair, 'side': trail_data['side'], 'entry_price': entry_price}
                self.cleanup_trailing_entry(pair)
                return entry_signal
        else:
            trail_data['confirmation_count'] = 0

        return None

    def calculate_trailing_entry_price(self, current_price: float, trail_data: dict) -> float:
        price = float(current_price)
        buffer = self.ENTRY_PRICE_BUFFER
        return price * (1 - buffer if trail_data['side'] == 'long' else 1 + buffer)

    def cleanup_trailing_entry(self, pair: str) -> None:
        if pair in self.trailing_entry_data:
            del self.trailing_entry_data[pair]
        if pair in self.pending_trailing_init:
            del self.pending_trailing_init[pair]
        if pair in self.signal_price_tracking:
            del self.signal_price_tracking[pair]
        if pair in self.signal_candles:
            del self.signal_candles[pair]

    def get_trailing_entry_signal(self, pair: str, last_candle: pd.Series, signals: dict) -> Optional[dict]:
        if self.INSTANT_ENTRY_MODE:
            side = 'long' if (signals.get('bb_long', False)
                              and signals.get('trend_long', False)) else 'short'
            current_price_val = float(last_candle['close'])
            if self.dp and self.dp.runmode.value in ('live', 'dry_run'):
                current_price_val = float(self.dp.ticker(
                    pair).get('last', current_price_val))

            if not self.confirm_trade_entry(pair, 'limit', 0, current_price_val, 'gtc', datetime.now(timezone.utc), None, side):
                logger.info(
                    f"[{pair}] Мгновенный вход отменен из-за фильтрации ставки финансирования.")
                return None

            entry_price = self.calculate_direct_entry_price(
                current_price_val, side)
            return {'pair': pair, 'side': side, 'entry_price': entry_price, 'direct_entry': True}

        if pair in self.trailing_entry_data:
            candle = last_candle.copy()
            if self.dp and self.dp.runmode.value in ('live', 'dry_run'):
                candle['close'] = self.dp.ticker(
                    pair).get('last', last_candle['close'])
            return self.update_trailing_entry(pair, candle)

        if self.should_start_trailing(pair, signals, last_candle):
            self.init_trailing_entry(pair, signals, last_candle)
        return None

    def calculate_direct_entry_price(self, current_price: float, side: str) -> float:
        price = float(current_price)
        buffer = self.ENTRY_PRICE_BUFFER
        return price * (1 - buffer if side == 'long' else 1 + buffer)

    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: Any) -> bool:
        real_profit_val = self.validate_profit_calculation(
            trade, rate, current_time)

        if exit_reason in ['roi', 'exit_signal']:
            if real_profit_val['has_sufficient_profit']:
                self.stats['total_exits'] += 1
                self._cleanup_trade_data(trade)
                return True
            else:
                return False

        self.stats['total_exits'] += 1
        self._cleanup_trade_data(trade)
        return True

    def validate_profit_calculation(self, trade: Trade, current_rate: float, current_time: datetime) -> dict:
        try:
            profit_ratio = trade.calc_profit_ratio(current_rate)
            leverage = trade.leverage or 1.0
            logger.debug(
                f"[{trade.pair}] Расчет прибыли: Текущая цена: {current_rate:.4f}, Прибыль без плеча: {profit_ratio:.2%}, Плечо: {leverage:.2f}x")

            leveraged_profit_ratio = profit_ratio * leverage
            logger.debug(
                f"[{trade.pair}] Расчет прибыли: Прибыль с плечом: {leveraged_profit_ratio:.2%}")

            trading_fees_ratio = (
                trade.fee_open + trade.fee_close) * leverage
            logger.debug(
                f"[{trade.pair}] Расчет прибыли: Торговые комиссии: {trading_fees_ratio:.4f}")

            funding_fees_cost = self._get_trade_funding_fees(trade)

            # Добавляем пессимистичный расчет комиссии за финансирование для повышения точности
            trade_duration_hours = (
                current_time - trade.open_date_utc).total_seconds() / 3600
            if trade_duration_hours > 8:  # Применяем для сделок дольше 8 часов
                num_funding_periods = math.floor(trade_duration_hours / 8)
                pessimistic_rate = self.MAX_FUNDING_RATE_THRESHOLD if self.USE_FUNDING_RATE_FILTER else 0.00075
                position_value = trade.stake_amount * (trade.leverage or 1.0)
                pessimistic_funding_cost = num_funding_periods * position_value * pessimistic_rate

                if pessimistic_funding_cost > funding_fees_cost:
                    logger.warning(
                        f"[{trade.pair}] Расчетная комиссия за финансирование ({funding_fees_cost:.4f}) "
                        f"ниже пессимистичной оценки ({pessimistic_funding_cost:.4f}). "
                        f"Используется большее значение для безопасности."
                    )
                    funding_fees_cost = pessimistic_funding_cost

            funding_fees_ratio = funding_fees_cost / \
                trade.stake_amount if trade.stake_amount > 0 else 0.0
            logger.debug(
                f"[{trade.pair}] Расчет прибыли: Стоимость финансирования: {funding_fees_cost:.4f}, Коэффициент финансирования: {funding_fees_ratio:.4f}")

            net_profit_ratio = leveraged_profit_ratio - \
                trading_fees_ratio - funding_fees_ratio
            net_profit_percent = net_profit_ratio * 100

            logger.debug(
                f"[{trade.pair}] Расчет выхода: P/L без плеча: {profit_ratio:.2%}, "
                f"P/L с плечом: {leveraged_profit_ratio:.2%}, Торговые комиссии: {trading_fees_ratio:.2%}, "
                f"Финансирование: {funding_fees_ratio:.2%}, Итог: {net_profit_percent:.2f}%"
            )

            return {
                'has_sufficient_profit': net_profit_percent >= self.MIN_NET_PROFIT_PERCENT,
                'profit_ratio': leveraged_profit_ratio,
                'net_profit_percent': net_profit_percent
            }
        except Exception as e:
            logger.error(
                f"Ошибка при расчете прибыли для сделки {trade.id}: {e}", exc_info=True)
            return {'has_sufficient_profit': False, 'profit_ratio': 0.0, 'net_profit_percent': 0.0}

    def _cleanup_trade_data(self, trade: Trade) -> None:
        trade_key = self.get_safe_trade_key(trade)
        if trade_key in self.dca_plans:
            del self.dca_plans[trade_key]
        if trade_key in self.last_emergency_dca_time:
            del self.last_emergency_dca_time[trade_key]
        if trade_key in self.last_dca_time:
            del self.last_dca_time[trade_key]

        if self._get_int_from_custom_data(trade, 'emergency_dca_count', 0) is not None:
            trade.set_custom_data('emergency_dca_count', None)

        self.emergency_dca_flags.pop(trade.pair, None)
        self.cleanup_trailing_entry(trade.pair)
