💬 Forum

VolBBSqueeze

🏆 League #401 / 1941

TraderAlice/Auto-Quant/versions/0.3.0/strategies/VolBBSqueeze.py · ★387 · ⑂47 · first seen 2026-07-16 · repo updated 2026-05-08

Basics mode: spot timeframe: 1h interface version: 3 4h 1d
Settings stoploss: -0.99 has minimal roi process only new candles startup candle count: 250
Indicators Bollinger_Bands EMA SMA talib
Concepts breakout
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
"""
VolBBSqueeze — 4h Bollinger-Band squeeze breakout, 1h entry on confirmation

Paradigm: volatility
Hypothesis: v0.2.0's VolSqueezeBreak was killed at Sharpe 0.17 — agent noted
            squeeze paradigm needs lower frequency than 1h. v0.3.0 affordance:
            move squeeze detection to 4h, then enter on 1h once break confirms.
            BB squeeze = 4h BB-width in bottom quartile of last 50 4h bars.
            Break = 4h close > 4h upper band after squeeze. Direction is
            implicitly long (we don't short on this universe). 1d EMA200
            regime gate to avoid bear-market squeeze releases that fail.
Parent: root (paradigm-inspired by v0.2.0's killed VolSqueezeBreak, restructured to MTF)
Created: pending — fill in after first commit
Status: active
Uses MTF: yes
"""

from pandas import DataFrame
import talib.abstract as ta

from freqtrade.strategy import IStrategy, informative


class VolBBSqueeze(IStrategy):
    INTERFACE_VERSION = 3

    timeframe = "1h"
    can_short = False

    minimal_roi = {"0": 100}
    stoploss = -0.99
    trailing_stop = False
    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # 4h indicators with 50-bar window need ~250 hourly bars warmup; 1d EMA200 dominates
    startup_candle_count: int = 250

    @informative("4h")
    def populate_indicators_4h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        upper, middle, lower = ta.BBANDS(
            dataframe["close"], timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0
        )
        dataframe["bb_upper"] = upper
        dataframe["bb_middle"] = middle
        dataframe["bb_lower"] = lower
        # BB width relative to its own 50-bar history — squeeze when width is in bottom quartile
        dataframe["bb_width"] = (upper - lower) / middle
        # q33 squeeze threshold (peak via r23 / r32 — tighter q20 hurts compounding, looser q50 over-trades)
        dataframe["bb_width_q33"] = dataframe["bb_width"].rolling(50).quantile(0.33)
        return dataframe

    @informative("1d")
    def populate_indicators_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["sma50"] = ta.SMA(dataframe, timeperiod=50)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        squeeze_then_break = (
            (dataframe["bb_width_4h"].shift(1) <= dataframe["bb_width_q33_4h"].shift(1))  # was squeezed
            & (dataframe["close_4h"] > dataframe["bb_upper_4h"])                          # now breaking upper band
        )
        dataframe.loc[
            squeeze_then_break
            & (dataframe["close"] > dataframe["ema200_1d"]),  # 1d bull regime
            "enter_long",
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            dataframe["close"] < dataframe["sma50"],  # 1h trend break — patient exit
            "exit_long",
        ] = 1
        return dataframe