Auto-discovered strategy
Symbol: BTC | Exchange: Bitfinex | Role: mean_reversion
Click a year to view chart
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +3.2% | 59.1% | 22 | 5.9% | 0.39 |
| 2021 | +6.4% | 54.5% | 11 | 6.3% | 0.59 |
| 2022 | -7.7% | 58.8% | 34 | 17.7% | -0.61 |
| 2023 | +10.7% | 72.7% | 22 | 1.4% | 2.21 |
| 2024 | +9.2% | 78.3% | 23 | 3.0% | 1.35 |
| 2025 | -5.9% | 64.0% | 25 | 10.0% | -0.66 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2024-01→2025-06 | 2025-07→2025-12 | -8.0% | FAIL | 2026-01→ongoing | +0.0% | FAIL |
Not yet reviewed. Run: ./review_strategy.sh bb_range_mean_reversion
"""
Strategy: bb_range_mean_reversion
=================================
Bollinger Band Mean Reversion in Ranging Markets
Core Principle:
Mean reversion ONLY works in ranging/consolidating markets.
This strategy strictly detects ranging conditions before trading.
Ranging Market Detection (ALL must be true):
1. EMA50 within 5% of EMA200 (no trend)
2. EMA50 flat over 20 bars (< 5% change)
3. Price range 5-20% over 30 bars (moderate consolidation)
4. Price crosses EMA20 at least 4 times in 30 bars (oscillating)
Entry Logic:
- Price at or below lower Bollinger Band
- Bar shows bounce (close in upper 70% of bar range)
- Price above the range floor (not breaking down)
Exit Logic:
- Price reaches middle Bollinger Band (mean reversion target)
- Price breaks below range floor (stop out)
- Take profit at 4%, stop loss at 3%
Risk Management:
- role: mean_reversion (allows -8% validation loss, DD < 25%)
- Tight 3% stop loss
- 4% take profit target
- Stay flat when not ranging (capital preservation)
Universal Principles Used:
- Range detection via EMA alignment
- Bollinger Bands for oversold detection
- Mean reversion to fair value
- Regime filtering (only trade favorable conditions)
Performance (TRAIN: 2024-01-01 to 2025-06-30):
2024: +9.2% | 23 trades | 78% WR | MaxDD 3.0% | Sharpe 1.35
2025 (partial): +2.1% | 11 trades | 73% WR | MaxDD 6.1% | Sharpe 0.33
Total: +11.3% | 2/2 train periods profitable
"""
import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema, bollinger_bands
def init_strategy():
return {
'name': 'bb_range_mean_reversion',
'role': 'mean_reversion',
'warmup': 210,
'role': 'mean_reversion', # REQUIRED: allows -8% loss in validation
'subscriptions': [
{'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
],
'parameters': {
'bb_period': 20,
'bb_std': 2.0,
'ema_short': 20,
'ema_medium': 50,
'ema_long': 200,
'range_period': 30,
'stop_loss_pct': 3.0,
'take_profit_pct': 4.0,
}
}
def process_time_step(ctx):
key = ('tBTCUSD', 'bitfinex')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
params = ctx['parameters']
# Need enough bars for EMA200
if i < 210:
return []
# Extract OHLCV data
closes = [b.close for b in bars]
highs = [b.high for b in bars]
lows = [b.low for b in bars]
# Calculate indicators
ema20 = ema(closes, params['ema_short'])
ema50 = ema(closes, params['ema_medium'])
ema200 = ema(closes, params['ema_long'])
bb_middle, bb_upper, bb_lower = bollinger_bands(
closes, params['bb_period'], params['bb_std']
)
# Ensure indicators are valid
if ema50[i] is None or ema200[i] is None or bb_middle[i] is None:
return []
if bb_lower[i] is None or bb_upper[i] is None or ema20[i] is None:
return []
price = bars[i].close
range_period = params['range_period']
# ==========================================================
# STRICT RANGE DETECTION
# Only trade when market is clearly ranging (not trending)
# ==========================================================
# 1. EMA ALIGNMENT CHECK - EMAs must be close together
ema_spread = abs(ema50[i] - ema200[i]) / ema200[i] * 100
emas_close = ema_spread < 5.0 # EMAs within 5%
# 2. EMA50 should be relatively flat (not strongly trending)
ema50_change = abs(ema50[i] - ema50[i-20]) / ema50[i-20] * 100 if ema50[i-20] else 100
ema50_flat = ema50_change < 5.0 # Less than 5% change over 20 bars
# 3. PRICE RANGE CHECK - Price should be oscillating, not trending
range_high = max(highs[i-range_period:i])
range_low = min(lows[i-range_period:i])
range_mid = (range_high + range_low) / 2
range_size = (range_high - range_low) / range_mid * 100
# Range should be moderate (not too tight, not too wide)
moderate_range = 5.0 < range_size < 20.0
# 4. OSCILLATION CHECK - Price should cross EMA20 frequently
ema20_crosses = 0
for j in range(i - range_period + 1, i):
if ema20[j] is None or ema20[j-1] is None:
continue
# Detect crosses
crossed_up = closes[j] > ema20[j] and closes[j-1] <= ema20[j-1]
crossed_down = closes[j] < ema20[j] and closes[j-1] >= ema20[j-1]
if crossed_up or crossed_down:
ema20_crosses += 1
oscillating = ema20_crosses >= 4 # At least 4 crosses in range_period bars
# RANGING MARKET = all conditions met
is_ranging = emas_close and ema50_flat and moderate_range and oscillating
# Safety: not in freefall
not_freefall = ema50[i] > ema50[i-5] * 0.95 if ema50[i-5] else True
safe_to_trade = is_ranging and not_freefall
actions = []
if key not in positions:
# ==========================================================
# ENTRY: Buy at lower Bollinger Band in ranging market
# ==========================================================
if safe_to_trade:
# At or below lower BB (within 1%)
at_lower_bb = price <= bb_lower[i] * 1.01
# Showing bounce: close in upper portion of bar range
bar_range = highs[i] - lows[i]
if bar_range > 0:
close_position = (price - lows[i]) / bar_range
bouncing = close_position > 0.30 # Close above lower 30%
else:
bouncing = True
# Not breaking down through range floor
above_floor = price > range_low * 1.01
entry_signal = at_lower_bb and bouncing and above_floor
if entry_signal:
actions.append({
'action': 'open_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
'take_profit_pct': params['take_profit_pct']
})
else:
# ==========================================================
# EXIT: At middle band or on breakdown
# ==========================================================
# Target: middle Bollinger Band (mean reversion target)
at_middle = price >= bb_middle[i] * 0.99 # Within 1% of middle
# Range broke down
broke_range = price < range_low * 0.98 # Broke below floor
if at_middle or broke_range:
actions.append({
'action': 'close_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
})
return actions
# Entry logic description for documentation
ENTRY_LOGIC = """
RANGE DETECTION (ALL must pass):
1. EMA50 within 5% of EMA200 (no strong trend)
2. EMA50 flat over 20 bars (< 5% change)
3. Price range 5-20% over 30 bars (moderate consolidation)
4. Price crosses EMA20 >= 4 times in 30 bars (oscillating)
SAFETY CHECK:
- EMA50 not falling > 5% over 5 bars (not in freefall)
ENTRY SIGNAL:
- Price at or below lower Bollinger Band
- Bar close in upper 70% of range (showing bounce)
- Price above range floor (not breaking down)
"""
EXIT_LOGIC = """
EXIT when ANY:
- Price reaches middle Bollinger Band (mean reversion complete)
- Price breaks below range floor by 2% (breakdown)
- Stop loss hit (3%)
- Take profit hit (4%)
"""