A public-fingerprint reconstruction of `Dev_dsl2` tuned for local Bybit futures backtesting.
Timeframe
5m
Direction
Long & Short
Stoploss
-99.0%
Trailing Stop
No
ROI
0m: 10000.0%
Interface Version
3
Startup Candles
240
Indicators
12
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
import logging
from datetime import datetime
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
import pandas as pd
import talib.abstract as ta
import technical.indicators as ftt
from freqtrade.persistence import Trade
from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy, stoploss_from_open
from pandas import DataFrame, Series
logger = logging.getLogger(__name__)
def vwap_bands(dataframe: DataFrame, window: int = 20, num_of_std: float = 1.0):
df = dataframe.copy()
df["vwap"] = qtpylib.rolling_vwap(df, window=window)
rolling_std = df["vwap"].rolling(window=window).std()
df["vwap_low"] = df["vwap"] - (rolling_std * num_of_std)
df["vwap_high"] = df["vwap"] + (rolling_std * num_of_std)
return df["vwap_low"], df["vwap"], df["vwap_high"]
def vwma(series: Series, volume: Series, length: int) -> Series:
vol_sum = volume.rolling(length).sum()
return (series * volume).rolling(length).sum() / vol_sum.replace(0, np.nan)
def smma(series: Series, period: int) -> Series:
result = pd.Series(np.nan, index=series.index, dtype=float)
if len(series) < period:
return result
result.iloc[period - 1] = series.iloc[:period].mean()
for idx in range(period, len(series)):
result.iloc[idx] = ((result.iloc[idx - 1] * (period - 1)) + series.iloc[idx]) / period
return result
def dsl_rsi_level(rsi: Series, length: int) -> Series:
upper = pd.Series(np.nan, index=rsi.index, dtype=float)
lower = pd.Series(np.nan, index=rsi.index, dtype=float)
alpha = 2.0 / (length + 1.0)
upper_prev = 50.0
lower_prev = 50.0
for idx, value in enumerate(rsi.fillna(50.0)):
if value > 50.0:
upper_prev = upper_prev + alpha * (value - upper_prev)
if value < 50.0:
lower_prev = lower_prev + alpha * (value - lower_prev)
upper.iloc[idx] = upper_prev
lower.iloc[idx] = lower_prev
return upper - lower
class DevDsl2Approx(IStrategy):
"""
A public-fingerprint reconstruction of `Dev_dsl2` tuned for local Bybit futures backtesting.
The strategy keeps the original indicator family but now runs as a balanced long/short
5m futures strategy so it can be evaluated against the available 2026 Bybit USDT dataset.
"""
INTERFACE_VERSION = 3
timeframe = "5m"
can_short = True
startup_candle_count = 240
process_only_new_candles = True
minimal_roi = {"0": 100.0}
stoploss = -0.99
trailing_stop = False
use_custom_stoploss = True
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
order_types = {
"entry": "market",
"exit": "market",
"stoploss": "market",
"stoploss_on_exchange": False,
}
BUY_DEFAULTS = {
"bb_factor": 0.992,
"vwap_factor": 0.998,
"rsi_14_max": 34,
"rsi_slow_max": 42,
"rsi_84_max": 58,
"dc_percent_max": 0.32,
"bbdelta_close_min": 0.011,
"close_delta_min": 0.006,
"dsl_delta_min": 0.2,
"goodloss_max": 0.06,
"donchian_room_min": 0.010,
"volume_factor": 0.80,
"smma_length": 14,
"dc_length": 20,
"trend_relax_factor": 0.975,
"donchian_smma_factor": 0.97,
}
SELL_DEFAULTS = {
"rsi_exit_min": 62,
"fastk_exit_min": 78,
"high_offset": 1.012,
"vwap_factor": 1.002,
"profit_fade_min": 0.022,
"trend_fail_min": 0.012,
"band_exhaustion_rsi_mfi_rmi_min": 60,
}
SHORT_DEFAULTS = {
"bb_factor": 1.000,
"vwap_factor": 1.000,
"rsi_14_min": 70,
"rsi_slow_min": 56,
"rsi_84_min": 50,
"dc_percent_min": 0.85,
"dsl_delta_max": 0.0,
"goodrise_max": 0.12,
"donchian_room_min": 0.010,
"volume_factor": 0.80,
"trend_relax_factor": 0.995,
"donchian_smma_factor": 1.01,
"cover_rsi_max": 38,
"cover_fastk_max": 22,
"low_offset": 0.988,
"cover_vwap_factor": 0.998,
"band_compression_rsi_mfi_rmi_max": 40,
}
RISK_DEFAULTS = {
"hard_stoploss": -0.08,
"profit_floor_1": 0.016,
"profit_floor_2": 0.06,
"stop_1": 0.012,
"stop_2": 0.04,
"time_stop_minutes": 16 * 60,
"time_stop_profit": 0.002,
}
buy_bb_factor = DecimalParameter(
0.985,
0.999,
default=BUY_DEFAULTS["bb_factor"],
decimals=3,
space="buy",
optimize=True,
)
buy_vwap_factor = DecimalParameter(
0.990,
1.000,
default=BUY_DEFAULTS["vwap_factor"],
decimals=3,
space="buy",
optimize=True,
)
buy_rsi_14_max = IntParameter(24, 40, default=BUY_DEFAULTS["rsi_14_max"], space="buy", optimize=True)
buy_rsi_slow_max = IntParameter(30, 50, default=BUY_DEFAULTS["rsi_slow_max"], space="buy", optimize=True)
buy_rsi_84_max = IntParameter(45, 70, default=BUY_DEFAULTS["rsi_84_max"], space="buy", optimize=True)
buy_dc_percent_max = DecimalParameter(
0.12,
0.45,
default=BUY_DEFAULTS["dc_percent_max"],
decimals=2,
space="buy",
optimize=True,
)
buy_bbdelta_close_min = DecimalParameter(
0.004,
0.03,
default=BUY_DEFAULTS["bbdelta_close_min"],
decimals=3,
space="buy",
optimize=True,
)
buy_close_delta_min = DecimalParameter(
0.001,
0.02,
default=BUY_DEFAULTS["close_delta_min"],
decimals=3,
space="buy",
optimize=True,
)
buy_dsl_delta_min = DecimalParameter(
-2.0,
5.0,
default=BUY_DEFAULTS["dsl_delta_min"],
decimals=1,
space="buy",
optimize=True,
)
buy_goodloss_max = DecimalParameter(
0.02,
0.12,
default=BUY_DEFAULTS["goodloss_max"],
decimals=2,
space="buy",
optimize=True,
)
buy_donchian_room_min = DecimalParameter(
0.004,
0.04,
default=BUY_DEFAULTS["donchian_room_min"],
decimals=3,
space="buy",
optimize=True,
)
short_bb_factor = DecimalParameter(
1.001,
1.03,
default=SHORT_DEFAULTS["bb_factor"],
decimals=3,
space="sell",
optimize=True,
)
short_vwap_factor = DecimalParameter(
1.000,
1.02,
default=SHORT_DEFAULTS["vwap_factor"],
decimals=3,
space="sell",
optimize=True,
)
short_rsi_14_min = IntParameter(55, 86, default=SHORT_DEFAULTS["rsi_14_min"], space="sell", optimize=True)
short_rsi_slow_min = IntParameter(45, 80, default=SHORT_DEFAULTS["rsi_slow_min"], space="sell", optimize=True)
short_rsi_84_min = IntParameter(40, 75, default=SHORT_DEFAULTS["rsi_84_min"], space="sell", optimize=True)
short_dc_percent_min = DecimalParameter(
0.55,
0.92,
default=SHORT_DEFAULTS["dc_percent_min"],
decimals=2,
space="sell",
optimize=True,
)
short_dsl_delta_max = DecimalParameter(
-5.0,
2.0,
default=SHORT_DEFAULTS["dsl_delta_max"],
decimals=1,
space="sell",
optimize=True,
)
short_goodrise_max = DecimalParameter(
0.03,
0.14,
default=SHORT_DEFAULTS["goodrise_max"],
decimals=2,
space="sell",
optimize=True,
)
short_donchian_room_min = DecimalParameter(
0.004,
0.04,
default=SHORT_DEFAULTS["donchian_room_min"],
decimals=3,
space="sell",
optimize=True,
)
sell_rsi_exit_min = IntParameter(55, 78, default=SELL_DEFAULTS["rsi_exit_min"], space="sell", optimize=True)
sell_fastk_exit_min = IntParameter(68, 95, default=SELL_DEFAULTS["fastk_exit_min"], space="sell", optimize=True)
sell_high_offset = DecimalParameter(
1.004,
1.03,
default=SELL_DEFAULTS["high_offset"],
decimals=3,
space="sell",
optimize=True,
)
sell_vwap_factor = DecimalParameter(
1.000,
1.02,
default=SELL_DEFAULTS["vwap_factor"],
decimals=3,
space="sell",
optimize=True,
)
plot_config = {
"main_plot": {
"vwap_low": {"color": "#7a8ca5"},
"vwap_upperband": {"color": "#d17b5f"},
"dc_upper": {"color": "#47a447"},
"dc_lower": {"color": "#c95f5f"},
"tenkan_sen": {"color": "#f2b134"},
"high_offset_sma": {"color": "#b06ab3"},
"low_offset_sma": {"color": "#3a7f55"},
},
"subplots": {
"Momentum": {
"rsi_14": {"color": "#4b8bbe"},
"rsi_slow": {"color": "#306998"},
"dsl_lvl": {"color": "#ff7f0e"},
},
"StochRSI": {
"fastk_rsi": {"color": "#2ca02c"},
"fastd_rsi": {"color": "#d62728"},
},
},
}
def version(self) -> str:
return "2026.04.23-devdsl2-bybit-v2"
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
heikinashi = qtpylib.heikinashi(dataframe)
dataframe["ha_open"] = heikinashi["open"]
dataframe["ha_close"] = heikinashi["close"]
dataframe["ha_high"] = heikinashi["high"]
dataframe["ha_low"] = heikinashi["low"]
dataframe["volume_mean_4"] = dataframe["volume"].rolling(4).mean().shift(1)
dataframe["sma_15"] = ta.SMA(dataframe, timeperiod=15)
dataframe["high_offset_sma"] = dataframe["sma_15"] * self.sell_high_offset.value
dataframe["low_offset_sma"] = dataframe["sma_15"] * self.SHORT_DEFAULTS["low_offset"]
dataframe["vwma"] = vwma(dataframe["close"], dataframe["volume"], 20)
dataframe["smma_smma_length_value"] = smma(dataframe["close"], self.BUY_DEFAULTS["smma_length"])
dataframe["trend_close_5m"] = dataframe["close"]
dataframe["trend_close_15m"] = ta.EMA(dataframe["close"], timeperiod=3)
dataframe["trend_close_30m"] = ta.EMA(dataframe["close"], timeperiod=6)
dataframe["trend_close_1h"] = ta.EMA(dataframe["close"], timeperiod=12)
dataframe["trend_close_2h"] = ta.EMA(dataframe["close"], timeperiod=24)
dataframe["trend_close_4h"] = ta.EMA(dataframe["close"], timeperiod=48)
dataframe["trend_close_6h"] = ta.EMA(dataframe["close"], timeperiod=72)
dataframe["trend_open_6h"] = ta.EMA(dataframe["open"], timeperiod=72)
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe["bb_lowerband2"] = bollinger["lower"]
dataframe["bb_middleband2"] = bollinger["mid"]
dataframe["bb_upperband2"] = bollinger["upper"]
dataframe["bbdelta"] = (dataframe["bb_middleband2"] - dataframe["bb_lowerband2"]).abs()
dataframe["close_delta2"] = (dataframe["close"] - dataframe["close"].shift()).abs()
dataframe["lower_far"] = (dataframe["bb_lowerband2"] - dataframe["close"]) / dataframe["close"]
dataframe["upper_near"] = (dataframe["bb_upperband2"] - dataframe["close"]) / dataframe["close"]
dataframe["upper_far"] = (dataframe["close"] - dataframe["bb_upperband2"]) / dataframe["close"]
dataframe["lower_near"] = (dataframe["close"] - dataframe["bb_lowerband2"]) / dataframe["close"]
vwap_low, vwap_mid, vwap_high = vwap_bands(dataframe, 20, 1)
dataframe["vwap_low"] = vwap_low
dataframe["vwap_high"] = vwap_high
dataframe["vwap_upperband"] = vwap_high
dataframe["vwap_middleband"] = vwap_mid
dc_length = self.BUY_DEFAULTS["dc_length"]
dataframe["dc_upper"] = dataframe["high"].shift(1).rolling(dc_length).max()
dataframe["dc_lower"] = dataframe["low"].shift(1).rolling(dc_length).min()
dataframe["dc_middle"] = (dataframe["dc_upper"] + dataframe["dc_lower"]) / 2.0
dc_range = (dataframe["dc_upper"] - dataframe["dc_lower"]).replace(0, np.nan)
dataframe["dc_percent"] = (dataframe["close"] - dataframe["dc_lower"]) / dc_range
dataframe["dc_chf"] = (dataframe["close"] - dataframe["dc_middle"]) / dataframe["dc_middle"].replace(0, np.nan)
dataframe["rsi_14"] = ta.RSI(dataframe, timeperiod=14)
dataframe["rsi_slow"] = ta.RSI(dataframe, timeperiod=20)
dataframe["rsi_84"] = ta.RSI(dataframe, timeperiod=84)
dataframe["mfi"] = ta.MFI(dataframe, timeperiod=14)
rsi_min = dataframe["rsi_14"].rolling(14).min()
rsi_max = dataframe["rsi_14"].rolling(14).max()
stoch_rsi = 100 * (dataframe["rsi_14"] - rsi_min) / (rsi_max - rsi_min).replace(0, np.nan)
dataframe["fastk_rsi"] = stoch_rsi.rolling(3).mean()
dataframe["fastd_rsi"] = dataframe["fastk_rsi"].rolling(3).mean()
rmi_source = dataframe["close"] - dataframe["close"].shift(4)
dataframe["rmi"] = ta.RSI(rmi_source.fillna(0), timeperiod=14)
dataframe["rsi_mfi_rmi_length"] = (dataframe["rsi_14"] + dataframe["mfi"] + dataframe["rmi"]) / 3.0
momentum = dataframe["close"] - dataframe["close"].shift(5)
positive_mom = momentum.clip(lower=0)
negative_mom = (-momentum).clip(lower=0)
pmom = positive_mom.rolling(3).mean()
nmom = negative_mom.rolling(3).mean()
dataframe["positive_mom_pmom_nmom"] = ((positive_mom > negative_mom) & (pmom > nmom)).astype(int)
dataframe["positive_pmom_nmom_prev"] = (
(pmom.shift(1) > nmom.shift(1)) & (pmom.shift(2) > nmom.shift(2))
).astype(int)
dataframe["negative_mom_pmom_nmom"] = ((negative_mom > positive_mom) & (nmom > pmom)).astype(int)
dataframe["negative_pmom_nmom_prev"] = (
(nmom.shift(1) > pmom.shift(1)) & (nmom.shift(2) > pmom.shift(2))
).astype(int)
ichimoku = ftt.ichimoku(
dataframe,
conversion_line_period=20,
base_line_periods=60,
laggin_span=120,
displacement=30,
)
dataframe["tenkan_sen"] = ichimoku["tenkan_sen"]
dataframe["cloud_red"] = ichimoku["cloud_red"].astype(int)
dataframe["dsl_lvl"] = dsl_rsi_level(dataframe["rsi_14"], 7)
dataframe["strike_strike"] = np.sign(dataframe["close"].diff()).rolling(3).sum()
dataframe["averimpet_mma_length_value"] = (
(dataframe["close"] - dataframe["smma_smma_length_value"]).abs()
/ dataframe["smma_smma_length_value"].replace(0, np.nan)
)
rsi_mean = dataframe["rsi_14"].rolling(20).mean()
rsi_std = dataframe["rsi_14"].rolling(20).std()
dataframe["dev_ma_period_rsi_period_stdev_multiplier"] = rsi_mean + (rsi_std * 1.6)
dataframe["disp_up_ma_period_rsi_period_stdev_multiplier_dispersion"] = (
rsi_std / rsi_mean.abs().replace(0, np.nan)
)
dataframe["goodloss"] = ((dataframe["trend_open_6h"] - dataframe["close"]) / dataframe["close"]).clip(lower=0)
dataframe["goodrise"] = ((dataframe["close"] - dataframe["trend_open_6h"]) / dataframe["close"]).clip(lower=0)
dataframe["dsl_delta"] = dataframe["dsl_lvl"] - dataframe["dsl_lvl"].shift(1)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["enter_long"] = 0
dataframe["enter_short"] = 0
dataframe["enter_tag"] = ""
general_guard = (
(dataframe["volume"] > 0)
& (dataframe["volume"] > dataframe["volume_mean_4"] * self.BUY_DEFAULTS["volume_factor"])
& (dataframe["goodloss"] < self.buy_goodloss_max.value)
& (dataframe["upper_near"] > self.buy_donchian_room_min.value)
)
trend_guard = (
(dataframe["trend_close_6h"] > dataframe["trend_open_6h"] * self.BUY_DEFAULTS["trend_relax_factor"])
| ((dataframe["close"] > dataframe["tenkan_sen"]) & (dataframe["cloud_red"] == 0))
)
bear_guard = (
(
(dataframe["trend_close_6h"] < dataframe["trend_open_6h"] * self.SHORT_DEFAULTS["trend_relax_factor"])
& (dataframe["close"] < dataframe["trend_open_6h"])
)
| ((dataframe["close"] < dataframe["tenkan_sen"]) & (dataframe["cloud_red"] == 1))
)
deep_reversion = (
(dataframe["close"] < dataframe["vwap_low"] * self.buy_vwap_factor.value)
& (dataframe["close"] < dataframe["bb_lowerband2"] * self.buy_bb_factor.value)
& ((dataframe["bbdelta"] / dataframe["close"]) > self.buy_bbdelta_close_min.value)
& ((dataframe["close_delta2"] / dataframe["close"]) > self.buy_close_delta_min.value)
& (dataframe["rsi_14"] < self.buy_rsi_14_max.value)
& (dataframe["rsi_slow"] < self.buy_rsi_slow_max.value)
& (dataframe["rsi_84"] < self.buy_rsi_84_max.value)
& (dataframe["dc_percent"] < self.buy_dc_percent_max.value)
& (dataframe["fastk_rsi"] > dataframe["fastd_rsi"])
& (dataframe["dsl_delta"] > self.buy_dsl_delta_min.value)
& (dataframe["positive_mom_pmom_nmom"] == 1)
)
donchian_reclaim = (
(dataframe["close"] <= dataframe["dc_lower"] * 1.01)
& (dataframe["dc_chf"] < -0.01)
& (dataframe["lower_far"] < -0.005)
& (dataframe["fastk_rsi"] > dataframe["fastd_rsi"])
& (dataframe["positive_pmom_nmom_prev"] == 1)
& (dataframe["strike_strike"] <= 0)
& (dataframe["dsl_delta"] > 0)
& (
dataframe["close"]
> dataframe["smma_smma_length_value"] * self.BUY_DEFAULTS["donchian_smma_factor"]
)
)
short_general_guard = (
(dataframe["volume"] > 0)
& (dataframe["volume"] > dataframe["volume_mean_4"] * self.SHORT_DEFAULTS["volume_factor"])
& (dataframe["goodrise"] < self.short_goodrise_max.value)
)
short_reversion = (
(dataframe["close"] > dataframe["vwap_high"] * self.short_vwap_factor.value)
& (dataframe["close"] > dataframe["bb_upperband2"] * self.short_bb_factor.value)
& (dataframe["upper_far"] > 0)
& (dataframe["rsi_14"] > self.short_rsi_14_min.value)
& (dataframe["rsi_slow"] > self.short_rsi_slow_min.value)
& (dataframe["rsi_84"] > self.short_rsi_84_min.value)
& (dataframe["dc_percent"] > self.short_dc_percent_min.value)
& (dataframe["fastk_rsi"] < dataframe["fastd_rsi"])
& (dataframe["dsl_delta"] < self.short_dsl_delta_max.value)
& (dataframe["close"] > dataframe["trend_close_30m"])
)
donchian_short = (
(dataframe["close"] >= dataframe["dc_upper"] * 0.99)
& (dataframe["dc_chf"] > 0.01)
& (dataframe["upper_far"] > 0.005)
& (dataframe["fastk_rsi"] < dataframe["fastd_rsi"])
& (dataframe["strike_strike"] >= 0)
& (dataframe["dsl_delta"] < 0)
& (
dataframe["close"]
> dataframe["smma_smma_length_value"] * self.SHORT_DEFAULTS["donchian_smma_factor"]
)
)
entry_dsl_vwap = general_guard & trend_guard & deep_reversion
entry_donchian = general_guard & donchian_reclaim
entry_short_reversion = short_general_guard & bear_guard & short_reversion
entry_short_donchian = short_general_guard & bear_guard & donchian_short
dataframe.loc[entry_dsl_vwap, ["enter_long", "enter_tag"]] = (1, "dsl_vwap_reclaim")
dataframe.loc[entry_donchian, ["enter_long", "enter_tag"]] = (1, "donchian_dsl_reclaim")
dataframe.loc[entry_short_reversion, ["enter_short", "enter_tag"]] = (1, "dsl_vwap_short")
dataframe.loc[entry_short_donchian, ["enter_short", "enter_tag"]] = (1, "donchian_dsl_short")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["exit_long"] = 0
dataframe["exit_short"] = 0
dataframe["exit_tag"] = ""
vwap_extension = (
(dataframe["close"] > dataframe["vwap_upperband"] * self.sell_vwap_factor.value)
& (dataframe["rsi_14"] > self.sell_rsi_exit_min.value)
& (dataframe["fastk_rsi"] > self.sell_fastk_exit_min.value)
)
band_exhaustion = (
(dataframe["close"] > dataframe["high_offset_sma"])
& (dataframe["close"] > dataframe["bb_upperband2"])
& (dataframe["fastk_rsi"] < dataframe["fastd_rsi"])
& (dataframe["rsi_mfi_rmi_length"] > self.SELL_DEFAULTS["band_exhaustion_rsi_mfi_rmi_min"])
)
donchian_take = (
(dataframe["close"] > dataframe["dc_upper"])
& (dataframe["rsi_14"] > self.sell_rsi_exit_min.value)
& (dataframe["dsl_lvl"] < dataframe["dsl_lvl"].shift(1))
)
vwap_cover = (
(dataframe["close"] < dataframe["vwap_low"] * self.SHORT_DEFAULTS["cover_vwap_factor"])
& (dataframe["rsi_14"] < self.SHORT_DEFAULTS["cover_rsi_max"])
& (dataframe["fastk_rsi"] < self.SHORT_DEFAULTS["cover_fastk_max"])
& (dataframe["dsl_lvl"] > dataframe["dsl_lvl"].shift(1))
)
band_compression = (
(dataframe["close"] < dataframe["low_offset_sma"])
& (dataframe["close"] < dataframe["bb_lowerband2"])
& (dataframe["fastk_rsi"] > dataframe["fastd_rsi"])
& (dataframe["dc_percent"] < 0.2)
& (dataframe["rsi_mfi_rmi_length"] < self.SHORT_DEFAULTS["band_compression_rsi_mfi_rmi_max"])
)
donchian_cover = (
(dataframe["close"] < dataframe["dc_lower"])
& (dataframe["rsi_14"] < self.SHORT_DEFAULTS["cover_rsi_max"])
& (dataframe["dsl_lvl"] > dataframe["dsl_lvl"].shift(1))
)
dataframe.loc[vwap_extension, ["exit_long", "exit_tag"]] = (1, "vwap_extension")
dataframe.loc[band_exhaustion, ["exit_long", "exit_tag"]] = (1, "band_exhaustion")
dataframe.loc[donchian_take, ["exit_long", "exit_tag"]] = (1, "donchian_take")
dataframe.loc[vwap_cover, ["exit_short", "exit_tag"]] = (1, "vwap_cover")
dataframe.loc[band_compression, ["exit_short", "exit_tag"]] = (1, "band_compression")
dataframe.loc[donchian_cover, ["exit_short", "exit_tag"]] = (1, "donchian_cover")
return dataframe
def custom_stoploss(
self,
pair: str,
trade: Trade,
current_time: datetime,
current_rate: float,
current_profit: float,
**kwargs,
) -> float:
hard_stoploss = self.RISK_DEFAULTS["hard_stoploss"]
profit_floor_1 = self.RISK_DEFAULTS["profit_floor_1"]
profit_floor_2 = self.RISK_DEFAULTS["profit_floor_2"]
stop_1 = self.RISK_DEFAULTS["stop_1"]
stop_2 = self.RISK_DEFAULTS["stop_2"]
if current_profit > profit_floor_2:
stop_profit = stop_2 + (current_profit - profit_floor_2)
elif current_profit > profit_floor_1:
stop_profit = stop_1 + (
(current_profit - profit_floor_1)
* (stop_2 - stop_1)
/ (profit_floor_2 - profit_floor_1)
)
else:
stop_profit = hard_stoploss
if stop_profit >= current_profit:
return -0.99
return stoploss_from_open(
stop_profit,
current_profit,
is_short=trade.is_short,
leverage=trade.leverage,
)
def custom_exit(
self,
pair: str,
trade: Trade,
current_time: datetime,
current_rate: float,
current_profit: float,
**kwargs,
):
if self.dp is None:
return None
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe.empty:
return None
last_candle = dataframe.iloc[-1]
previous_candle = dataframe.iloc[-2] if len(dataframe) > 1 else last_candle
held_minutes = (current_time - trade.open_date_utc).total_seconds() / 60.0
if trade.is_short:
if (
current_profit > self.SELL_DEFAULTS["trend_fail_min"]
and last_candle["cloud_red"] == 0
and last_candle["close"] > last_candle["trend_open_6h"]
):
return "trend_fail_short"
if (
current_profit > self.SELL_DEFAULTS["profit_fade_min"]
and last_candle["dsl_lvl"] > previous_candle["dsl_lvl"]
):
return "profit_fade_short"
if (
held_minutes > self.RISK_DEFAULTS["time_stop_minutes"]
and current_profit > self.RISK_DEFAULTS["time_stop_profit"]
and last_candle["close"] > last_candle["trend_close_6h"]
):
return "time_stop_short"
else:
if (
current_profit > self.SELL_DEFAULTS["trend_fail_min"]
and last_candle["cloud_red"] == 1
and last_candle["close"] < last_candle["trend_open_6h"]
):
return "trend_fail"
if (
current_profit > self.SELL_DEFAULTS["profit_fade_min"]
and last_candle["dsl_lvl"] < previous_candle["dsl_lvl"]
):
return "profit_fade"
if (
held_minutes > self.RISK_DEFAULTS["time_stop_minutes"]
and current_profit > self.RISK_DEFAULTS["time_stop_profit"]
and last_candle["close"] < last_candle["trend_close_6h"]
):
return "time_stop"
return None