# source: https://raw.githubusercontent.com/titouannwtt/freqtrade-ultimate/d5e14b95d458bc14b44e7fc6fcf1784e4311b9ac/.uncompacted_edge_strategies_backup/edge_strategy9.py
# directory_url: https://github.com/titouannwtt/freqtrade-ultimate/blob/main/.uncompacted_edge_strategies_backup/
# User: titouannwtt
# Repository: freqtrade-ultimate
# --------------------# =============================================================================
# Promoted: 2026-05-24 15:16:49 UTC
# Source:
#   generator   = strategies_generator_v14_short
#   fthypt      = strategy_strategies_generator_v14_short_2026-05-24_12-05-22.fthypt
#   best_epoch  = 122
#   iteration   = 1
#   seed        = 17277
#   config      = /home/moutonneux/freqtrade/backtest_configs/hl_150pairs_mot3.json
#
# Validation results (out-of-sample):
#   timerange       = 20241117-20260517
#   trades          = 11
#   profit          = 1.22%
#   max_drawdown    = 3.76%
#   sharpe          = 0.021215455189529442
#   deflated_sharpe = None
#   p-value         = None
# Walk-forward windows:
#    wf_1 (20241117-20250518): 3 trades, profit=-1.99%, dd=3.76%, sharpe=-0.06962282031353315
#    wf_2 (20250518-20251116): 6 trades, profit=+2.89%, dd=2.20%, sharpe=0.2388390101139699
#    wf_3 (20251116-20260517): 3 trades, profit=+0.13%, dd=2.15%, sharpe=0.00768069624898791
#   positive_ratio = 2/3
# Validation thresholds used:
#   max_drawdown = 0.25
#   max_permutation_pvalue = 0.15
#   min_dsr = 0.5
#   min_oos_trades = 7
#   min_positive_windows = 0.65
#   slippage_bps = 5.0
# =============================================================================

# =============================================================================
# Github_titouannwtt_freqtrade_ultimate__edge_strategy9__20260727_134322 - standalone v14 short strategy
# Generated by strategies_generator pipeline (codegen/writer.py)
#
# This file is FULLY SELF-CONTAINED.
# - No import from external_indicators_v11
# - No import from strategies_generator_v14_*
# - All indicator classes are inlined verbatim from the source library
# - All DCA / leverage / ROI / exit / volume / volatility logic is inlined
#   with chosen hyperopt parameters frozen as class constants
#
# By construction this strategy produces IDENTICAL backtest results to running
# strategies_generator_v14_short with the same buy_params/sell_params dict.
# =============================================================================


from __future__ import annotations

import math
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Union
from functools import reduce
import logging

import numpy as np
import pandas as pd
from pandas import DataFrame, concat
from scipy import stats, signal
import warnings
warnings.simplefilter("ignore")

from ta.volatility import AverageTrueRange
import ta as clean_ta
import talib.abstract as ta

from freqtrade.persistence import Trade
from freqtrade.strategy import (
    BooleanParameter,
    CategoricalParameter,
    DecimalParameter,
    IntParameter,
    IStrategy,
    timeframe_to_minutes,
)

# ----- Imports replicated from external_indicators_v11 (needed by inlined indicators/helpers) -----
from datetime import datetime, timedelta, timezone
import os
from datetime import datetime
import time
from technical import qtpylib
from tvDatafeed import TvDatafeed, Interval
from datetime import datetime, timedelta
from pytz import UTC
from freqtrade.strategy import CategoricalParameter
from technical.util import resample_to_interval, resampled_merge
from scipy.signal import argrelextrema
from sklearn.cluster import DBSCAN
from collections import defaultdict
from sklearn.cluster import DBSCAN, KMeans
import talib
from pandas import DataFrame
from sklearn.linear_model import LinearRegression
from external_indicators_v14_addons import *

logger = logging.getLogger(__name__)


def lerp(a: float, b: float, t: float) -> float:
    return (1 - t) * a + t * b


class _FrozenParam:
    """Lightweight stand-in for a freqtrade Parameter that always returns the
    same chosen value. Used by the inlined indicator classes below so they can
    be called with the frozen size selections of this strategy without needing
    to touch the generator's optimization machinery."""
    __slots__ = ("value", "range")

    def __init__(self, val):
        self.value = val
        self.range = [val]


# ===== Inlined Indicateur base class =====
class Indicateur:
    def __init__(self, name: str, types: list, enable: bool):
        self.name = name
        self.types = types
        self.enable = enable


