Funding Arbitrage Strategy¶
Funding rate arbitrage (cash-and-carry) is one of the most common delta-neutral strategies in crypto. This guide shows how to use HypQuant funding data to research and backtest a funding arb approach on Hyperliquid.
Background¶
On Hyperliquid, funding is paid every hour. Longs pay shorts when funding is positive; shorts pay longs when funding is negative.
Basic funding arb: When funding is persistently positive, you can short the Hyperliquid perp and simultaneously hold the spot asset (on Binance or self-custodied). Your P&L comes from collecting the funding rate, net of: - Bid/ask spread on entry and exit - Spot/perp price divergence (if basis changes) - Opportunity cost of margin
This guide focuses on the research phase — finding periods of high funding, not live execution.
1. Fetch funding data¶
from hypquant import MarketData
import polars as pl
SYMBOL = "BTC-USDC"
EXCHANGE = "hyperliquid"
with MarketData(api_key="qp_...") as md:
df = md.funding(
SYMBOL,
exchange=EXCHANGE,
start="2023-10-31",
normalized=True,
)
print(df.head())
# ┌──────────────────────────┬────────────┬──────────────────┬──────────────┐
# │ time ┆ rate ┆ rate_normalized ┆ premium │
# │ datetime[μs, UTC] ┆ f64 ┆ f64 ┆ f64 │
# ╞══════════════════════════╪════════════╪══════════════════╪══════════════╡
# │ 2023-10-31 00:00:00 UTC ┆ 0.0001 ┆ null ┆ 0.0005 │
rate_normalized is null for the first 719 hours (30 days) of warm-up. Use drop_nulls() when analyzing z-scores.
2. Identify high-funding windows¶
# Focus on z-score after warm-up
df_ready = df.drop_nulls(subset=["rate_normalized"])
# High funding: z-score > 1.5 (above 1.5σ of 30-day rolling average)
high_funding = df_ready.filter(pl.col("rate_normalized") > 1.5)
print(f"Hours with high funding: {len(high_funding)} / {len(df_ready)}")
print(f"Fraction: {len(high_funding) / len(df_ready) * 100:.1f}%")
# Annualized carry estimate (holding the short during these hours)
avg_rate_during_high = high_funding["rate"].mean()
annualized_carry = avg_rate_during_high * 8760 # hours per year
print(f"Avg rate during high-funding: {avg_rate_during_high*100:.4f}%/h")
print(f"Annualized carry: {annualized_carry*100:.1f}%")
3. Fetch cumulative funding features¶
# Pre-computed 7-day cumulative funding
with MarketData(api_key="qp_...") as md:
feat = md.features(
SYMBOL,
exchange=EXCHANGE,
timeframe="funding",
features=["funding_cumulative_7d", "funding_norm", "premium_pct"],
start="2023-10-31",
)
# Sort entries by 7-day cumulative funding
top_carry = feat.sort("funding_cumulative_7d", descending=True).head(10)
print(top_carry.select(["time", "funding_cumulative_7d", "funding_norm"]))
4. Compute rolling carry return¶
# Strategy: short perp when funding_norm > 1.5, exit when < 0.5
# Assume: zero transaction costs, no slippage (first-pass analysis)
df_strat = df_ready.with_columns([
pl.when(pl.col("rate_normalized") > 1.5).then(pl.lit(-1.0))
.when(pl.col("rate_normalized") < 0.5).then(pl.lit(0.0))
.otherwise(None)
.alias("raw_signal")
]).with_columns(
pl.col("raw_signal").forward_fill().fill_null(0.0).alias("position")
)
# Short position earns funding when we hold -1
# Funding collected = -position × rate (short = +rate when positive)
df_strat = df_strat.with_columns(
(-pl.col("position") * pl.col("rate")).alias("funding_earned")
)
total_carry = df_strat["funding_earned"].sum()
hours_active = (df_strat["position"] != 0).sum()
print(f"Total funding collected: {total_carry*100:.2f}%")
print(f"Hours in position: {hours_active}")
print(f"Avg carry per active hour: {total_carry/hours_active*100:.4f}%")
5. Risk considerations¶
Funding arb is not risk-free. Key risks:
Basis risk: The spot/perp premium can shift dramatically. Use premium_pct to monitor:
# Monitor basis risk: how much did the basis move against us?
df_risk = feat.filter(pl.col("premium_pct").abs() > 1.0)
print(f"Hours with basis > 1%: {len(df_risk)}")
print(df_risk.select(["time", "premium_pct"]).sort("premium_pct", descending=True).head(5))
Funding reversal: Positive funding can flip negative, especially after large price corrections. The funding_norm z-score helps identify when funding is abnormally high vs. historically elevated but trending lower.
Margin/liquidation risk: A sharp move in the perp price can trigger margin calls even if the net position is delta-neutral. Always size positions with adequate margin buffer.
6. Historical context¶
From Hyperliquid mainnet launch (2023-10-31) through 2024-06:
| Metric | Value |
|---|---|
| Avg hourly BTC-USDC funding | ~0.0090%/h |
| Annualized (constant) | ~78.7% |
| % of hours with positive funding | ~73% |
| % of hours with funding > 1σ | ~21% |
| Max drawdown (basis risk) | ~2.8% |
These numbers are directional estimates from the data; your actual execution will differ based on spread, margin, and basis timing.
Next steps¶
- Market Regime Analysis — condition your carry trades on market regime
- Features Catalogue —
premium_pctandpremium_zscoredetails - Data Cleaning Decisions — understanding the basis spread