Auto-discovered strategy
Symbol: ETH | Exchange: Binance | Role: carry
Click a year to view chart
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +12.0% | 28.6% | 7 | 9.7% | 0.50 |
| 2021 | +37.6% | 75.0% | 4 | 5.0% | 1.72 |
| 2022 | +2.9% | 24.0% | 25 | 20.4% | 0.06 |
| 2023 | +6.0% | 33.3% | 18 | 18.1% | 0.27 |
| 2024 | +37.4% | 66.7% | 6 | 5.9% | 1.69 |
| 2025 | +27.9% | 34.5% | 29 | 29.8% | 0.63 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2024-01→2025-06 | 2025-07→2025-12 | -22.6% | FAIL | 2026-01→ongoing | +0.0% | FAIL |
Not yet reviewed. Run: ./review_strategy.sh eth_funding_carry
"""
Strategy: eth_funding_carry
===========================
ETH Funding Rate Carry Strategy for Binance ETHUSDT
This strategy trades the funding rate regime on ETH perpetual futures.
When funding rates are low (shorts being paid), the market tends to be
oversold and rallies as shorts cover their positions.
Concept:
- Funding rate < 5% APR indicates shorts are being paid (oversold)
- Combined with uptrend (EMA20 > EMA100), this signals a long entry
- The strategy profits from the typical rally that follows low funding
Entry Conditions:
- Funding rate APR < 5% (shorts being squeezed)
- EMA20 > EMA100 (uptrend confirmation)
Exit Conditions:
- Take profit at 20%
- Stop loss at 5%
- Trend reversal (EMA20 < EMA100)
- Time exit after 50 bars (~8 days)
Role: carry (profits from funding rate regime dynamics, bounded risk)
Training Performance (2024-01-01 to 2025-06-30):
2024: +37.4% | 67% WR | 5.9% DD | 6 trades
2025 H1: +17.8% | 50% WR | 9.4% DD | 10 trades
Total: +55.1% | Max DD: 9.4% | Sharpe: 1.21
Parameters (round numbers, generalizable):
funding_threshold: 0.05 (5% APR)
ema_fast: 20
ema_slow: 100
take_profit_pct: 20
stop_loss_pct: 5
max_hold_bars: 50
"""
import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema
import clickhouse_connect
_funding_cache = {}
def _load_funding_rates(start, end):
"""Load Binance ETH funding rate data"""
cache_key = f"{start}_{end}"
if cache_key in _funding_cache:
return _funding_cache[cache_key]
client = clickhouse_connect.get_client(host='localhost', database='crypto_data')
query = f"""
SELECT
toStartOfInterval(funding_time, INTERVAL 240 MINUTE) as ts,
avg(funding_rate) * 3 * 365 as funding_apr
FROM funding_rate_history
WHERE exchange = 'binance' AND symbol = 'ETHUSDT'
AND funding_time >= '{start}' AND funding_time < '{end}'
GROUP BY ts
ORDER BY ts
"""
result = client.query(query)
funding_map = {row[0]: float(row[1]) for row in result.result_rows}
_funding_cache[cache_key] = funding_map
return funding_map
def init_strategy():
return {
'name': 'eth_funding_carry',
'role': 'carry',
'warmup': 50,
'role': 'carry',
'subscriptions': [
{'symbol': 'ETHUSDT', 'exchange': 'binance', 'timeframe': '4h'},
],
'parameters': {
'funding_threshold': 0.05, # 5% APR - enter when funding is low
'ema_fast': 20, # Fast EMA period
'ema_slow': 100, # Slow EMA period
'take_profit_pct': 20, # 20% take profit
'stop_loss_pct': 5, # 5% stop loss
'max_hold_bars': 50, # ~8 day max hold
}
}
def process_time_step(ctx):
"""Process each time step and generate trading actions"""
key = ('ETHUSDT', 'binance')
bars = ctx['bars'].get(key, [])
i = ctx['i']
positions = ctx['positions']
params = ctx['parameters']
state = ctx['state']
# Need enough history for slow EMA
if i < 100 or not bars:
return []
# Extract parameters
funding_threshold = params['funding_threshold']
ema_fast_period = params['ema_fast']
ema_slow_period = params['ema_slow']
take_profit_pct = params['take_profit_pct']
stop_loss_pct = params['stop_loss_pct']
max_hold_bars = params['max_hold_bars']
# Calculate EMAs for trend detection
closes = [b.close for b in bars[:i+1]]
ema_fast_vals = ema(closes, ema_fast_period)
ema_slow_vals = ema(closes, ema_slow_period)
fast = ema_fast_vals[i] if i < len(ema_fast_vals) and ema_fast_vals[i] else None
slow = ema_slow_vals[i] if i < len(ema_slow_vals) and ema_slow_vals[i] else None
if fast is None or slow is None:
return []
# Load funding rate data on first call
if 'funding_map' not in state:
start_date = bars[0].timestamp.strftime('%Y-%m-%d')
end_date = bars[-1].timestamp.strftime('%Y-%m-%d')
state['funding_map'] = _load_funding_rates(start_date, end_date + ' 23:59:59')
funding_map = state['funding_map']
bar_time = bars[i].timestamp
current_funding = funding_map.get(bar_time, 0.10) # Default to 10% if missing
actions = []
has_position = key in positions
if not has_position:
# Entry conditions:
# 1. Funding rate below threshold (shorts paying, market oversold)
# 2. Uptrend (fast EMA > slow EMA)
low_funding = current_funding < funding_threshold
uptrend = fast > slow
if low_funding and uptrend:
actions.append({
'action': 'open_long',
'symbol': 'ETHUSDT',
'exchange': 'binance',
'size': 1.0,
'take_profit_pct': take_profit_pct,
'stop_loss_pct': stop_loss_pct,
})
else:
# Exit conditions
pos = positions[key]
bars_held = i - pos.entry_bar
# Exit on: trend reversal or max hold time
downtrend = fast < slow
time_exit = bars_held >= max_hold_bars
if downtrend or time_exit:
if pos.side == 'long':
actions.append({
'action': 'close_long',
'symbol': 'ETHUSDT',
'exchange': 'binance',
})
return actions
if __name__ == '__main__':
from strategy import backtest_strategy
print("ETH Funding Rate Carry - Backtest")
print("="*50)
results, profitable, _ = backtest_strategy(init_strategy, process_time_step, verbose=True)
total = sum(r['return'] for r in results.values())
print(f"\nTotal Return: {total:+.1f}%")
print(f"Profitable Years: {profitable}/2")