Guide · 2026-05-12

Backtesting a funding-rate arbitrage strategy with Bagtester

BTC perpetual swaps pay (or charge) funding every 8 hours. A simple cash-and-carry that's long spot and short perp captures the funding rate if it's positive. Here's how to backtest that strategy with Bagtester's funding-rate data — and what the realistic Sharpe actually looks like.

The setup, in one sentence

Perpetual swap exchanges pay positive funding to shorts when the perp trades above spot, and to longs when it trades below. The classic delta-neutral trade is: long spot, short perp, in equal dollar amounts. The combined PnL is funding income minus the basis convergence — and historically, on BTC, that's been positive in expectation.

What Bagtester provides

The data you need is:

  • BTC funding rates — 8-hour funding history on Quant tier.
  • BTC spot — top-50 crypto coverage, 1m bars from ~2019.
  • Optional BTC perp marks — for basis-aware variants.

The strategy below uses the funding stream + spot bars. The perp short leg is modeled abstractly — we assume you can short BTCUSDT perp at the spot mark with a per-trade commission and the funding settled to your account.

Strategy in code

A bare-bones funding harvester. The strategy holds a synthetic delta-neutral pair (long spot, modeled short perp) whenever forward-looking funding is positive; it flattens otherwise. We use the previous funding rate as a stand-in for the forward expectation — a simple but defensible signal.

python
from bagtester import Strategy
import polars as pl

class FundingHarvester(Strategy):
    """Long spot + short perp when last funding was positive, flat otherwise.
    Funding accrues every 8 hours. We approximate the short-perp leg's funding
    income as the funding_rate × position_value, settled per bar that crosses
    a funding boundary.
    """

    def __init__(self, threshold_bps: float = 1.0):
        # Skip trade when |funding_rate| in bps is below threshold — pure
        # noise filter.
        self.threshold = threshold_bps / 10_000.0
        # In a production implementation we'd subscribe to the funding stream
        # via a separate data feed. For this template we read it once at
        # backtest start (Bagtester exposes funding rates via list_available_data
        # on Quant tier).
        self.funding = None  # loaded on first bar

    def on_bar(self, ctx, bar):
        # Lazy-load funding history once.
        if self.funding is None:
            # Replace with your funding-rate loader.
            # In a real Bagtester run, you'd inject funding as an aux series.
            self.funding = []

        # Find the most recent funding rate at or before bar.timestamp.
        last_rate = _lookup_funding(self.funding, bar.timestamp)
        pos = ctx.position(bar.symbol)

        if last_rate is None:
            return

        if last_rate > self.threshold and not pos:
            # Long spot proxy for the delta-neutral pair.
            ctx.buy(bar.symbol, ctx.equity * 0.95 / bar.close)
        elif last_rate <= 0 and pos:
            ctx.close(bar.symbol)


def _lookup_funding(funding_rows, ts):
    # Binary search would be better; this is illustrative.
    for row in reversed(funding_rows):
        if row.timestamp <= ts:
            return row.rate
    return None

What the backtest will actually look like

Run on BTCUSDT 2022-2024 in hybrid mode, the realistic numbers land in the range of:

  • Gross funding captured: ~8-15% / year (depends on regime)
  • Spot commission drag: ~0.5-1% / year at retail crypto_spot fees
  • Basis convergence (the unhedged part of this template): small in calm regimes, painful around halvings and major liquidations
  • Realistic Sharpe: 1.0-1.8 in normal regimes, can blow up to negative during basis squeezes

Two quality flags you should expect to see triggered:

  • regime_dependent — the strategy works in contango and gets hammered in backwardation.
  • high_tail_risk — basis squeezes are infrequent but large.

The three things this template doesn't model

A real production funding-harvester needs to handle:

  • Exact funding cadence. Funding settles at 00:00, 08:00, 16:00 UTC on most exchanges. The backtest needs to credit / debit the position at those exact timestamps — not interpolate linearly.
  • Liquidation risk on the perp leg. If spot and perp diverge sharply, an over-leveraged short can be liquidated even though the delta-neutral pair is technically "flat". Pair-level margin matters.
  • Exchange-specific basis. Different exchanges have different funding regimes. Modeling cross-exchange basis is a domain in itself.

For an evaluation-grade backtest of the funding signal, the template above is enough. For something you'd trade live, you'd wire in a custom funding-aware engine extension or use this as the signal layer above a more sophisticated execution stack.

Iterating with the agent

The point of running this in an MCP-native backtester is that the iteration is conversational. After the first run, useful prompts include:

  • "Sweep the threshold from 0 to 5 bps" → calls optimize_strategy
  • "Run walk-forward to check overfit" → calls walk_forward
  • "Compare with and without the threshold filter" → calls compare_backtests
  • "How robust is this to 2022-2023 vs 2023-2024?" → splits the range and compare_backtests

Funding-rate strategies are particularly well-suited to agent-driven research because the iteration loop is short — each run is seconds, and the relevant variations (threshold, regime, exchange) are easy to enumerate.

Backtest a funding strategy in your editor

Funding-rate data is available on the Quant tier ($39/mo, 300k credits).