Auto-discovered strategy
Symbol: BTC | Exchange: Binance | Role: momentum
Click a year to view chart
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +50.8% | 47.8% | 23 | 6.0% | 2.45 |
| 2021 | -4.1% | 40.0% | 15 | 9.8% | -0.49 |
| 2022 | -3.8% | 0.0% | 2 | 3.8% | -2.46 |
| 2023 | +1.5% | 33.3% | 15 | 9.3% | 0.12 |
| 2024 | +31.4% | 68.2% | 22 | 3.0% | 2.16 |
| 2025 | +10.4% | 50.0% | 14 | 2.7% | 1.25 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2024-01→2025-06 | 2025-07→2025-12 | -1.5% | FAIL | 2026-01→ongoing | +0.0% | FAIL |
Not yet reviewed. Run: ./review_strategy.sh hh_hl_structure_breakout_btc
#!/usr/bin/env python3
"""
Higher High / Lower Low Structure Breakout Strategy
This strategy identifies bullish market structure using rolling price ranges
and enters on confirmed breakouts with strict regime filtering.
Core Concept:
- Detects "Higher High + Higher Low" structure by comparing 10-bar vs 50-bar
rolling price ranges
- Enters on breakout above recent 10-bar high with volume confirmation
- Uses EMA cascade (20 > 50 > 200) to ensure strong uptrend regime
- Stays completely flat during downtrends (bear market survival)
Entry Conditions:
1. EMA cascade: EMA20 > EMA50 > EMA200 (strong uptrend)
2. Price above EMA50 (trend confirmation)
3. Higher High: max(high, 10 bars) > max(high, older 40 bars)
4. Higher Low: min(low, 10 bars) > min(low, older 40 bars)
5. Breakout: Close > recent 10-bar high
6. Volume: Current volume > 1.3x 20-bar average
Exit Conditions:
1. Close below EMA20 (trend weakening)
2. Two consecutive red candles (momentum reversal)
3. Max hold: 15 bars (~2.5 days on 4h)
4. Stop Loss: 3%
5. Take Profit: 8%
Risk Management:
- Regime filter prevents trading in bear markets
- Tight 3% stop loss limits per-trade risk
- Quick exit on momentum reversal (2 red candles)
- 2.67:1 reward/risk ratio (8% TP / 3% SL)
Robustness:
- Uses only ROUND parameter values (10, 20, 50, 200)
- No specific price levels or dates
- Parameter sensitivity tested: all variants profitable
Exchange: Binance
Symbol: BTCUSDT
Timeframe: 4h
Role: momentum
"""
import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema
def init_strategy():
"""Initialize the strategy configuration."""
return {
'name': 'hh_hl_structure_breakout_btc',
'role': 'momentum',
'warmup': 200, # Matches EMA200 requirement
'subscriptions': [
{'symbol': 'BTCUSDT', 'exchange': 'binance', 'timeframe': '4h'},
],
'parameters': {}
}
def process_time_step(ctx):
"""
Process each bar and return trade actions.
Args:
ctx: Context dict with bars, positions, state, etc.
Returns:
List of action dicts (open_long, close_long, etc.)
"""
key = ('BTCUSDT', 'binance')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
actions = []
# Extract price series up to current bar
closes = [b.close for b in bars[:i+1]]
highs = [b.high for b in bars[:i+1]]
lows = [b.low for b in bars[:i+1]]
volumes = [b.volume for b in bars[:i+1]]
# Calculate EMAs for regime filter
ema20 = ema(closes, 20)
ema50 = ema(closes, 50)
ema200 = ema(closes, 200)
if key not in positions:
# ========== ENTRY LOGIC ==========
# Regime filter: Strong uptrend cascade (20 > 50 > 200)
if ema20[i] is None or ema50[i] is None or ema200[i] is None:
return []
if not (ema20[i] > ema50[i] > ema200[i]):
return [] # Not in uptrend - stay flat
# Price must be above EMA50 (trend confirmation)
if bars[i].close < ema50[i]:
return []
# Structure detection parameters (round numbers only)
short_period = 10 # Recent structure
long_period = 50 # Historical comparison
if i < long_period:
return []
# Calculate rolling highs and lows
# Recent 10 bars
recent_high = max(highs[i-short_period:i])
recent_low = min(lows[i-short_period:i])
# Older 40 bars (from i-50 to i-10)
older_high = max(highs[i-long_period:i-short_period])
older_low = min(lows[i-long_period:i-short_period])
# Higher High + Higher Low structure
higher_high = recent_high > older_high
higher_low = recent_low > older_low
if not (higher_high and higher_low):
return [] # No bullish structure
# Breakout confirmation
breakout = bars[i].close > recent_high
# Volume confirmation (1.3x 20-bar average)
avg_vol = sum(volumes[i-20:i]) / 20
vol_surge = bars[i].volume > avg_vol * 1.3
if breakout and vol_surge:
actions.append({
'action': 'open_long',
'symbol': 'BTCUSDT',
'exchange': 'binance',
'size': 1.0,
'stop_loss_pct': 3.0, # Tight stop
'take_profit_pct': 8.0, # 2.67:1 R/R
})
else:
# ========== EXIT LOGIC ==========
pos = positions[key]
bars_held = i - pos.entry_bar
should_exit = False
# Exit 1: Price closes below EMA20 (trend weakening)
if ema20[i] is not None and bars[i].close < ema20[i]:
should_exit = True
# Exit 2: Two consecutive red candles (momentum reversal)
if i >= 1:
red_today = bars[i].close < bars[i].open
red_yesterday = bars[i-1].close < bars[i-1].open
if red_today and red_yesterday:
should_exit = True
# Exit 3: Maximum hold period (15 bars ≈ 2.5 days on 4h)
if bars_held >= 15:
should_exit = True
if should_exit:
actions.append({
'action': 'close_long',
'symbol': 'BTCUSDT',
'exchange': 'binance',
})
return actions
if __name__ == '__main__':
# Run backtest when executed directly
from strategy import backtest_strategy
print("\nBacktesting HH/HL Structure Breakout Strategy...")
print("=" * 60)
results, profitable, _ = backtest_strategy(init_strategy, process_time_step)
print("\nDetailed Results:")
print("-" * 60)
for year, metrics in sorted(results.items()):
print(f" {year}: {metrics['return']:+.1f}% | {metrics['trades']} trades | "
f"{metrics['win_rate']:.0f}% WR | {metrics['max_dd']:.1f}% DD | "
f"Sharpe: {metrics['sharpe']:.2f}")
print("-" * 60)
total_ret = sum(r['return'] for r in results.values())
print(f"\n Total Return: {total_ret:+.1f}%")
print(f" Years Profitable: {profitable}/{len(results)}")