Auto-discovered strategy
Symbol: ETH | Exchange: Binance | Role: momentum
Click a year to view chart
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +48.5% | 28.1% | 64 | 17.0% | 1.12 |
| 2021 | +57.2% | 29.6% | 71 | 31.2% | 1.24 |
| 2022 | +13.8% | 22.7% | 66 | 29.8% | 0.34 |
| 2023 | -9.2% | 27.9% | 68 | 28.0% | -0.30 |
| 2024 | +23.7% | 28.6% | 56 | 28.3% | 0.64 |
| 2025 | +24.6% | 26.0% | 73 | 29.9% | 0.57 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2024-01→2025-06 | 2025-07→2025-12 | -23.1% | FAIL | 2026-01→ongoing | +0.0% | FAIL |
Not yet reviewed. Run: ./review_strategy.sh eth_sr_ema_bounce
"""
ETH Support/Resistance EMA Bounce Strategy
===========================================
A dual-mode strategy that adapts to market regime by going long on EMA50
support bounces in uptrends and short on EMA50 resistance rejections
in downtrends.
Strategy Concept:
-----------------
The EMA50 acts as a dynamic support/resistance level. In uptrends,
pullbacks to EMA50 are buying opportunities. In downtrends, rallies to
EMA50 are selling opportunities. This strategy captures these bounces
with strict regime filtering.
LONG ENTRIES (Bull Markets - EMA50 > EMA200):
- Low touched EMA50 zone (within 3%)
- Close bounced above EMA50
- Bullish candle (close > open)
- Volume above 20-bar average
SHORT ENTRIES (Bear Markets - EMA50 < EMA200):
- High touched EMA50 zone (within 3%)
- Close rejected below EMA50
- Bearish candle (close < open)
- Volume above 20-bar average
EXITS:
- 2 consecutive closes on wrong side of EMA50
- Stop loss: 3%
- Take profit: 10%
Risk Management:
----------------
- Regime filter prevents fighting the trend
- 3% stop loss limits downside per trade
- 10% take profit captures trend continuation moves
- Volume confirmation reduces false signals
Expected Behavior:
------------------
- Performs well in trending markets (both up and down)
- Will have losing trades in choppy/ranging conditions
- Win rate ~30%, relies on winners (10%) being larger than losers (3%)
- Approximately 2:1 short to long ratio in mixed conditions
Training Period Performance (2024-01-01 to 2025-06-30):
- 2024: +23.7% | 56 trades | 29% WR
- 2025 (partial): +27.4% | 35 trades | 29% WR
- Total: +51.1% | 101 trades | Sharpe 0.95 | Max DD 28.3%
"""
import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema
def init_strategy():
"""Initialize the strategy configuration."""
return {
'name': 'eth_sr_ema_bounce',
'role': 'momentum', # Can lose bounded in bear markets
'warmup': 200,
'subscriptions': [
{'symbol': 'ETHUSDT', 'exchange': 'binance', 'timeframe': '4h'},
],
'parameters': {}
}
def process_time_step(ctx):
"""
Process each time step and return trading actions.
Args:
ctx: Context dict containing bars, positions, state, etc.
Returns:
List of action dicts (open_long, close_long, open_short, close_short)
"""
key = ('ETHUSDT', 'binance')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
# Build price/volume arrays up to current bar
closes = [b.close for b in bars[:i+1]]
lows = [b.low for b in bars[:i+1]]
highs = [b.high for b in bars[:i+1]]
opens = [b.open for b in bars[:i+1]]
volumes = [b.volume for b in bars[:i+1]]
# Calculate EMAs for regime detection
ema50 = ema(closes, 50)
ema200 = ema(closes, 200)
actions = []
if key not in positions:
# Check indicator availability
if ema50[i] is None or ema200[i] is None:
return []
# Determine market regime
is_bull = ema50[i] > ema200[i]
is_bear = ema50[i] < ema200[i]
# Volume baseline
avg_vol = sum(volumes[i-20:i]) / 20
if is_bull:
# LONG ENTRY: Pullback to EMA50 with bounce
# Condition 1: Low touched EMA50 zone (within 3%)
if lows[i] > ema50[i] * 1.03:
return []
# Condition 2: Close bounced above EMA50
if closes[i] <= ema50[i]:
return []
# Condition 3: Bullish candle
if closes[i] <= opens[i]:
return []
# Condition 4: Volume confirmation
if volumes[i] < avg_vol:
return []
# All conditions met - open long
actions.append({
'action': 'open_long',
'symbol': 'ETHUSDT',
'exchange': 'binance',
'size': 1.0,
'stop_loss_pct': 3.0,
'take_profit_pct': 10.0,
})
elif is_bear:
# SHORT ENTRY: Rally to EMA50 with rejection
# Condition 1: High touched EMA50 zone (within 3%)
if highs[i] < ema50[i] * 0.97:
return []
# Condition 2: Close rejected below EMA50
if closes[i] >= ema50[i]:
return []
# Condition 3: Bearish candle
if closes[i] >= opens[i]:
return []
# Condition 4: Volume confirmation
if volumes[i] < avg_vol:
return []
# All conditions met - open short
actions.append({
'action': 'open_short',
'symbol': 'ETHUSDT',
'exchange': 'binance',
'size': 1.0,
'stop_loss_pct': 3.0,
'take_profit_pct': 10.0,
})
else:
# EXIT LOGIC
pos = positions[key]
bars_held = i - pos.entry_bar
should_exit = False
if pos.side == 'long':
# Exit long: 2 consecutive closes below EMA50
if bars_held >= 2 and closes[i] < ema50[i] and closes[i-1] < ema50[i-1]:
should_exit = True
else:
# Exit short: 2 consecutive closes above EMA50
if bars_held >= 2 and closes[i] > ema50[i] and closes[i-1] > ema50[i-1]:
should_exit = True
if should_exit:
action_type = 'close_long' if pos.side == 'long' else 'close_short'
actions.append({
'action': action_type,
'symbol': 'ETHUSDT',
'exchange': 'binance',
})
return actions
if __name__ == '__main__':
# Test the strategy
from strategy import backtest_strategy
results, profitable, _ = backtest_strategy(init_strategy, process_time_step)