# ----- Inlined indicateur_VWAP -----
class indicateur_VWAP(Indicateur):
    def __init__(self):
        name = "VWAP"
        types = ["trend", "over"]
        enable = True
        Indicateur.__init__(self=self, name=name, types=types, enable=enable)

    def get_dataframe(
        self,
        dataframe,
        ps1: CategoricalParameter,
        ps2: CategoricalParameter,
        ps3: CategoricalParameter,
        type: str,
    ):
        for size1 in ps1.range:
            s1 = self.sizes["s1"].get(size1)
            for size2 in ps2.range:
                s2 = self.sizes["s2"].get(size2)
                for size3 in ps3.range:
                    s3 = self.sizes["s3"].get(size3)
                    window = int(s1*s3)
                    if window<1:
                        window=1
                    dataframe[f'{type}_VWAP_{size1}_{size2}_{size3}'] = clean_ta.volume.VolumeWeightedAveragePrice(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'], window=window).volume_weighted_average_price()
            #dataframe[f'{type}_VWAP_{size1}_{size2}_{size3}_RVWAP'] = clean_ta.volume.VolumeWeightedAveragePrice(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'], window=int(s2)).volume_weighted_average_price().rolling(int(s3)).mean()
        return dataframe

    sizes = {
        "s1": {"p1": 1, "p2": 2, "p3": 3, "p4": 4, "p5": 5, "p6": 6, "p7": 7, "p8": 8, "p9": 9, "p10": 10, "p11": 11, "p12": 12, "p13": 13, "p14": 14, "p15": 15},
        "s2": {"p1": 1, "p2": 2, "p3": 3, "p4": 4, "p5": 5, "p6": 6, "p7": 7, "p8": 8, "p9": 9, "p10": 10, "p11": 11, "p12": 12, "p13": 13, "p14": 14, "p15": 15},
        "s3": {"p1": 1.0, "p2": 1.5, "p3": 2.0, "p4": 2.5, "p5": 3.0, "p6": 3.5, "p7": 4.0, "p8": 4.25, "p9": 0.85, "p10": 0.6, "p11": 0.4, "p12": 0.3, "p13": 0.5, "p14": 0.75, "p15": 0.25},
    }

    def get_conditions(self, dataframe, s1, s2, s3, type, condition_type, condition_value, conditions, value):
        if condition_type not in ["buy", "sell"]:
            raise SyntaxError
        cv_dict = self.conditions_values.get(condition_type, {}).get(condition_value)
        if condition_value != 0 and cv_dict is None:
            return conditions
        v = cv_dict.get(value) if condition_value != 0 else None
        if condition_value == 1:
            conditions.append(
                dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v) <= dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}']
            )
        if condition_value == 2:
            conditions.append(
                dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v) >= dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}']
            )
        if condition_value == 3:
            conditions.append(
                dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v) > dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}']
            )
        if condition_value == 4:
            conditions.append(
                dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v) < dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}']
            )
        if condition_value == 5:
            conditions.append(
                (dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'] > dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v)) & (dataframe['close'] > dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'])
            )
        if condition_value == 6:
            conditions.append(
                (dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'] < dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v)) & (dataframe['close'] < dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'])
            )
        if condition_value == 7:
            conditions.append(
                dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v) < dataframe['close']
            )
        if condition_value == 8:
            conditions.append(
                dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v) > dataframe['close']
            )
        if condition_value == 9:
            conditions.append(
                (dataframe['close'] > dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}']) & (dataframe['close'].shift(v) <= dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v))
            )
        if condition_value == 10:
            conditions.append(
                (dataframe['close'] < dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}']) & (dataframe['close'].shift(v) >= dataframe[f'{type}_VWAP_{s1}_{s2}_{s3}'].shift(v))
            )
        return conditions

    conditions_values = {
        "buy": {
            1: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            2: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            3: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            4: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            5: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            6: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            7: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            8: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            9: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            10: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
        },
        "sell": {
            1: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            2: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            3: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            4: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            5: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            6: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            7: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            8: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            9: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            10: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
        },
    }


