← Back to list

funding_rate_carry VALIDATED PASS

Auto-discovered strategy

Symbol: BTC | Exchange: Bitfinex | Role: carry

3/6
Profitable Years
+14.6%
Total Return
36.8%
Avg Win Rate
-0.25
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 -4.5% 0.0% 4 4.5% -2.62
2021 -0.1% 40.0% 15 4.0% -0.02
2022 -7.9% 28.6% 14 9.6% -1.89
2023 +9.3% 47.3% 91 7.6% 0.71
2024 +4.4% 43.9% 82 13.0% 0.35
2025 +13.5% 61.2% 67 4.4% 1.98

Performance Chart

Loading chart...

Walk-Forward Validation PASS

1/1 Windows Profitable
+0.2% OOS Return
0.00 Median Sharpe
0.000 Score
Window Train Period Val Period Val Return Val Test Period Test Return Status
WF-1 2024-01→2025-06 2025-07→2025-12 +0.2% OK 2026-01→ongoing +0.0% PASS

AI Review

Not yet reviewed. Run: ./review_strategy.sh funding_rate_carry

Source Code

"""
Strategy: funding_rate_carry
============================
Carry-style strategy harvesting extreme funding rate premiums on BTC.

Key concept: High funding rates indicate strong bullish sentiment and
speculator positioning. When funding is at extreme levels (top 10%),
it signals strong momentum that tends to persist short-term.

We enter long during these extreme funding periods with strict trend
confirmation and tight risk management, capturing price momentum while
maintaining bounded risk appropriate for a carry strategy.

Entry conditions:
- Funding rate >= 90th percentile (100-bar lookback) AND >= 40% APR
- Strong uptrend confirmed: EMA20 > EMA50 > EMA200
- All three EMAs must be valid (no entry during warmup)

Exit conditions:
- Funding drops below median (50th percentile) - sentiment normalizing
- Short-term trend break: Close < EMA20
- Max hold: 10 bars (carry = short duration exposure)
- Stop loss: 2% (tight for carry role)
- Take profit: 5%

Role: carry
Validation gates: max_loss=-5%, max_dd=15%, min_sharpe=-0.2, min_trades=3
Expected behavior: Bounded losses in bear markets, consistent small gains

Train results (2024-01-01 to 2025-06-30):
- 2024: +4.4%, 82 trades, 44% WR, 13.0% DD
- 2025H1: +8.2%, 14 trades, 71% WR, 1.7% DD
- Total: +12.5%, Max DD 13.0%
"""
import sys
sys.path.insert(0, '/root/trade_rules')

from lib import ema


def init_strategy():
    return {
        'name': 'funding_rate_carry',
        'role': 'carry',
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {
            'funding_pctl_entry': 0.90,   # Top 10% funding to enter
            'funding_min': 0.40,          # At least 40% APR
            'funding_pctl_exit': 0.50,    # Exit when below median
            'lookback': 100,              # Percentile lookback period
            'max_hold': 10,               # Max bars to hold
            'stop_loss': 2.0,             # 2% stop loss (tight for carry)
            'take_profit': 5.0,           # 5% take profit
        }
    }


def process_time_step(ctx):
    key = ('tBTCUSD', 'bitfinex')
    bars = ctx['bars'].get(key, [])
    i = ctx['i']
    positions = ctx['positions']
    params = ctx['parameters']
    state = ctx['state']

    # Warmup period - now handled by framework via 'warmup' field
    # if i < 200 or i >= len(bars):
    #     return []
    if i >= len(bars):
        return []

    funding = bars[i].funding_apr
    close = bars[i].close

    # Track funding history for percentile calculation
    if 'funding_hist' not in state:
        state['funding_hist'] = []
    state['funding_hist'].append(funding)
    if len(state['funding_hist']) > params['lookback']:
        state['funding_hist'] = state['funding_hist'][-params['lookback']:]

    # Calculate funding percentile
    hist_len = len(state['funding_hist'])
    if hist_len >= params['lookback']:
        below = sum(1 for f in state['funding_hist'][:-1] if f < funding)
        funding_pctl = below / (hist_len - 1)
    else:
        funding_pctl = 0.5  # Default to median if not enough history

    # Calculate EMAs for trend confirmation
    closes = [b.close for b in bars[:i+1]]
    ema20_vals = ema(closes, 20)
    ema50_vals = ema(closes, 50)
    ema200_vals = ema(closes, 200)

    ema20 = ema20_vals[i] if ema20_vals[i] else 0
    ema50 = ema50_vals[i] if ema50_vals[i] else 0
    ema200 = ema200_vals[i] if ema200_vals[i] else 0

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry conditions
        # 1. Strong uptrend: EMA20 > EMA50 > EMA200 (all positive)
        strong_uptrend = ema20 > ema50 > ema200 > 0

        # 2. Extreme funding: in top percentile AND above minimum APR
        extreme_funding = (
            funding_pctl >= params['funding_pctl_entry'] and
            funding >= params['funding_min']
        )

        if strong_uptrend and extreme_funding:
            actions.append({
                'action': 'open_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
                'size': 1.0,
                'stop_loss_pct': params['stop_loss'],
                'take_profit_pct': params['take_profit'],
            })
    else:
        # Exit conditions
        pos = positions[key]
        bars_held = i - pos.entry_bar

        should_exit = False

        # Exit 1: Funding normalizes (drops below median)
        if funding_pctl < params['funding_pctl_exit']:
            should_exit = True

        # Exit 2: Short-term trend break (close below EMA20)
        elif ema20 > 0 and close < ema20:
            should_exit = True

        # Exit 3: Max hold period reached
        elif bars_held >= params['max_hold']:
            should_exit = True

        if should_exit:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions


if __name__ == '__main__':
    from strategy import backtest_strategy

    print("Backtesting funding_rate_carry strategy...")
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step)

    print(f"\nSummary:")
    print(f"  Profitable years: {profitable}/2")
    total_ret = sum(r['return'] for r in results.values())
    max_dd = max(r['max_dd'] for r in results.values())
    avg_sharpe = sum(r['sharpe'] for r in results.values()) / len(results)
    print(f"  Total return: {total_ret:.1f}%")
    print(f"  Max drawdown: {max_dd:.1f}%")
    print(f"  Avg Sharpe: {avg_sharpe:.2f}")