Auto-discovered strategy
Symbol: BTC | Exchange: Bitfinex | Role: momentum
Click a year to view chart
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +11.4% | 38.9% | 18 | 17.8% | 0.52 |
| 2021 | +3.7% | 41.2% | 17 | 16.2% | 0.15 |
| 2022 | +2.3% | 50.0% | 2 | 4.5% | 0.29 |
| 2023 | +18.6% | 53.3% | 15 | 6.8% | 1.06 |
| 2024 | +20.8% | 53.3% | 15 | 6.6% | 1.50 |
| 2025 | +16.6% | 71.4% | 7 | 3.8% | 1.81 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2024-01→2025-06 | 2025-07→2025-12 | +1.2% | OK | 2026-01→ongoing | +0.0% | PASS |
"""
Swing Higher Low Breakout Strategy
==================================
Detects genuine swing points (local extrema) and trades breakouts
in established uptrends with proper higher-high, higher-low structure.
Key features:
- Real swing detection (3-bar confirmation on each side)
- Higher low + higher high confirmation
- Volume-confirmed breakout above recent swing high
- EMA50/200 regime filter (only trade in bull markets)
- Tight stop loss (4.5%) for bear market survival
TRAIN RESULTS (2024-01 to 2025-06):
2024: +20.8% | 15 trades | 53% WR | 6.6% DD | Sharpe 1.50
2025H1: +15.3% | 4 trades | 75% WR | 0.8% DD | Sharpe 2.56
Total: +36.2%
Designed to survive bear markets by:
1. Staying flat when EMA50 < EMA200
2. Using tight 4.5% stop losses
3. Exiting on structure break (close below swing low)
4. Time-based exit (max 20 bars)
Symbol: tBTCUSD
Exchange: bitfinex
Timeframe: 4h
"""
import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema
def init_strategy():
return {
'name': 'swing_hl_breakout',
'role': 'momentum',
'warmup': 210, # EMA200 + 10 bars buffer
'subscriptions': [
{'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
],
'parameters': {}
}
def process_time_step(ctx):
"""
Process each time step and return trading actions.
Entry Logic:
- Bull regime: EMA50 > EMA200
- Find swing lows and highs in last 40 bars
- Higher Low: most recent swing low > previous swing low
- Higher High: most recent swing high > previous swing high
- Breakout: close > most recent swing high
- Volume: current volume > 110% of 20-bar average
- Momentum: bullish candle (close > open)
Exit Logic:
- Structure break: close < recent swing low
- Regime change: EMA50 < EMA200
- Time exit: held for 20 bars
- Stop loss: 4.5%
- Take profit: 9.0%
"""
key = ('tBTCUSD', 'bitfinex')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
# warmup=210 in init_strategy ensures we have 200+ bars for EMA200
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
ema50_vals = ema(closes, 50)
ema200_vals = ema(closes, 200)
ema50_now = ema50_vals[i]
ema200_now = ema200_vals[i]
# CRITICAL: Only trade in bull regime
bull_regime = ema50_now > ema200_now
actions = []
if key not in positions:
# STAY FLAT in bear markets
if not bull_regime:
return []
# Find swing points in last 40 bars
# Swing = local extrema with 3-bar confirmation on each side
swing_lows = []
swing_highs = []
for j in range(i-40, i-3):
if j < 3:
continue
# Check if j is a swing low
is_swing_low = True
for k in range(j-3, j+4):
if k != j and 0 <= k < len(bars):
if bars[k].low < bars[j].low:
is_swing_low = False
break
if is_swing_low:
swing_lows.append((j, bars[j].low))
# Check if j is a swing high
is_swing_high = True
for k in range(j-3, j+4):
if k != j and 0 <= k < len(bars):
if bars[k].high > bars[j].high:
is_swing_high = False
break
if is_swing_high:
swing_highs.append((j, bars[j].high))
# Need at least 2 swing lows and 2 swing highs
if len(swing_lows) < 2 or len(swing_highs) < 2:
return []
# Sort by time
swing_lows.sort(key=lambda x: x[0])
swing_highs.sort(key=lambda x: x[0])
# Get last 2 of each
last_two_lows = swing_lows[-2:]
last_two_highs = swing_highs[-2:]
# Higher Low: more recent swing low > previous swing low
higher_low = last_two_lows[1][1] > last_two_lows[0][1]
# Higher High: more recent swing high > previous swing high
higher_high = last_two_highs[1][1] > last_two_highs[0][1]
# Breakout: close above most recent swing high
recent_swing_high = last_two_highs[-1][1]
breakout = bars[i].close > recent_swing_high
# Volume confirmation: above 110% of average
avg_vol = sum(volumes[i-20:i]) / 20
vol_ok = volumes[i] > avg_vol * 1.1
# Momentum: bullish candle
bullish_close = bars[i].close > bars[i].open
# ENTRY: All conditions must be true
if higher_low and higher_high and breakout and vol_ok and bullish_close:
actions.append({
'action': 'open_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
'size': 1.0,
'stop_loss_pct': 4.5, # Tight stop for bear survival
'take_profit_pct': 9.0, # 2:1 risk/reward
})
else:
# === EXIT LOGIC ===
pos = positions[key]
bars_held = i - pos.entry_bar
# Find recent swing lows for exit reference
swing_lows = []
for j in range(i-20, i-3):
if j < 3:
continue
is_swing_low = all(bars[j].low <= bars[k].low
for k in range(j-3, j+4) if k != j and 0 <= k < len(bars))
if is_swing_low:
swing_lows.append(bars[j].low)
recent_swing_low = min(swing_lows) if swing_lows else lows[i-10]
# Exit conditions
break_structure = bars[i].close < recent_swing_low
regime_exit = not bull_regime
time_exit = bars_held >= 20
if break_structure or regime_exit or time_exit:
actions.append({
'action': 'close_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
})
return actions
# Allow direct execution for testing
if __name__ == "__main__":
from strategy import backtest_strategy
results, profitable, _ = backtest_strategy(init_strategy, process_time_step, verbose=True)