# ----- Inlined indicateur_MFI -----
class indicateur_MFI(Indicateur):
    """Money Flow Index: volume-weighted RSI."""
    def __init__(self):
        Indicateur.__init__(self, name="MFI",
                            types=["over", "momentum"], enable=True)

    sizes = {
        "s1": {"p1": 7, "p2": 10, "p3": 14, "p4": 18, "p5": 21, "p6": 25, "p7": 28, "p8": 35, "p9": 42, "p10": 50, "p11": 60, "p12": 70, "p13": 80, "p14": 100, "p15": 120},
        "s2": {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75},
        "s3": {"p1": 1, "p2": 2, "p3": 3, "p4": 4, "p5": 5, "p6": 7, "p7": 10, "p8": 14,
               "p9": 18, "p10": 21, "p11": 25, "p12": 30, "p13": 40, "p14": 50, "p15": 60},
    }

    def get_dataframe(self, dataframe, ps1, ps2, ps3, type):
        for size1 in ps1.range:
            s1 = self.sizes["s1"].get(size1)
            mfi = ta.MFI(dataframe, timeperiod=max(2, int(s1)))
            for size3 in ps3.range:
                s3 = self.sizes["s3"].get(size3)
                smooth = mfi.rolling(max(1, int(s3))).mean()
                for size2 in ps2.range:
                    pfx = f"{type}_MFI_{size1}_{size2}_{size3}"
                    dataframe[f"{pfx}_mfi"] = mfi
                    dataframe[f"{pfx}_smooth"] = smooth
        return dataframe

    conditions_values = {
        "buy": {
            1: {f"p{i}": 5 + 3 * i for i in range(1, 11)},   # MFI < v (8..35)
            2: {f"p{i}": 60 + 4 * i for i in range(1, 11)},  # MFI > v (64..100)
            3: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75}, 4: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75},
            5: {f"p{i}": 10 + 3 * i for i in range(1, 11)},  # threshold for cross from below
            6: {f"p{i}": 60 + 4 * i for i in range(1, 11)},  # threshold for cross from above
            7: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75},
            8: {f"p{i}": 5 + 5 * i for i in range(1, 11)},   # extreme deviation from 50
            9: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75}, 10: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75},
        },
    }
    conditions_values["sell"] = conditions_values["buy"]

    def get_conditions(self, dataframe, s1, s2, s3, type, condition_type, condition_value, conditions, value):
        if condition_type not in ("buy", "sell"):
            raise SyntaxError
        cv_dict = self.conditions_values.get(condition_type, {}).get(condition_value)
        if condition_value != 0 and cv_dict is None:
            return conditions
        v = cv_dict.get(value) if condition_value != 0 else None
        pfx = f"{type}_MFI_{s1}_{s2}_{s3}"
        mfi = dataframe[f"{pfx}_mfi"]
        sm = dataframe[f"{pfx}_smooth"]
        c = dataframe["close"]
        if condition_value == 1:  # MFI oversold
            conditions.append(mfi < v)
        if condition_value == 2:  # MFI overbought
            conditions.append(mfi > v)
        if condition_value == 3:  # rising
            conditions.append(mfi > mfi.shift(v))
        if condition_value == 4:  # falling
            conditions.append(mfi < mfi.shift(v))
        if condition_value == 5:  # cross v from below
            conditions.append((mfi > v) & (mfi.shift(1) <= v))
        if condition_value == 6:  # cross v from above
            conditions.append((mfi < v) & (mfi.shift(1) >= v))
        if condition_value == 7:  # smooth cross 50 up
            conditions.append((sm > 50) & (sm.shift(v) <= 50))
        if condition_value == 8:  # extreme deviation from 50
            conditions.append((mfi - 50).abs() > v)
        if condition_value == 9:  # bearish divergence (MFI overbought + price up)
            conditions.append((mfi > 70) & (c > c.shift(v)))
        if condition_value == 10:  # bullish divergence (MFI oversold + price down)
            conditions.append((mfi < 30) & (c < c.shift(v)))
        return conditions


