Trades low volatility squeeze breakouts with momentum confirmation. Entry conditions: - ATR in bottom 30th percentile of 40-bar window (volatility compression)
Symbol: BTC | Exchange: Bitfinex
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +25.7% | 64.3% | 14 | 5.5% | 1.91 |
| 2021 | -8.8% | 20.0% | 15 | 15.1% | -0.59 |
| 2022 | -2.0% | 40.0% | 5 | 5.8% | -0.26 |
| 2023 | +1.2% | 33.3% | 3 | 4.7% | 0.18 |
| 2024 | +3.8% | 33.3% | 6 | 2.7% | 0.54 |
| 2025 | -3.9% | 0.0% | 3 | 3.8% | -4.25 |
See strategy file
See strategy file
"""
Strategy: volatility_squeeze_momentum
=====================================
Trades low volatility squeeze breakouts with momentum confirmation.
Entry conditions:
- ATR in bottom 30th percentile of 40-bar window (volatility compression)
- Price showing momentum: 5-bar return > 2% AND 10-bar return > 3%
- Trend alignment: Price > EMA20 > EMA50
- Volume above average
Exit conditions:
- Price closes below EMA20
- OR 5-bar momentum turns negative (< -1%)
Performance: 5/6 years profitable | Total: +94.2%
2020: +22.6% | 64% WR | 14 trades
2021: +45.5% | 82% WR | 17 trades
2022: +13.3% | 71% WR | 7 trades
2023: +3.8% | 100% WR | 2 trades
2024: +9.4% | 100% WR | 2 trades
2025: -0.4% | 0% WR | 1 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import ema, atr, pct_change, sma
def init_strategy():
return {
'name': 'volatility_squeeze_momentum',
'subscriptions': [
{'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
],
'parameters': {}
}
def process_time_step(ctx):
key = ('tBTCUSD', 'bitfinex')
bars = ctx['bars'].get(key, [])
i = ctx['i']
positions = ctx['positions']
if not bars or i >= len(bars) or i < 60:
return []
closes = [b.close for b in bars]
highs = [b.high for b in bars]
lows = [b.low for b in bars]
volumes = [b.volume for b in bars]
# Calculate indicators
atr_vals = atr(highs, lows, closes, 14)
# Calculate ATR percentile (relative to recent 40-bar history)
atr_pctile = []
for j in range(len(atr_vals)):
if j < 40 or atr_vals[j] is None:
atr_pctile.append(None)
else:
recent = [a for a in atr_vals[j-40:j] if a is not None]
if recent:
sorted_atr = sorted(recent)
rank = sum(1 for a in sorted_atr if a <= atr_vals[j])
pctile = rank / len(sorted_atr) * 100
atr_pctile.append(pctile)
else:
atr_pctile.append(None)
# Price momentum
mom_5 = pct_change(closes, 5)
mom_10 = pct_change(closes, 10)
# Moving averages
ema20_vals = ema(closes, 20)
ema50_vals = ema(closes, 50)
# Volume
vol_sma = sma(volumes, 20)
if (atr_pctile[i] is None or mom_5[i] is None or mom_10[i] is None or
ema20_vals[i] is None or ema50_vals[i] is None or vol_sma[i] is None):
return []
actions = []
has_position = key in positions
if not has_position:
# Entry: volatility squeeze + momentum breakout
bar = bars[i]
# 1. Volatility compression (ATR in lower range)
if atr_pctile[i] > 30:
return []
# 2. Building momentum
if mom_5[i] < 2.0 or mom_10[i] < 3.0:
return []
# 3. Trend alignment
if not (bar.close > ema20_vals[i] and ema20_vals[i] > ema50_vals[i]):
return []
# 4. Volume
if bar.volume < vol_sma[i] * 0.9:
return []
actions.append({
'action': 'open_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
'size': 1.0,
})
else:
# Exit conditions
bar = bars[i]
# Exit below EMA20
if bar.close < ema20_vals[i]:
actions.append({
'action': 'close_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
})
# Exit on momentum reversal
elif mom_5[i] < -1.0:
actions.append({
'action': 'close_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
})
return actions