💬 Forum

ZaratustraV27

🏆 League #697 / 1936

remiotore/ccxt-freqtrade/my-strategies/ZaratustraV27.py · ★3 · ⑂2 · first seen 2026-07-16 · repo updated 2026-01-11

Basics mode: futures timeframe: 15m interface version: 3
Settings stoploss: -0.99 has minimal roi trailing dca
Indicators ADX ATR Bollinger_Bands RSI talib technical
Concepts dca mean_reversion trailing
15 related strategies ( identical code, similar name)

Each tile is a different kind of check — from an instant code lint to full sandboxed backtests and forward tests on recent data. Not sure what a check actually proves? See the FAQ →

Source

Download Raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these imports ---
from datetime import datetime
from pandas import DataFrame
from freqtrade.strategy import IStrategy, Trade, informative, IntParameter, CategoricalParameter
import talib.abstract as ta
from technical import qtpylib


class ZaratustraV27(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "15m"
    can_short = True
    use_exit_signal = False
    exit_profit_only = True
    position_adjustment_enable = True

    # ROI table:
    minimal_roi = {}

    # Base Stoploss:
    stoploss = -0.99

    # Trailing Stop Settings:
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True

    # Max Open Trades
    max_open_trades = 1

    # Optional Risk/Trade Management parameters
    max_dca_orders = 3
    max_exit_position_adjustment = 2

    def leverage(self, pair: str, current_time: "datetime", current_rate: float, proposed_leverage: float, max_leverage: float, side: str, **kwargs) -> float:
        return 10

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Bollinger Bands for trend context
        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe[['BBL', 'BBM', 'BBU']] = bollinger[['lower', 'mid', 'upper']]

        # RSI: Momentum and overbought/oversold indicator
        dataframe['RSI'] = ta.RSI(dataframe)

        # ADX and DI components: measure trend strength and direction
        dataframe['ADX'] = ta.ADX(dataframe)
        dataframe['PDI'] = ta.PLUS_DI(dataframe)
        dataframe['MDI'] = ta.MINUS_DI(dataframe)

        # TSF: Time Series Forecast as a trend direction indicator
        dataframe['TSF'] = ta.TSF(dataframe)

        # ATR: Average True Range to capture volatility (useful for adaptive stoploss)
        dataframe['ATR'] = ta.ATR(dataframe, timeperiod=14)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Bullish entry: Price above the mid Bollinger band and ADX confirms trend strength
        dataframe.loc[
            (
                (dataframe['close'] > dataframe['BBM']) &
                (dataframe['ADX'] > 20) &
                (dataframe['PDI'] > dataframe['MDI'])
            ),
            ['enter_long', 'enter_tag']
        ] = (1, 'Bullish trend')

        # Bearish entry: Price below the mid Bollinger band and ADX confirms trend strength
        dataframe.loc[
            (
                (dataframe['close'] < dataframe['BBM']) &
                (dataframe['ADX'] > 20) &
                (dataframe['MDI'] > dataframe['PDI'])
            ),
            ['enter_short', 'enter_tag']
        ] = (1, 'Bearish trend')

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Custom exit logic is handled via custom_exit function.
        return dataframe

    def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float,
                              current_profit: float, min_stake: float, max_stake: float, **kwargs) -> float:
        """
        Dynamically adjust trade position:
          - Scale into losing positions if RSI is favorable and TSF confirms trend.
          - Scale out of winning positions when RSI indicates exhaustion.
        """
        # Get the most recent candle data for the pair
        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        last_candle = dataframe.iloc[-1]

        rsi = last_candle['RSI']
        tsf = last_candle['TSF']
        adx = last_candle['ADX']

        filled_entries = trade.select_filled_orders('enter_short' if trade.is_short else 'enter_long')
        filled_exits = trade.select_filled_orders('exit_short' if trade.is_short else 'exit_long')

        count_entries = len(filled_entries)
        count_exits = len(filled_exits)

        # ------ Scale Into the Position (DCA) ------
        # Conditions: trade is losing, market oversold for longs or overbought for shorts,
        # the price respects the TSF line, and the overall trend is strong (ADX > 20)
        if current_profit < 0 and count_entries < self.max_dca_orders and adx > 20:
            if not trade.is_short and rsi < 40 and current_rate > tsf:
                stake_amount = min(trade.stake_amount * 1.5, max_stake)
                return stake_amount
            elif trade.is_short and rsi > 60 and current_rate < tsf:
                stake_amount = min(trade.stake_amount * 1.5, max_stake)
                return stake_amount

        # ------ Scale Out of the Position ------
        # Conditions: trade is profitable; RSI indicates potential overextension;
        # and only scale out a limited number of times.
        if current_profit > 0.05 and count_exits < self.max_exit_position_adjustment:
            if not trade.is_short and rsi > 70:
                return -(trade.amount * 0.25)  # exit 25% of the position
            elif trade.is_short and rsi < 30:
                return -(trade.amount * 0.25)

        return None