# ----- Inlined indicateur_BollingerBands -----
class indicateur_BollingerBands(Indicateur):
    """Bollinger Bands: MA ± k*StdDev. Computes mid/high/low/pct_b/bandwidth.

    THE classic mean-reversion tool — surprisingly missing from v11.
    """
    def __init__(self):
        Indicateur.__init__(self, name="BollingerBands",
                            types=["volatility", "over"], enable=True)

    sizes = {
        "s1": {"p1": 10, "p2": 14, "p3": 16, "p4": 18, "p5": 20, "p6": 24, "p7": 28,
               "p8": 32, "p9": 40, "p10": 50, "p11": 60, "p12": 80, "p13": 100, "p14": 120, "p15": 150},
        "s2": {"p1": 1.0, "p2": 1.25, "p3": 1.5, "p4": 1.75, "p5": 2.0, "p6": 2.25, "p7": 2.5,
               "p8": 2.75, "p9": 3.0, "p10": 3.5, "p11": 1.5, "p12": 2.0, "p13": 2.5, "p14": 1.8, "p15": 2.2},
        "s3": {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75},
    }

    def get_dataframe(self, dataframe, ps1, ps2, ps3, type):
        for size1 in ps1.range:
            s1 = self.sizes["s1"].get(size1)
            for size2 in ps2.range:
                s2 = self.sizes["s2"].get(size2)
                window = max(2, int(s1))
                ma = dataframe["close"].rolling(window).mean()
                std = dataframe["close"].rolling(window).std()
                high = ma + s2 * std
                low = ma - s2 * std
                pct_b = (dataframe["close"] - low) / (high - low).replace(0, np.nan)
                bandwidth = (high - low) / ma.replace(0, np.nan)
                for size3 in ps3.range:
                    pfx = f"{type}_BB_{size1}_{size2}_{size3}"
                    dataframe[f"{pfx}_mid"] = ma
                    dataframe[f"{pfx}_high"] = high
                    dataframe[f"{pfx}_low"] = low
                    dataframe[f"{pfx}_pctb"] = pct_b
                    dataframe[f"{pfx}_bw"] = bandwidth
        return dataframe

    conditions_values = {
        "buy": {
            1: {"p1": 1, "p2": 1, "p3": 1, "p4": 1, "p5": 1, "p6": 1, "p7": 1, "p8": 1, "p9": 1, "p10": 1},
            2: {"p1": 1, "p2": 1, "p3": 1, "p4": 1, "p5": 1, "p6": 1, "p7": 1, "p8": 1, "p9": 1, "p10": 1},
            3: {"p1": 0.0, "p2": 0.05, "p3": 0.1, "p4": 0.15, "p5": 0.2, "p6": 0.25, "p7": 0.3, "p8": 0.35, "p9": 0.4, "p10": 0.5},
            4: {"p1": 0.5, "p2": 0.6, "p3": 0.65, "p4": 0.7, "p5": 0.75, "p6": 0.8, "p7": 0.85, "p8": 0.9, "p9": 0.95, "p10": 1.0},
            5: {"p1": 0.005, "p2": 0.01, "p3": 0.015, "p4": 0.02, "p5": 0.025, "p6": 0.03, "p7": 0.04, "p8": 0.05, "p9": 0.06, "p10": 0.08},
            6: {"p1": 0.02, "p2": 0.03, "p3": 0.04, "p4": 0.05, "p5": 0.06, "p6": 0.08, "p7": 0.1, "p8": 0.12, "p9": 0.15, "p10": 0.2},
            7: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75}, 8: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75}, 9: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75}, 10: {"p1": 1, "p2": 2, "p3": 3, "p4": 5, "p5": 7, "p6": 10, "p7": 14, "p8": 18, "p9": 21, "p10": 25, "p11": 30, "p12": 40, "p13": 50, "p14": 60, "p15": 75},
            11: {"p1": 10, "p2": 14, "p3": 20, "p4": 25, "p5": 30, "p6": 40, "p7": 50, "p8": 60, "p9": 75, "p10": 96},
            12: {"p1": 2, "p2": 3, "p3": 4, "p4": 5, "p5": 6, "p6": 7, "p7": 8, "p8": 10, "p9": 12, "p10": 15},
        },
    }
    conditions_values["sell"] = conditions_values["buy"]

    def get_conditions(self, dataframe, s1, s2, s3, type, condition_type, condition_value, conditions, value):
        if condition_type not in ("buy", "sell"):
            raise SyntaxError
        cv_dict = self.conditions_values.get(condition_type, {}).get(condition_value)
        if condition_value != 0 and cv_dict is None:
            return conditions
        v = cv_dict.get(value) if condition_value != 0 else None
        pfx = f"{type}_BB_{s1}_{s2}_{s3}"
        c = dataframe["close"]
        if condition_value == 1:
            conditions.append(dataframe[c.name] < dataframe[f"{pfx}_low"])
        if condition_value == 2:
            conditions.append(dataframe[c.name] > dataframe[f"{pfx}_high"])
        if condition_value == 3:
            conditions.append(dataframe[f"{pfx}_pctb"] < v)
        if condition_value == 4:
            conditions.append(dataframe[f"{pfx}_pctb"] > v)
        if condition_value == 5:
            conditions.append(dataframe[f"{pfx}_bw"] < v)
        if condition_value == 6:
            conditions.append(dataframe[f"{pfx}_bw"] > v)
        if condition_value == 7:
            conditions.append((c > dataframe[f"{pfx}_mid"])
                              & (c.shift(v) <= dataframe[f"{pfx}_mid"].shift(v)))
        if condition_value == 8:
            conditions.append((c < dataframe[f"{pfx}_mid"])
                              & (c.shift(v) >= dataframe[f"{pfx}_mid"].shift(v)))
        if condition_value == 9:
            conditions.append((c < dataframe[f"{pfx}_low"])
                              & (c.shift(v) >= dataframe[f"{pfx}_low"].shift(v)))
        if condition_value == 10:
            conditions.append((c > dataframe[f"{pfx}_high"])
                              & (c.shift(v) <= dataframe[f"{pfx}_high"].shift(v)))
        if condition_value == 11:
            bw = dataframe[f"{pfx}_bw"]
            conditions.append(bw < bw.rolling(max(2, v)).quantile(0.2))
        if condition_value == 12:
            conditions.append(c.rolling(max(2, v)).min() > dataframe[f"{pfx}_mid"])
        return conditions


