File size: 3,767 Bytes
40d4cd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""

Test Bollinger Band Width Filter on 15-Minute Strategy

------------------------------------------------------

Goal: Avoid trading during "Squeezes" (low volatility) to reduce whipsaws.

Hypothesis: Only entering when Band Width > Threshold (expansion) will improve performance.

"""

import pandas as pd
import numpy as np
import sys
import os
from typing import Dict, Any

sys.path.append(os.path.dirname(os.path.abspath(__file__)))
import config
from core.backtester import Backtester

DATA_FILE = os.path.join(config.DATA_DIR, "btc_15min_binance.csv")
DF_15M = pd.read_csv(DATA_FILE)
DF_15M['timestamp'] = pd.to_datetime(DF_15M['timestamp'])
DF_15M.set_index('timestamp', inplace=True)

def calculate_bb_width(df, period=20, std_dev=2.0):
    """Calculate Bollinger Band Width."""
    df = df.copy()
    sma = df['close'].rolling(period).mean()
    std = df['close'].rolling(period).std()
    upper = sma + (std * std_dev)
    lower = sma - (std * std_dev)
    
    # Width normalized by price level
    df['bb_width'] = (upper - lower) / sma
    return df['bb_width']

def run_strategy_with_bb_width(width_threshold):
    df = DF_15M.copy()
    
    # Indicators (MA 10/100 Baseline)
    df['fast'] = df['close'].ewm(span=10).mean()
    df['slow'] = df['close'].ewm(span=100).mean()
    df['bb_width'] = calculate_bb_width(df)
    
    # ATR for Stops
    tr = (df['high'] - df['low']).rolling(14).mean()
    df['atr'] = tr
    
    # Signals
    # Only signal if Crossover AND BB Width > Threshold
    signals = pd.Series(0, index=df.index)
    
    long_cond = (df['fast'] > df['slow']) & (df['bb_width'] > width_threshold)
    short_cond = (df['fast'] < df['slow']) & (df['bb_width'] > width_threshold)
    
    signals[long_cond] = 1
    signals[short_cond] = -1
    
    # Stops (Same as Baseline)
    avg_atr_pct = (df['atr'] / df['close']).mean()
    if pd.isna(avg_atr_pct): avg_atr_pct = 0.005
    stop_dist = avg_atr_pct * 3.0
    
    class DummySignal:
        def get_params(self): return {"model": f"BBW_{width_threshold}"}
        
    backtester = Backtester(
        data=df,
        features=df,
        signals=signals,
        signal_model=DummySignal(),
        e_base=1000.0,
        leverage=2.0,
        risk_fraction=0.02,
        stop_distance=stop_dist,
        tp_distance=stop_dist * 3.0,
        d_max=0.40,
        commission=0.0,
        slippage=0.0001,
        verbose=False
    )
    
    results = backtester.run()
    m = results['metrics']
    
    total_days = (df.index[-1] - df.index[0]).days
    trades_per_day = m['num_trades'] / total_days if total_days > 0 else 0
    
    return {
        'threshold': width_threshold,
        'return': m['total_return_pct'],
        'trades': m['num_trades'],
        'trades_per_day': trades_per_day,
        'win_rate': m['win_rate'],
        'sharpe': m['sharpe_ratio'],
        'drawdown': m['max_drawdown_pct']
    }

def test_bb_width():
    print("\n" + "="*70)
    print("TESTING BOLLINGER BAND WIDTH FILTER (15-Minute)")
    print("="*70)
    print("Baseline: MA 10/100 | Stop 3.0ATR | 2x Leverage")
    print("-" * 70)
    print(f"{'BB Width >':<15} {'Return':<10} {'Trades':<10} {'Win Rate':<10} {'Drawdown':<10} {'Sharpe':<10}")
    print("-" * 70)
    
    # Width usually ranges from 0.005 (squeeze) to 0.05+ (expansion) on 15m
    thresholds = [0.0, 0.005, 0.01, 0.015, 0.02]
    
    for t in thresholds:
        r = run_strategy_with_bb_width(t)
        print(f"{t:<15} {r['return']:>8.2f}% {r['trades']:>9} {r['win_rate']:>9.1f}% {r['drawdown']:>9.1f}% {r['sharpe']:>9.2f}")

    print("-" * 70)

if __name__ == "__main__":
    test_bb_width()