Funding Rate Arbitrage on Crypto Perpetuals: Implementation and Backtest

CrazyTomato1 pts0 comments

Profiting from Perpetuals: Implementing a Funding Rate Arbitrage Strategy with Backtesting | by DolphinDB | MediumSitemapOpen in appSign up<br>Sign in

Medium Logo

Get app<br>Write

Search

Sign up<br>Sign in

Profiting from Perpetuals: Implementing a Funding Rate Arbitrage Strategy with Backtesting

DolphinDB

5 min read·<br>Mar 10, 2026

Listen

Share

Press enter or click to view image in full size

Introduction<br>In traditional financial markets, arbitrage opportunities are rare, fleeting, and fiercely competed over. Cryptocurrency markets are different. One structural feature unique to crypto — the funding rate mechanism of perpetual contracts — creates a recurring, systematic opportunity that sophisticated traders have long exploited: funding rate arbitrage.<br>Unlike futures contracts with fixed expiry dates, perpetual contracts stay open indefinitely. To keep their prices anchored to the spot market, exchanges periodically charge funding fees between long and short holders. When the market is overwhelmingly bullish, longs pay shorts. When bearish sentiment dominates, shorts pay longs. A trader who can simultaneously hold an opposing spot position stands to collect these fees while remaining largely neutral to price direction.<br>This post walks through a complete implementation of a funding rate arbitrage strategy — from logic design to backtesting — using DolphinDB’s cryptocurrency backtesting engine and minute-level market data.<br>Strategy Logic<br>The core idea is straightforward: earn funding fees by taking a position in the perpetual contract market, while hedging price risk with an equivalent spot position. The result is a portfolio whose value is largely insensitive to price movements — profits come from the funding rate itself.<br>Short the contract (positive funding rate): When the funding rate exceeds +0.03%, the market is in a bullish state and long holders pay funding fees to shorts. The strategy responds by:<br>Shorting the perpetual contract<br>Buying an equivalent amount of the asset in the spot market<br>When the funding rate turns negative, signaling a sentiment shift, both positions are closed.<br>Long the contract (negative funding rate): When the funding rate falls below −0.03%, the market is bearish and short holders pay longs. The strategy responds by:<br>Going long on the perpetual contract<br>Selling an equivalent amount of the asset in the spot market<br>When the funding rate turns positive again, both positions are unwound.<br>Note: The implementation below covers the short-side strategy (positive funding rate scenario).

Strategy Implementation<br>Initialization<br>The initialize callback runs once when the engine is created. It loads the funding rate data for the backtest period and initializes a dictionary to track the previous funding rate — used later to detect trend reversals and determine exit timing.<br>def initialize(mutable context){<br>// Initialization<br>print("initialize")<br>// Backtest::setUniverse(context["engine"], context.Universe)<br>context["fundingRate"] = Backtest::getConfig(context["engine"])[`fundingRate]<br>context["lastlastFunding"] = dict(SYMBOL,ANY) // Stores the previous funding rate, used to determine exit conditions<br>}Pre-Market Setup<br>The beforeTrading callback runs once per trading day before market open. It organizes the day's funding rate data into a nested dictionary keyed by symbol and settlement time, making lookups fast and straightforward within the bar callback.<br>def beforeTrading(mutable context){<br>// Daily pre-market callback function<br>// The current trading date can be obtained via context["tradeDate"]<br>print("beforeTrading: " + context["tradeDate"])<br>// Retrieve the funding rate table for the current day and store it as a dictionary keyed by symbol<br>fundingRate = context["fundingRate"]<br>d = dict(STRING,ANY)<br>for (i in distinct(fundingRate.symbol)){<br>temp = select * from fundingRate where symbol = i and date(settlementTime) = context["tradeDate"]<br>replaceColumn!(temp, `settlementTime, datetime(exec settlementTime from temp))<br>d[i] = dict(temp.settlementTime, temp.lastFundingRate, true)<br>context["dailyLastFundingPrice"] = d<br>}Bar-Level Signal Logic<br>The onBar callback fires on every incoming minute bar. For each asset, it first resolves the applicable funding rate for the current time window — funding settlements occur at 00:00, 08:00, and 16:00 UTC — then checks current spot and futures positions before evaluating entry or exit conditions.<br>def onBar(mutable context, msg, indicator = NULL){<br>// ...<br>dailyFundingRate = context["dailyLastFundingPrice"]<br>// Iterate over multiple assets<br>for(i in msg.keys()){<br>istock = split(i,"_")[0]<br>istockFut = istock + "_futures"<br>istockSpo = istock + "_spot"<br>source = msg[i]["symbolSource"]<br>closePrice = msg[i]["close"]<br>// Current asset funding rate; select the corresponding funding rate based on the current time window<br>if(second(context["tradeTime"]) >= 16:00:00){fundingRateTime = temporalAdd(datetime(context["tradeDate"]), 16, "h")}<br>if(second(context["tradeTime"]) >= 08:00:00 and...

funding rate context market strategy arbitrage

Related Articles