# ----- Inlined indicateur_CoppockCurve -----
class indicateur_CoppockCurve(Indicateur):
    def __init__(self):
        name = "CoppockCurve"
        types = ["momentum", "trend"]
        enable = True
        Indicateur.__init__(self=self, name=name, types=types, enable=enable)

    def get_dataframe(
        self,
        dataframe,
        ps1: CategoricalParameter,
        ps2: CategoricalParameter,
        ps3: CategoricalParameter,
        type: str,
    ):
        for size1 in ps1.range:
            s1 = self.sizes["s1"].get(size1)
            for size2 in ps2.range:
                s2 = self.sizes["s2"].get(size2)
                for size3 in ps3.range:
                    s3 = self.sizes["s3"].get(size3)
                    dataframe[f"{type}_CoppockCurve_{size1}_{size2}_{size3}"] = (dataframe["close"].pct_change(int(s2)) + dataframe["close"].pct_change(int(s3))).rolling(window=s1).mean()
        return dataframe

    sizes = {
        "s1": {"p1": 3, "p2": 6, "p3": 9, "p4": 14, "p5": 20, "p6": 24, "p7": 28, "p8": 32, "p9": 40, "p10": 48, "p11": 55, "p12": 70, "p13": 90, "p14": 100, "p15": 120},
        "s2": {"p1": 3, "p2": 6, "p3": 9, "p4": 14, "p5": 20, "p6": 24, "p7": 28, "p8": 32, "p9": 40, "p10": 48, "p11": 55, "p12": 70, "p13": 90, "p14": 100, "p15": 120},
        "s3": {"p1": 3, "p2": 6, "p3": 9, "p4": 14, "p5": 20, "p6": 24, "p7": 28, "p8": 32, "p9": 40, "p10": 48, "p11": 55, "p12": 70, "p13": 90, "p14": 100, "p15": 120},
    }

    def get_conditions(self, dataframe, s1, s2, s3, type, condition_type, condition_value, conditions, value):
        if condition_type not in ["buy", "sell"]:
            raise SyntaxError
        cv_dict = self.conditions_values.get(condition_type, {}).get(condition_value)
        if condition_value != 0 and cv_dict is None:
            return conditions
        v = cv_dict.get(value) if condition_value != 0 else None
        if condition_type == "buy" or condition_type == "sell":
            cc = dataframe[f"{type}_CoppockCurve_{s1}_{s2}_{s3}"]
            if condition_value == 1:
                conditions.append(cc > v)
            if condition_value == 2:
                conditions.append(cc > cc.shift(v))
            if condition_value == 3:
                conditions.append((cc.diff(1) > 0) & (cc.diff(1) > cc.diff(1).shift(v)))
            if condition_value == 4:
                conditions.append(cc < v)
            if condition_value == 5:
                conditions.append(cc < cc.shift(v))
            if condition_value == 6:
                conditions.append((cc.diff(1) < 0) & (cc.diff(1) < cc.diff(1).shift(v)))
            if condition_value == 7:
                conditions.append((cc > 0) & (cc.shift(v) <= 0))
            if condition_value == 8:
                conditions.append((cc < 0) & (cc.shift(v) >= 0))
            if condition_value == 9:
                conditions.append(cc.diff() > cc.diff().shift(v))
            if condition_value == 10:
                conditions.append(cc.diff() < cc.diff().shift(v))
        return conditions

    conditions_values = {
        "buy": {
            1: {"p1": -75, "p2": -60, "p3": -40, "p4": -20, "p5": 0, "p6": 20, "p7": 40, "p8": 60, "p9": 75, "p10": 100},
            2: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            3: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            4: {"p1": -75, "p2": -60, "p3": -40, "p4": -20, "p5": 0, "p6": 20, "p7": 40, "p8": 60, "p9": 75, "p10": 100},
            5: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            6: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            7: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            8: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            9: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            10: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
        },
        "sell": {
            1: {"p1": -75, "p2": -60, "p3": -40, "p4": -20, "p5": 0, "p6": 20, "p7": 40, "p8": 60, "p9": 75, "p10": 100},
            2: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            3: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            4: {"p1": -75, "p2": -60, "p3": -40, "p4": -20, "p5": 0, "p6": 20, "p7": 40, "p8": 60, "p9": 75, "p10": 100},
            5: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            6: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            7: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            8: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            9: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
            10: {"p1": 1, "p2": 3, "p3": 5, "p4": 8, "p5": 15, "p6": 20, "p7": 30, "p8": 40, "p9": 50, "p10": 75},
        },
    }



