💬 Forum

BtcTrendPassive

🏆 League #424 / 1941

safakavciis123-netizen/-safak-/user_data/strategies/BtcTrendPassive.py · first seen 2026-07-16 · repo updated 2026-05-20

Basics mode: spot timeframe: 4h interface version: 3
Settings stoploss: -0.1 has minimal roi trailing process only new candles startup candle count: 200
Indicators RSI SMA talib
Concepts mean_reversion trailing
2 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 →

  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
"""
BtcTrendPassive — Sprint 7 winning strategy

The simplest strategy that empirically beat 7 sprint of complex alternatives:

  ENTRY: Long when BTC daily MA200 trending up AND coin's own trend bullish
  EXIT:  When BTC regime ends OR coin's trend breaks OR -10% stop loss

Validated:
  - Sprint 7 Test 1 (4y full): Sharpe 0.3-0.6 on top 5 coins
  - Beats single-coin + complex strategies
  - Mütevazı edge but robust + understandable

Universe: BTC, ETH, SOL, BNB, XRP (top 5 by Sharpe in Sprint 7)
Timeframe: 4h (daily-equivalent response, intra-candle granularity)

Position sizing: equal weight 20% per coin (5 coins → 100% when full bull)
Risk overlay:
  - Stop loss: -10% per trade
  - ROI exits: avoid premature, let trends run (15%/10%/5%)
  - Trailing stop: lock gains after +10%
"""

from datetime import datetime
from typing import Optional
import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame

from freqtrade.strategy import IStrategy


class BtcTrendPassive(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = '4h'
    can_short = False  # spot for paper trade, can enable futures later
    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False
    startup_candle_count = 200  # need MA200

    # ---- Risk parameters (let trends run, hard stop) ----
    minimal_roi = {
        "0":    0.15,   # take 15% if available immediately
        "1440": 0.10,   # 10% after 1 day
        "4320": 0.05,   # 5% after 3 days
        "10080": 0,     # break even after 1 week
    }
    stoploss = -0.10    # -10% hard stop

    trailing_stop = True
    trailing_stop_positive = 0.03           # trail at +3% drawdown from peak
    trailing_stop_positive_offset = 0.10    # only after +10% profit
    trailing_only_offset_is_reached = True

    # ---- Inform pairs (BTC trend regime) ----
    def informative_pairs(self):
        return [("BTC/USDT", self.timeframe)]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Own coin's indicators
        dataframe['ma_50'] = ta.SMA(dataframe, timeperiod=50)
        dataframe['ma_100'] = ta.SMA(dataframe, timeperiod=100)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        # Volume sanity (24h average)
        dataframe['vol_avg_24'] = dataframe['volume'].rolling(24).mean()

        # BTC regime indicator (informative pair)
        try:
            from freqtrade.data.dataprovider import DataProvider
            btc = self.dp.get_pair_dataframe(pair="BTC/USDT", timeframe=self.timeframe)
            btc['btc_ma_200'] = ta.SMA(btc, timeperiod=200)
            btc['btc_bull_regime'] = (btc['close'] > btc['btc_ma_200']).astype(int)

            # Merge BTC regime onto our dataframe (latest known)
            btc_lite = btc[['date', 'btc_bull_regime', 'close', 'btc_ma_200']].rename(
                columns={'close': 'btc_close', 'btc_ma_200': 'btc_ma_200'}
            )
            dataframe = dataframe.merge(btc_lite, on='date', how='left')
            dataframe['btc_bull_regime'] = dataframe['btc_bull_regime'].ffill().fillna(0)
        except Exception:
            # Fallback: assume bull (skip regime filter — not ideal but graceful)
            dataframe['btc_bull_regime'] = 1

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['btc_bull_regime'] == 1)            # BTC > MA(200): bull regime
                & (dataframe['close'] > dataframe['ma_50'])     # coin's own trend up
                & (dataframe['rsi'] < 70)                       # not overbought
                & (dataframe['volume'] > 0)                     # liquidity check
                & (dataframe['vol_avg_24'] > 0)
            ),
            'enter_long'
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['btc_bull_regime'] == 0)            # BTC dropped below MA(200): regime exit
                | (dataframe['close'] < dataframe['ma_100'])    # coin trend break
            ),
            'exit_long'
        ] = 1
        return dataframe