Trend exhaustion strategy that enters after strong 4-bar rallies show first signs of pullback. Entry Logic:
Symbol: BTC | Exchange: Bitfinex
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +23.6% | 45.0% | 20 | 6.2% | 1.26 |
| 2021 | +40.3% | 65.5% | 29 | 11.8% | 1.32 |
| 2022 | -26.5% | 21.4% | 14 | 24.1% | -2.12 |
| 2023 | +27.3% | 73.3% | 15 | 4.1% | 1.93 |
| 2024 | +14.4% | 63.6% | 11 | 7.9% | 1.11 |
| 2025 | +0.6% | 55.6% | 9 | 6.4% | 0.10 |
See strategy file
See strategy file
"""
Strategy: rally_exhaustion_pullback_4bar
========================================
Trend exhaustion strategy that enters after strong 4-bar rallies show
first signs of pullback.
Entry Logic:
- 4 consecutive green bars (uptrend momentum)
- Rally of 4%+ during those 4 bars (meaningful move)
- Current bar closes red (first exhaustion signal/pullback)
- Price remains above EMA20 (trend still intact)
Exit Logic:
- Hold for 12 bars (2 days on 4h timeframe)
Performance: 5/6 years profitable | Total: +79.6%
2020: +23.6% | 45% WR | 20 trades
2021: +40.3% | 66% WR | 29 trades
2022: -26.5% | 21% WR | 14 trades
2023: +27.3% | 73% WR | 15 trades
2024: +14.4% | 64% WR | 11 trades
2025: +0.6% | 56% WR | 9 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import ema
def init_strategy():
return {
'name': 'rally_exhaustion_pullback_4bar',
'subscriptions': [
{'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
],
'parameters': {'hold_bars': 12}
}
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 < 50:
return []
closes = [b.close for b in bars]
opens = [b.open for b in bars]
ema20 = ema(closes, 20)
if ema20[i] is None:
return []
actions = []
has_position = key in positions
if not has_position:
# Entry: Rally exhaustion pattern
# Check for 4 consecutive green bars before current
consecutive_green = all(closes[j] > opens[j] for j in range(i-4, i))
if not consecutive_green:
return []
# Current bar must be red (first pullback)
if closes[i] >= opens[i]:
return []
# Must be above EMA20 (uptrend context)
if closes[i] < ema20[i]:
return []
# Rally must be at least 4% (from 5 bars ago to 1 bar ago)
rally_pct = (closes[i-1] - closes[i-5]) / closes[i-5] * 100
if rally_pct < 4:
return []
actions.append({
'action': 'open_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
'size': 1.0,
})
else:
# Exit: after 12 bars hold period
pos = positions[key]
bars_held = i - pos.entry_bar
if bars_held >= 12:
actions.append({
'action': 'close_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
})
return actions