# ===== Strategy class =====
class Github_titouannwtt_freqtrade_ultimate__edge_strategy9__20260727_134322(IStrategy):
    INTERFACE_VERSION = 3
    can_short = True
    timeframe = "1d"
    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = True
    startup_candle_count: int = 400
    position_adjustment_enable = True

    minimal_roi = {'0': 10.0}
    stoploss = -0.52
    trailing_stop = False
    trailing_stop_positive = None
    trailing_stop_positive_offset = 0.0
    trailing_only_offset_is_reached = False

    max_open_trades = 6

    order_types = {'entry': 'limit', 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False}
    order_time_in_force = {'entry': 'GTC', 'exit': 'GTC'}

    cust_proposed_initial_stakes: Dict[str, float] = {}

    # ----- Frozen buy params -----
    _adjust_need_entry_signal = True
    _adjust_require_volume_spike = False
    _cumulative_entry_call = 8
    _dd_throttle_floor_pct = 0.9
    _entry_filter_mode = 'block_weekend'
    _exit_conditions_type = 'AND'
    _initial_safety_order_trigger = -0.178
    _leverage_value = 2
    _max_dca_age_candles = 200
    _max_exposure_ratio = 0.5
    _max_so_multiplier_orig = 2
    _min_volume_filter = 5000000
    _momentum_buy_conditions = 14
    _momentum_buy_value = 'p9'
    _momentum_indicator = 'CoppockCurve'
    _momentum_size1 = 'p9'
    _momentum_size2 = 'p6'
    _momentum_size3 = 'p10'
    _need_cumulative_entry_call = True
    _over_buy_conditions = 0
    _over_buy_value = 'p5'
    _over_indicator = 'MFI'
    _over_size1 = 'p9'
    _over_size2 = 'p12'
    _over_size3 = 'p12'
    _overbuy_factor = 0.7
    _pair_cooldown_hours = 16
    _partial_fill_compensation_scale = 1.0
    _safety_order_step_scale = 15
    _safety_order_volume_scale = 1.2
    _so_power_w = 0.5
    _tradable_balance_ratio = 0.5
    _trailing_so_atr_period = 21
    _trend_buy_conditions = 16
    _trend_buy_value = 'p8'
    _trend_indicator = 'VWAP'
    _trend_size1 = 'p6'
    _trend_size2 = 'p14'
    _trend_size3 = 'p10'
    _use_cumulative_end_entry = False
    _use_custom_leverage = False
    _use_custom_stake = True
    _use_dca_age_limit = False
    _use_entry_filter = True
    _use_max_exposure_cap = True
    _use_min_volume_filter = True
    _use_pair_cooldown = False
    _use_position_adjustment = False
    _use_power_so_scaling = False
    _use_trailing_so_trigger = True
    _use_vol_sizing = False
    _use_volatility_filter = True
    _vol_ref_window = 500
    _vol_sizing_atr_period = 7
    _volatility_buy_conditions = 15
    _volatility_buy_value = 'p1'
    _volatility_indicator = 'BollingerBands'
    _volatility_size = 20
    _volatility_size1 = 'p3'
    _volatility_size2 = 'p4'
    _volatility_size3 = 'p3'
    _volatility_threshold = 0.053
    _volume_rolling = 96
    _volume_spike_threshold = 2.5
    _volume_spike_window = 15

    # ----- Frozen sell params -----
    _csl_atr_mult = 1.8
    _csl_giveback_ratio = 0.83
    _csl_mode = 'profit_lock'
    _csl_profit_threshold = 0.09
    _exit_only_profit = False
    _max_hold_hours = 750
    _momentum_sell_conditions = 8
    _momentum_sell_value = 'p8'
    _momentum_use_sell = True
    _my_custom_stoploss = -0.56
    _over_sell_conditions = 16
    _over_sell_value = 'p1'
    _over_use_sell = False
    _pair_lock_hours = 4
    _profit_ratio_needed = -0.078
    _timestop_hours = 48
    _timestop_profit_threshold = -0.28
    _trend_sell_conditions = 12
    _trend_sell_value = 'p10'
    _trend_use_sell = False
    _use_csl = False
    _use_my_custom_stoploss = True
    _use_pair_lock = False
    _use_timestop = True
    _use_timestop_profit_condition = True
    _volatility_sell_conditions = 0
    _volatility_sell_value = 'p5'
    _volatility_use_sell = True

    def bot_start(self, **kwargs) -> None:
        if self.dp.runmode.value in ("backtest", "hyperopt"):
            self._open_trades = []
        self.scaled_entries = {}
        self.trailing_buy_data = {}
        self.daily_profit_tracker = {}
        self.daily_trades_closed = {}
        self.partial_exits = {}
        self._entry_atr_cache = {}

    def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs):
        # Timestop: force exit after 48h
        _trade_hours = (current_time - trade.open_date_utc).total_seconds() / 3600
        if _trade_hours >= 48:
            if current_profit < -0.28:
                return f'timestop_{int(_trade_hours)}h'
        if not getattr(self, '_use_my_custom_stoploss', True):
            return None
        entry_tag = 'empty'
        if hasattr(trade, 'entry_tag') and trade.entry_tag is not None:
            entry_tag = trade.entry_tag
        if current_profit <= self._my_custom_stoploss:
            return f'stop_loss ({entry_tag})'
        return None

    def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool:
        if self._exit_only_profit:
            trade_duration_hours = (current_time - trade.open_date_utc).total_seconds() / 3600
            if trade_duration_hours >= self._max_hold_hours:
                pass
            elif trade.calc_profit_ratio(rate) < self._profit_ratio_needed:
                return False

        if trade.amount == amount and pair in self.cust_proposed_initial_stakes:
            del self.cust_proposed_initial_stakes[pair]
            if pair in self._entry_atr_cache:
                del self._entry_atr_cache[pair]
        return True

    def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: str | None, side: str, **kwargs) -> float:
        if self._use_custom_leverage:
            return float(self._leverage_value)
        return 1.0

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, **kwargs) -> float:
        if self._use_custom_stake:
            base_stake = proposed_stake / self._get_max_so_multiplier() * self._overbuy_factor
            self.cust_proposed_initial_stakes[pair] = base_stake
        else:
            base_stake = proposed_stake

        ratio = self._tradable_balance_ratio
        max_trades = self.config.get("max_open_trades", 1)
        available_wallet = self.wallets.get_total_stake_amount()
        quota = (available_wallet * ratio) / max_trades

        base_stake = min(base_stake, quota)

        if self.dp.runmode.value not in ("hyperopt", "backtest") and base_stake < 15.0:
            logger.info(f"Stake amount for {pair} is too small ({base_stake:.2f} < 15.0), adjusting to 15.0.")
        stake_final = max(15.0, base_stake)
        stake_final = min(stake_final, max_stake)

        # Cache entry ATR for trailing SO trigger
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            if dataframe is not None and not dataframe.empty:
                _entry_atr = dataframe['vol_atr_pct_21'].iloc[-1]
                if _entry_atr > 0 and np.isfinite(_entry_atr):
                    self._entry_atr_cache[pair] = _entry_atr
        except Exception:
            pass
        return stake_final

    def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float, max_stake: float, **kwargs) -> Optional[float]:
        if not self._use_position_adjustment:
            return None

        filled_buys = trade.select_filled_orders(trade.entry_side)
        count_of_buys = len(filled_buys)

        if current_profit > self._initial_safety_order_trigger:
            return None

        dataframe = None
        if self._adjust_need_entry_signal or self._adjust_require_volume_spike or True:
            dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)

        if self._adjust_need_entry_signal:
            if dataframe is None or dataframe.empty:
                return None
            entry_col = "enter_short" if self.can_short else "enter_long"
            if dataframe.iloc[-1].get(entry_col, 0) != 1:
                return None

        if self._adjust_require_volume_spike:
            if dataframe is None or dataframe.empty:
                return None
            vol_window = int(self._volume_spike_window)
            vol_threshold = self._volume_spike_threshold
            if len(dataframe) < vol_window + 1:
                return None
            recent_volume = dataframe['volume'].iloc[-1]
            avg_volume = dataframe['volume'].rolling(vol_window).mean().iloc[-1]
            if recent_volume < avg_volume * vol_threshold:
                return None

        if 1 <= count_of_buys <= self._max_so_multiplier_orig:
            isot = abs(self._initial_safety_order_trigger)
            sss = self._safety_order_step_scale
            if sss > 1:
                safety_order_trigger = isot + (isot * sss * (math.pow(sss, (count_of_buys - 1)) - 1) / (sss - 1))
            elif sss < 1:
                safety_order_trigger = isot + (isot * sss * (1 - math.pow(sss, (count_of_buys - 1))) / (1 - sss))
            else:
                safety_order_trigger = isot * count_of_buys

            # Trailing SO trigger: adjust spacing by current_ATR / entry_ATR
            if dataframe is not None and not dataframe.empty:
                _entry_atr = self._entry_atr_cache.get(trade.pair, 0)
                if _entry_atr > 0:
                    _cur_atr = dataframe['vol_atr_pct_21'].iloc[-1]
                    if _cur_atr > 0 and np.isfinite(_cur_atr):
                        _atr_ratio = max(0.5, min(3.0, _cur_atr / _entry_atr))
                        safety_order_trigger *= _atr_ratio

            if current_profit <= (-1 * abs(safety_order_trigger)):
                try:
                    _so_scale = math.pow(self._safety_order_volume_scale, (count_of_buys - 1))
                    actual_initial_stake = filled_buys[0].cost
                    stake_amount = actual_initial_stake
                    already_bought = sum(filled_buy.cost for filled_buy in filled_buys)
                    if trade.pair in self.cust_proposed_initial_stakes:
                        if self.cust_proposed_initial_stakes[trade.pair] > 0:
                            proposed_initial_stake = self.cust_proposed_initial_stakes[trade.pair]
                            current_actual_stake = already_bought * _so_scale
                            current_stake_preposition = proposed_initial_stake * _so_scale
                            current_stake_preposition_compensation = current_stake_preposition + abs(current_stake_preposition - current_actual_stake)
                            total_so_stake = lerp(current_actual_stake, current_stake_preposition_compensation, self._partial_fill_compensation_scale)
                            stake_amount = total_so_stake
                        else:
                            stake_amount = stake_amount * _so_scale
                    else:
                        stake_amount = stake_amount * _so_scale
                    return stake_amount
                except Exception as exc:
                    logger.info(f'Error adjusting position for {trade.pair}: {exc}')
                    return None
        return None

    def _get_max_so_multiplier(self) -> float:
        if self._max_so_multiplier_orig > 0:
            sovs = self._safety_order_volume_scale
            if sovs > 1:
                first_line = sovs * (math.pow(sovs, self._max_so_multiplier_orig - 1) - 1)
                divisor = sovs - 1
                return 2 + first_line / divisor
            if sovs < 1:
                first_line = sovs * (1 - math.pow(sovs, self._max_so_multiplier_orig - 1))
                divisor = 1 - sovs
                return 2 + first_line / divisor
            return self._max_so_multiplier_orig
        return self._max_so_multiplier_orig

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['volume_mean_rolling'] = dataframe['volume'].rolling(self._volume_rolling).mean()
        _atr_21 = AverageTrueRange(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], window=21).average_true_range()
        dataframe['vol_atr_pct_21'] = _atr_21 / dataframe['close']
        # trend: indicateur_VWAP sizes=p6,p14,p10
        _ind = indicateur_VWAP()
        dataframe = _ind.get_dataframe(dataframe=dataframe, ps1=_FrozenParam('p6'), ps2=_FrozenParam('p14'), ps3=_FrozenParam('p10'), type='trend')
        # over: indicateur_MFI sizes=p9,p12,p12
        _ind = indicateur_MFI()
        dataframe = _ind.get_dataframe(dataframe=dataframe, ps1=_FrozenParam('p9'), ps2=_FrozenParam('p12'), ps3=_FrozenParam('p12'), type='over')
        # volatility: indicateur_BollingerBands sizes=p3,p4,p3
        _ind = indicateur_BollingerBands()
        dataframe = _ind.get_dataframe(dataframe=dataframe, ps1=_FrozenParam('p3'), ps2=_FrozenParam('p4'), ps3=_FrozenParam('p3'), type='volatility')
        # momentum: indicateur_CoppockCurve sizes=p9,p6,p10
        _ind = indicateur_CoppockCurve()
        dataframe = _ind.get_dataframe(dataframe=dataframe, ps1=_FrozenParam('p9'), ps2=_FrozenParam('p6'), ps3=_FrozenParam('p10'), type='momentum')

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # WARNING: trend buy cv=16 has NO branch in indicateur_VWAP.get_conditions (valid: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
        # -> the generator selected a silent no-op condition; skipping the call to keep the strategy self-documenting.
        # WARNING: volatility buy cv=15 has NO branch in indicateur_BollingerBands.get_conditions (valid: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
        # -> the generator selected a silent no-op condition; skipping the call to keep the strategy self-documenting.
        # WARNING: momentum buy cv=14 has NO branch in indicateur_CoppockCurve.get_conditions (valid: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
        # -> the generator selected a silent no-op condition; skipping the call to keep the strategy self-documenting.

        if self._use_volatility_filter:
            window_size = int(self._volatility_size)
            if len(dataframe) >= window_size:
                atr = AverageTrueRange(high=dataframe['high'], low=dataframe['low'], close=dataframe['close'], window=window_size).average_true_range()
                atr_pct = atr / dataframe['close']
                conditions.append((atr_pct < self._volatility_threshold))

        if self._use_min_volume_filter:
            conditions.append((dataframe['volume_mean_rolling'] > self._min_volume_filter))

        try:
            if conditions:
                dataframe.loc[reduce(lambda x, y: x & y, conditions), 'enter_short'] = 1
                if self._need_cumulative_entry_call:
                    dataframe['enter_short'] = dataframe['enter_short'].rolling(window=self._cumulative_entry_call).min()
                if self._use_cumulative_end_entry:
                    dataframe['enter_short'] = ((dataframe['enter_short'] == 0) & (dataframe['enter_short'].shift(1) == 1)).astype(int)
            else:
                dataframe['enter_short'] = 0
        except TypeError:
            dataframe['enter_short'] = 0

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # momentum sell condition cv=8 value=p8
        _ind = indicateur_CoppockCurve()
        conditions = _ind.get_conditions(dataframe=dataframe, s1='p9', s2='p6', s3='p10', type='momentum', condition_type='sell', condition_value=8, value='p8', conditions=conditions)

        try:
            if conditions:
                if self._exit_conditions_type == "OR":
                    exit_mask = reduce(lambda x, y: x | y, conditions)
                else:
                    exit_mask = reduce(lambda x, y: x & y, conditions)
                dataframe.loc[exit_mask, 'exit_short'] = 1
            else:
                dataframe['exit_short'] = 0
        except TypeError:
            dataframe['exit_short'] = 0

        return dataframe

    def confirm_trade_entry(self, pair, order_type, amount, rate, time_in_force,
                             current_time, entry_tag, side, **kwargs):
        # Entry gate (active because use_entry_filter=True at generation).
        if current_time.weekday() in (5, 6):  # block weekend
            return False
        # Portfolio exposure cap (ratio > 0.5 = block)
        try:
            open_trades = Trade.get_open_trades()
            total_stake = sum(float(t.stake_amount or 0) * float(t.leverage or 1) for t in open_trades)
            wallet = self.wallets.get_total_stake_amount()
            if wallet > 0 and (total_stake / wallet) > 0.5:
                return False
        except Exception:
            pass
        return True
