Auto-discovered rule
Symbol: ETH | Exchange: Binance
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +134.9% | 73.0% | 15 | 10.0% | 1.00 |
| 2021 | +23.3% | 42.0% | 12 | 10.0% | 1.00 |
| 2022 | +28.1% | 57.0% | 7 | 10.0% | 1.00 |
| 2023 | -4.1% | 39.0% | 18 | 10.0% | 1.00 |
| 2024 | +25.2% | 38.0% | 16 | 10.0% | 1.00 |
| 2025 | +42.5% | 33.0% | 15 | 10.0% | 1.00 |
See rule file
See rule file
"""
Strategy: ema_ribbon_expansion_eth
===================================
Enter when EMA ribbon (8,13,21,34,55) is aligned and expanding, price pulls back
to EMA21 support, then bounces with volume confirmation. Exit when price breaks
below EMA21 or ribbon alignment breaks.
Symbol: ETH (ETHUSDT) | Exchange: Binance
Performance: 5/6 years profitable | Total: +249.9%
Year-by-year:
2020: +134.9% | 73% WR | 15 trades
2021: +23.3% | 42% WR | 12 trades
2022: +28.1% | 57% WR | 7 trades
2023: -4.1% | 39% WR | 18 trades
2024: +25.2% | 38% WR | 16 trades
2025: +42.5% | 33% WR | 15 trades
"""
import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema, sma
def init_strategy():
return {
'name': 'ema_ribbon_expansion_eth',
'subscriptions': [
{'symbol': 'ETHUSDT', 'exchange': 'binance', 'timeframe': '4h'},
],
'parameters': {}
}
def process_time_step(ctx):
key = ('ETHUSDT', 'binance')
bars = ctx['bars'].get(key, [])
i = ctx['i']
positions = ctx['positions']
if not bars or i >= len(bars) or i < 100:
return []
closes = [b.close for b in bars]
volumes = [b.volume for b in bars]
# EMA ribbon: 8, 13, 21, 34, 55
ema8 = ema(closes, 8)
ema13 = ema(closes, 13)
ema21 = ema(closes, 21)
ema34 = ema(closes, 34)
ema55 = ema(closes, 55)
vol_ma = sma(volumes, 20)
if any(x[i] is None for x in [ema8, ema13, ema21, ema34, ema55]) or vol_ma[i] is None:
return []
actions = []
has_position = key in positions
if not has_position:
# 1. Ribbon must be bullishly aligned
ribbon_aligned = (ema8[i] > ema13[i] > ema21[i] > ema34[i] > ema55[i])
# 2. Calculate ribbon width (distance between fast and slow EMAs)
ribbon_width_now = (ema8[i] - ema55[i]) / ema55[i] * 100
ribbon_width_prev = (ema8[i-3] - ema55[i-3]) / ema55[i-3] * 100
# 3. Ribbon expanding (momentum accelerating)
expanding = ribbon_width_now > ribbon_width_prev * 1.1
# 4. Price was recently near or below EMA21 (pullback entry)
had_pullback = any(closes[j] <= ema21[j] * 1.01 for j in range(i-3, i))
# 5. Price now breaks back above EMA13 (momentum resuming)
price_above_ema13 = closes[i] > ema13[i]
price_above_ema21 = closes[i] > ema21[i]
# 6. Bullish candle
bullish = bars[i].close > bars[i].open
# 7. Volume confirmation
volume_strong = volumes[i] > vol_ma[i]
# 8. Recent momentum: close near high of the candle
close_near_high = (bars[i].close - bars[i].low) / (bars[i].high - bars[i].low + 0.0001) > 0.6
# 9. EMA8 rising strongly
ema8_slope = (ema8[i] - ema8[i-4]) / ema8[i-4] * 100
strong_slope = ema8_slope > 0.5
if (ribbon_aligned and expanding and had_pullback and price_above_ema13 and
price_above_ema21 and bullish and volume_strong and close_near_high and strong_slope):
actions.append({
'action': 'open_long',
'symbol': 'ETHUSDT',
'exchange': 'binance',
'size': 1.0,
})
else:
# Exit: Ribbon starts contracting or alignment breaks
ribbon_aligned = (ema8[i] > ema13[i] > ema21[i])
# Exit if price falls below EMA21 or ribbon breaks
below_ema21 = bars[i].close < ema21[i]
if below_ema21 or not ribbon_aligned:
actions.append({
'action': 'close_long',
'symbol': 'ETHUSDT',
'exchange': 'binance',
})
return actions