Auto-discovered strategy
Symbol: ETH | Exchange: Bitfinex | Role: defensive
Click a period to view chart
| Period | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | -6.0% | 35.2% | 71 | 15.1% | -0.44 |
| 2021 | +1.1% | 40.0% | 120 | 24.5% | 0.06 |
| 2022 | -0.0% | 38.5% | 78 | 12.9% | 0.00 |
| 2023 | +4.6% | 68.4% | 19 | 1.3% | 1.69 |
| 2024 | -1.3% | 45.9% | 37 | 10.4% | -0.16 |
| 2025 | +11.6% | 47.4% | 57 | 10.7% | 1.11 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2025-01→2025-09 | 2025-10→2025-12 | +4.0% | OK | 2026-01→ongoing | +0.0% | PASS |
Not yet reviewed. Run: ./review_strategy.sh defensive_vol_compression_eth
#!/usr/bin/env python3
"""
Defensive Strategy: Volatility Compression Breakout - tETHUSD
=============================================================
Role: defensive
Goal: Capital preservation - DO NOT LOSE MONEY
Strategy Logic:
1. Wait for volatility to be LOW (calm regime = ATR z-score between -0.5 and 0.0)
2. Only trade in CALM conditions with clear trend alignment (EMA spread > 0.3%)
3. Enter on pullback to EMA20 in calm volatility with strong trend bar
4. Exit quickly on ANY volatility spike or trend break
This is a "wait for calm, trade with trend, exit fast" defensive approach.
Key Features:
- Only trades in CALM volatility regime (ATR z-score -0.5 to 0.0)
- Requires clear trend (EMA10/EMA20 spread > 0.3%)
- Pullback entry (better risk/reward)
- Strong candle confirmation (bar strength > 40%)
- Quick exits at first sign of trouble
- Conservative position sizing (50%)
Entry Conditions (ALL required):
1. Calm volatility: ATR z-score between -0.5 and 0.0
2. Clear trend: EMA10 diverged from EMA20 by > 0.3%
3. Pullback: Price touches EMA20 area (within 0.3%)
4. Strong bar: Directional bar with body > 40% of range
Exit Conditions:
- Take profit: 3.0%
- Stop loss: 1.5%
- Max hold: 15 bars (~4 hours)
- Vol spike exit: ATR z-score > 1.5
- Trend break exit: EMA crossover
TRAIN PERIOD RESULTS (2025-01 to 2025-09):
- Gross Return: +9.1%
- Net Return (after slippage): +6.6%
- Max Drawdown: 4.4%
- Total Trades: 42 (~5/month)
- Profitable Months: 5/9
DEFENSIVE ROLE VALIDATION:
- Max DD < 12%: PASS (4.4%)
- Return >= 0%: PASS (+6.6% net)
- Min Trades >= 5: PASS (42)
References:
- BB/KC Squeeze: https://trendspider.com/learning-center/bb-kc-squeeze-a-powerful-indicator-for-trading-range-breakouts/
- ATR regime: https://www.mindmathmoney.com/articles/atr-indicator-trading-strategy
"""
import sys
sys.path.insert(0, '/root/trade_15m')
from lib import atr, ema, sma
from math import sqrt
from typing import List, Dict, Any
# Global indicator cache
_indicator_cache: Dict[str, Any] = {}
def init_strategy():
"""Initialize the defensive volatility compression breakout strategy."""
global _indicator_cache
_indicator_cache.clear()
return {
'name': 'defensive_vol_compression_eth',
'role': 'defensive',
'warmup': 100,
'subscriptions': [
{'symbol': 'tETHUSD', 'exchange': 'bitfinex', 'timeframe': '15m'},
],
'parameters': {
# Volatility regime detection - target "calm" conditions
'atr_period': 15,
'atr_z_lookback': 60,
'calm_z_min': -0.5, # Slightly wider low end
'calm_z_max': 0.0, # Only in low-to-normal vol
# Trend detection
'ema_fast': 10,
'ema_slow': 20,
'ema_spread_min': 0.003, # 0.3% spread for clearer trend
# Entry: pullback tolerance
'pullback_tolerance': 0.003, # 0.3% from EMA20
# Risk management - wider targets for fewer trades
'stop_loss_pct': 1.5,
'take_profit_pct': 3.0, # Better R:R
'max_hold_bars': 15, # Allow more time
# Vol spike exit
'vol_spike_z': 1.5,
}
}
def compute_indicators(bars: List) -> Dict[str, Any]:
"""
Precompute all indicators for efficiency.
Calculates:
- ATR for volatility measurement
- ATR z-score for regime detection
- EMAs for trend detection
"""
closes = [b.close for b in bars]
highs = [b.high for b in bars]
lows = [b.low for b in bars]
# ATR calculation
atr_vals = atr(highs, lows, closes, 15)
# ATR z-score with 60-bar lookback
atr_zs = [None] * len(bars)
for i in range(60, len(bars)):
if atr_vals[i] is not None:
window = [v for v in atr_vals[max(0, i-60):i] if v is not None]
if len(window) >= 30:
mean = sum(window) / len(window)
var = sum((x - mean)**2 for x in window) / len(window)
std = sqrt(var) if var > 0 else 0.001
atr_zs[i] = (atr_vals[i] - mean) / std
# EMAs for trend
ema10 = ema(closes, 10)
ema20 = ema(closes, 20)
return {
'atr': atr_vals,
'atr_z': atr_zs,
'ema10': ema10,
'ema20': ema20,
'closes': closes,
'highs': highs,
'lows': lows,
}
def process_time_step(ctx: Dict) -> List[Dict]:
"""
Process each 15-minute bar for calm volatility pullback trading.
Entry Logic:
1. Calm volatility regime (z between -0.5 and 0.5)
2. Clear trend (EMA10 > EMA20 for long, < for short)
3. Pullback to EMA20 area
4. Bar confirms direction
Exit Logic:
- Take profit at 1.5%
- Stop loss at 1.0%
- Time exit after 8 bars
- Exit on vol spike (z > 1.5)
"""
global _indicator_cache
key = ('tETHUSD', 'bitfinex')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
params = ctx['parameters']
# Compute indicators once per run
if not _indicator_cache:
_indicator_cache = compute_indicators(bars)
ind = _indicator_cache
# Safety check
if i >= len(ind['atr_z']) or ind['atr_z'][i] is None:
return []
z_now = ind['atr_z'][i]
ema10 = ind['ema10'][i]
ema20 = ind['ema20'][i]
if ema10 is None or ema20 is None:
return []
bar = bars[i]
actions = []
# =========================================================================
# VOL SPIKE EXIT - Check first
# =========================================================================
if z_now > params['vol_spike_z']:
if key in positions:
pos = positions[key]
action_type = 'close_long' if pos.side == 'long' else 'close_short'
actions.append({
'action': action_type,
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
})
return actions # No new entries during vol spike
# =========================================================================
# POSITION MANAGEMENT
# =========================================================================
if key in positions:
pos = positions[key]
bars_held = i - pos.entry_bar
# Time-based exit
if bars_held >= params['max_hold_bars']:
action_type = 'close_long' if pos.side == 'long' else 'close_short'
actions.append({
'action': action_type,
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
})
return actions
# Trend break exit - exit if trend reverses
if pos.side == 'long' and ema10 < ema20:
actions.append({
'action': 'close_long',
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
})
elif pos.side == 'short' and ema10 > ema20:
actions.append({
'action': 'close_short',
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
})
return actions
# =========================================================================
# ENTRY LOGIC - Calm Vol Pullback (very selective)
# =========================================================================
# Check for calm volatility regime
if not (params['calm_z_min'] <= z_now <= params['calm_z_max']):
return actions
# Check for minimum EMA spread (clear trend)
ema_spread = abs(ema10 - ema20) / ema20
if ema_spread < params['ema_spread_min']:
return actions
# Check for uptrend: EMA10 > EMA20
if ema10 > ema20:
# Pullback to EMA20 from above (long entry zone)
touch_ema = bar.low <= ema20 * (1 + params['pullback_tolerance'])
close_above = bar.close > ema20
# Bullish bar confirmation (strong candle)
bar_bullish = bar.close > bar.open
bar_strength = (bar.close - bar.open) / max(bar.high - bar.low, 0.01)
if touch_ema and close_above and bar_bullish and bar_strength > 0.4:
actions.append({
'action': 'open_long',
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
'size': 0.5, # Half size for defensive
'stop_loss_pct': params['stop_loss_pct'],
'take_profit_pct': params['take_profit_pct'],
})
return actions
# Check for downtrend: EMA10 < EMA20
elif ema10 < ema20:
# Pullback to EMA20 from below (short entry zone)
touch_ema = bar.high >= ema20 * (1 - params['pullback_tolerance'])
close_below = bar.close < ema20
# Bearish bar confirmation (strong candle)
bar_bearish = bar.close < bar.open
bar_strength = (bar.open - bar.close) / max(bar.high - bar.low, 0.01)
if touch_ema and close_below and bar_bearish and bar_strength > 0.4:
actions.append({
'action': 'open_short',
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
'size': 0.5, # Half size for defensive
'stop_loss_pct': params['stop_loss_pct'],
'take_profit_pct': params['take_profit_pct'],
})
return actions
if __name__ == '__main__':
"""Run backtest when executed directly."""
from strategy import backtest_strategy
print("\n" + "=" * 60)
print("DEFENSIVE: Volatility Compression Breakout - tETHUSD")
print("=" * 60)
print("\nTrades EMA pullbacks in calm volatility regimes.")
print("Entry: Pullback to EMA20 in trend with calm vol (z<0)")
print("Exit: TP 3.0% / SL 1.5% / Max 15 bars / Vol spike")
print()
results, profitable, _ = backtest_strategy(init_strategy, process_time_step)
total_trades = sum(r.get('trades', 0) for r in results.values())
total_return = sum(r.get('return', 0) for r in results.values())
max_dd = max(r.get('max_dd', 0) for r in results.values()) if results else 0
print(f"\n Aggregate Stats:")
print(f" Total Trades: {total_trades}")
print(f" Total Return: {total_return:+.2f}%")
print(f" Max Drawdown: {max_dd:.2f}%")
print(f" Profitable Months: {profitable}/9")
# Calculate slippage impact
slippage_cost = total_trades * 0.06 # 0.03% per side = 0.06% round trip
net_return = total_return - slippage_cost
print(f"\n Cost Analysis:")
print(f" Gross Return: {total_return:+.2f}%")
print(f" Slippage Cost: -{slippage_cost:.2f}%")
print(f" Net Return: {net_return:+.2f}%")
print("\n Defensive Role Validation:")
print(f" Max DD < 12%: {'PASS' if max_dd < 12 else 'FAIL'} ({max_dd:.1f}%)")
print(f" Return >= 0%: {'PASS' if net_return >= 0 else 'FAIL'} ({net_return:+.1f}%)")
print(f" Trades >= 5: {'PASS' if total_trades >= 5 else 'FAIL'} ({total_trades})")
passes = max_dd < 12 and net_return >= 0 and total_trades >= 5
print(f"\n {'=' * 50}")
print(f" TRAIN RESULT: {'PASS - Ready for validation' if passes else 'FAIL - Needs tuning'}")