💬 Forum

FuturesSwingStrategy

🏆 League #60 / 1927

Chris6622/class-AdvancedFuturesSwingStrategy-IStrategy-/user_data/strategies/futures_strategy.py · first seen 2026-07-16 · repo updated 2026-06-14 · ⬇ 1 download

Basics mode: futures timeframe: 4h interface version: 3
Settings stoploss: -0.282 has minimal roi trailing custom stoploss startup candle count: 250 hyperopt hyperopt params: 4
Indicators ATR EMA MACD talib
Concepts 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
# --- Do not remove these libs ---
import numpy as np
import pandas as pd
from pandas import DataFrame
from datetime import datetime
from typing import Optional, List

from freqtrade.strategy import (IStrategy, IntParameter, DecimalParameter, CategoricalParameter)
from freqtrade.persistence import Trade
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib


class FuturesSwingStrategy(IStrategy):
    """
    This is the final, robust hyperoptable version.
    It separates static defaults from hyperopt spaces and correctly assigns
    parameters to the 'buy' and 'sell' spaces for full compatibility.
    """
    INTERFACE_VERSION = 3
    timeframe = '4h'
    can_short: bool = True

    # --- Static Default Parameters for Backtesting & Initial Validation ---
    minimal_roi = {
        "0": 0.862,
        "1538": 0.312,
        "3044": 0.1,
        "7553": 0
    }
    
    stoploss = -0.282
    trailing_stop = True
    trailing_stop_positive = 0.015
    trailing_stop_positive_offset = 0.041
    trailing_only_offset_is_reached = True

    # --- Hyperoptable Entry Parameters (buy space) ---
    # These are defined directly in the class body.
    # The 'space' argument assigns them to the 'buy' hyperopt space.
    ema_fast_period = IntParameter(10, 60, default=19, space="buy", optimize=True)
    ema_slow_period = IntParameter(100, 250, default=175, space="buy", optimize=True)
    atr_period = IntParameter(5, 20, default=11, space="buy", optimize=True)
    atr_multiplier = DecimalParameter(1.0, 5.0, default=1.0, decimals=1, space="buy", optimize=True)


    # --- Strategy Settings ---
    startup_candle_count: int = 250
    
    order_types = {
        'entry': 'market',
        'exit': 'market',
        'stoploss': 'market',
        'stoploss_on_exchange': False,
    }
    order_time_in_force = {'entry': 'gtc', 'exit': 'gtc'}

    # --- Hyperopt Space for Exit Parameters ---

    def populate_exit_trend_space(self) -> List[any]:
        """
        Defines the hyperopt space for parameters used in exit logic.
        These are defined here because they are validated by Freqtrade at startup.
        """
        return [
            DecimalParameter(0.05, 0.25, default=0.15, name='stoploss'),
            IntParameter(10, 240, name='roi_t1'),
            DecimalParameter(0.01, 0.10, name='roi_p1'),
            CategoricalParameter([True, False], default=True, name='trailing_stop'),
            DecimalParameter(0.01, 0.10, default=0.03, name='trailing_stop_positive_offset'),
            DecimalParameter(0.005, 0.05, default=0.01, name='trailing_stop_positive'),
            CategoricalParameter([True, False], default=True, name='trailing_only_offset_is_reached'),
        ]

    # --- Indicator & Signal Logic ---

    def informative_pairs(self):
        return []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # MACD
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        
        # EMAs - uses the value from the hyperoptable parameter
        dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=self.ema_fast_period.value)
        dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=self.ema_slow_period.value)

        # ATR - uses the value from the hyperoptable parameter
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=self.atr_period.value)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['ema_fast'] > dataframe['ema_slow']) &
                (qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal']))
            ),
            'enter_long'] = 1

        dataframe.loc[
            (
                (dataframe['ema_fast'] < dataframe['ema_slow']) &
                (qtpylib.crossed_below(dataframe['macd'], dataframe['macdsignal']))
            ),
            'enter_short'] = 1
        
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exits are primarily handled by ROI, Stoploss, and Trailing Stop
        return dataframe

    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:
        
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        if last_candle is not None and 'atr' in last_candle and last_candle['atr'] > 0:
            return (last_candle['atr'] * self.atr_multiplier.value) / current_rate
        
        # Fallback to the stoploss defined in the exit space or static default
        return self.stoploss

    def leverage(self, **kwargs) -> float:
        return 3.0