Lesson 3.7.2 · 75 min
Funding arbitrage, basis with futures and cross-exchange arbitrage
The cash-and-carry collects the perpetual's funding or locks in the quarterly's basis without betting on the price. This lesson measures how much it paid on four synthetic exchanges and how much it narrowed, what it costs in margin and liquidations, when locking in the basis pays off and what is left of it all when the exchange that paid the most goes bankrupt.
- Module
- 3.7 · Crypto derivatives and specific strategies
- Duration
- 75 min
- Prerequisites
- 2.3.7 · Crypto-specific strategies: momentum, funding arbitrage and basis, 2.4.7 · Cost modeling: commissions, bid-ask spread, impact, financing and securities borrowing, 3.7.1 · Perpetuals: funding mechanics, mark price and liquidations
- Author
- Jesús Cuesta
- Updated
Level 3 lessons
3.1 · Futures in depth
3.2 · Futures spreads
- 3.2.1 Calendar spreads, butterflies and condors
- 3.2.2 Intercommodity spreads: crack, spark and crush
- 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
- 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
- 3.2.5 Spread data and backtesting: listed, synthetic and legged
- 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
3.3 · Advanced forex
3.4 · Option theory and pricing
- 3.4.1 Payoffs, put-call parity and arbitrage bounds
- 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
- 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
- 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
- 3.4.5 Smile, skew and term structure; volatility surface with SVI
- 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
- 3.4.7 Advanced models: Heston, SABR and local volatility
3.5 · Option structures in Python
- 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
- 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
- 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
- 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
- 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
3.6 · Systematic volatility and options strategies
3.7 · Crypto derivatives and specific strategies
- 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
- 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
- 3.7.3 Crypto options, on-chain data and DeFi
- 3.7.4 ccxt for trading on multiple exchanges
- 3.7.5 MiCA: the EU framework for crypto-assets and their service providers (CASPs)
Three ways to collect the carry
Lesson 2.3.7 introduced the cash-and-carry: buy the coin spot and sell the same quantity in a derivative, so that the price does not matter and the carry remains. With the level 3 data it can be done with four exchanges and two assets, with perpetuals and with quarterlies, and measure what lesson 2.3.7 only listed: how much margin the short leg needs, when it is liquidated, when the future pays off and what happens when the exchange goes bankrupt. There are three variants:
Mermaid source
flowchart TB C[Long spot] --> P[Short perpetual] C --> T[Short quarterly] C --> O[Perp. other exch.] P --> F[Variable funding] T --> B[Locked-in basis] O --> R[Two counterparties]
- With the perpetual. It collects each period's funding, which changes and can turn negative.
- With the quarterly. The basis is locked in at opening: at expiry the future settles to the index and the result is the initial difference, whatever funding does.
- Split across exchanges. The spot on one and the derivative on another, or the derivative on the one that pays the most. It gains some carry or margin and adds a counterparty.
import numpy as np
import pandas as pd
fu = pd.read_parquet("cripto3_funding.parquet")
carry = (100 * fu.groupby(["activo", "exchange",
fu["ts"].dt.year])["tasa_funding"]
.sum()).unstack()
print("funding collected by the short, % of notional per year:")
print(carry.round(1).to_string())
suelo = 100 * 0.0001 * 3 * 365
print(f"floor of the interest term: {suelo:.2f}% a year")funding collected by the short, % of notional per year:
ts 2020 2021 2022 2023 2024 2025
activo exchange
XBQ alfa 25.6 24.4 10.4 11.8 11.2 9.7
boreal 28.7 26.4 11.4 13.3 12.2 10.5
cenit 23.7 23.0 8.8 10.8 9.9 8.8
dedalo 29.8 28.7 7.5 NaN NaN NaN
XEQ alfa 52.9 38.3 11.7 12.4 10.9 9.4
boreal 55.5 40.0 13.4 13.8 11.9 10.4
cenit 49.9 35.6 9.6 11.8 10.2 8.6
dedalo 59.5 42.8 10.1 NaN NaN NaN
floor of the interest term: 10.95% a yearThe table is what the perpetual's short collected each year, in % of notional, without costs or compounding. For XBQ,
from 23.0% to 29.8% in 2020 and 2021 depending on the exchange; since 2022, between 8.8% and 13.3% on the three that
survive. XEQ pays double in the years of euphoria (from 49.9% to 59.5% in 2020) and almost the same as XBQ afterwards.
The figure at the top draws the XBQ lines. Two things stand out: dedalo is the one that pays the most while it
exists, and since 2022 all the others stick to the 10.95% floor set by the formula's interest term
(, lesson 3.7.1).
That floor deserves attention. It is not the dollar interest rate or the result of supply and demand: it is a constant of the exchange's formula, the one a long pays while the premium is inside the band. The “market” carry is what lies above it, and in the data it is planted as a bullish premium that narrows with a half-life of 2.5 years (more capital devoted to arbitraging it). Is that half-life recovered?
from scipy.optimize import curve_fit
a = fu[(fu["exchange"] == "alfa") & (fu["activo"] == "XBQ")]
a = a.set_index("ts")
r7 = np.log(a["precio_indice"] / a["precio_indice"].shift(21))
z = np.tanh(r7 / 0.15) # euphoria: z close to 1
d = pd.DataFrame({"prima": a["prima_media"], "z": z}).dropna()
d["anos"] = (d.index - d.index[0]).days / 365.25
eu = d[d["z"] > 0.5]
def prima_alcista(X, amp, vida):
anos, z = X
return amp * np.exp(-np.log(2) * anos / vida) * z ** 2
(amp, vida), _ = curve_fit(prima_alcista, (eu["anos"], eu["z"]),
eu["prima"], p0=[0.003, 2.0])
print(f"{len(eu)} euphoria periods; amplitude {1e4 * amp:.1f} bp,"
f" half-life {vida:.2f} years")
print((1e4 * eu["prima"] / eu["z"] ** 2).groupby(eu.index.year)
.mean().round(1).to_dict())1091 euphoria periods; amplitude 33.7 bp, half-life 2.53 years
{2020: 29.0, 2021: 23.1, 2022: 15.8, 2023: 14.2, 2024: 10.0, 2025: 7.2}In the 1,091 periods of euphoria (high 7-day return, ), a nonlinear fit recovers an amplitude of 33.7 basis points per period and a half-life of 2.53 years, versus the planted 35 and 2.5. The premium per unit of euphoria falls from 29.0 basis points in 2020 to 7.2 in 2025. The data is not reality, but the direction matches what He, Manela, Ross and von Wachter document: the deviations of perpetuals from their no-arbitrage price have been shrinking over time1Note 1 · S. He, A. Manela, O. Ross and V. von Wachter, “Fundamentals of Perpetual Futures”, arXiv 2212.06888 (version 1 from December 2022, revised later). The abstract says the deviations are larger than in currency markets, comove across coins and shrink over time; the lesson does not cite figures from the paper..
The short leg needs margin
The cash-and-carry has no price risk on paper, but the capital is not just the spot. The short leg needs margin, and
with isolated margin it is liquidated if the mark rises to its liquidation price, even if the spot has gained the same
(lesson 3.7.1). With leverage on that leg, the capital per unit of notional is and the return on capital,
the carry divided by that amount. More leverage raises the return and brings liquidation closer. The 1-hour bars of
alfa let us count how many times it would have happened if every week (or every month) the margin were topped up and
the short leg reopened at the price of that moment:
h1 = pd.read_parquet("cripto3_1h.parquet")
b = h1[h1["activo"] == "XBQ"].set_index("ts")
f_alfa = a["tasa_funding"]
anual = f_alfa.sum() / ((f_alfa.index[-1] - f_alfa.index[0]).days
/ 365.25)
MMR = 0.005
filas = []
for L in (1, 2, 3, 5, 10, 20):
fila = {"apal_corto": L,
"rend_capital_pct": 100 * anual / (1 + 1 / L)}
for nombre, frec in (("semanal", "7D"), ("mensual", "30D")):
g = b.groupby(pd.Grouper(freq=frec))
liq = g["apertura"].first() * (1 + 1 / L) / (1 + MMR)
fila[f"liq_{nombre}"] = int((g["mark_maximo"].max() >= liq)
.sum())
filas.append(fila)
print(f"average carry on alfa, XBQ: {100 * anual:.1f}% of notional")
pd.DataFrame(filas).set_index("apal_corto").round(1)average carry on alfa, XBQ: 15.5% of notional
rend_capital_pct liq_semanal liq_mensual
apal_corto
1 7.8 0 0
2 10.4 0 2
3 11.6 1 9
5 12.9 8 28
10 14.1 71 52
20 14.8 180 60The average carry of XBQ on alfa is 15.5% a year of notional. Without leverage on the short (1x), the capital doubles
and the return falls to 7.8%; with 3x, 11.6%, and in six years the mark would have liquidated the short in only one
week; with 10x, 14.1%, but 71 weeks out of 314 end with the short liquidated and the spot unhedged. With monthly
rebalancing the risk grows at low leverage (9 liquidations at 3x versus 1): in 30 days the price has more time to rise
before the margin is topped up. The monthly figures at 10x and 20x are lower only because there are fewer months than
weeks. The chart sums up the trade-off: the last points of return are paid for with a number of liquidations that
grows much faster.
Costs count too. Entering and exiting pays four sides (buying and selling spot, selling and buying back the perpetual). With an assumption of 5 basis points per side, on the order of the commission of a client taking liquidity on a large exchange (lesson 2.4.7), it costs:
COMISION = 5 / 1e4 # assumption: 5 bp per side and leg
lados = 4 # buy and sell spot and perpetual
for nombre, diario in [("floor (0.03% daily)", 0.0003),
("2025 average", carry.loc[("XBQ", "alfa"),
2025] / 100 / 365)]:
dias = lados * COMISION / diario
print(f"{nombre}: {dias:.1f} days of carry to pay for entry"
" and exit")floor (0.03% daily): 6.7 days of carry to pay for entry and exit
2025 average: 7.5 days of carry to pay for entry and exitAbout 7 days of carry at the floor, 7.5 at the 2025 pace. A cash-and-carry that is opened and closed often, for example every time a week's funding drops (exercise of lesson 2.3.7), eats the carry in commissions.
The basis of the quarterlies
cripto3_futuros.parquet has a daily snapshot, at 00:00 UTC, of the two nearest quarterlies of each exchange and
asset; they expire on the last Friday of March, June, September and December at 08:00 UTC. The annualized basis is
measured with the index at the same time, as :
ft = pd.read_parquet("cripto3_futuros.parquet")
ind = (fu[fu["exchange"] == "alfa"]
.set_index(["ts", "activo"])["precio_indice"])
ft = ft.join(ind, on=["ts", "activo"]).dropna(subset=["precio_indice"])
ft["basis"] = np.log(ft["precio"] / ft["precio_indice"]) * 365 \
/ ft["dias"]
vivos = ft[ft["exchange"] != "dedalo"]
err = 100 * (vivos["basis"] - vivos["basis_anual_modelo"]).abs()
print(f"error versus the model's (points): median {err.median():.4f}"
f", maximum {err.max():.2f}, maximum beyond 10 days "
f"{err[vivos['dias'] > 10].max():.2f}")
x = vivos[(vivos["exchange"] == "alfa") & (vivos["activo"] == "XBQ")]
orden = x.groupby("ts").cumcount()
primero, segundo = x[orden == 0], x[orden == 1]
print("average basis of the front quarterly, % a year:")
print((100 * primero.groupby(primero["ts"].dt.year)["basis"].mean())
.round(1).to_dict())
dif = (segundo.set_index("ts")["basis"] - primero.set_index("ts")
["basis"])
print(f"second minus first: {100 * dif.mean():+.2f} points")error versus the model's (points): median 0.0029, maximum 1.33, maximum beyond 10 days 0.26
average basis of the front quarterly, % a year:
{2020: 22.3, 2021: 23.1, 2022: 10.9, 2023: 11.9, 2024: 11.1, 2025: 9.2}
second minus first: +0.40 pointsThe measured basis matches the model column with a median of 0.003 points; the maximum error, 1.33 annualized points,
appears in the last days before expiry, when the rounding of the price to the tick is multiplied by 365 divided by a
few days (beyond 10 days, 0.26). The average basis of the front XBQ quarterly on alfa is 22.3% and 23.1% in 2020 and
2021, and from 9.2% to 11.9% afterwards, in line with funding. The second quarterly trades 0.40 annualized points above
the first: the planted term premium.
The practical question is whether it pays to lock in the basis or collect the funding. For each quarterly, the cash-and-carry is opened when it has about 95 days left and the locked-in basis (in % of notional for the whole quarter) is compared with the funding the perpetual would have collected from that day until expiry:
def trimestres(fut, fund):
"""Per contract: basis locked in at <= 95 days to expiry, funding
collected until expiry and that of the previous 90 days (%)."""
filas = []
for contrato, c in fut.groupby("contrato"):
ent = c[c["dias"] <= 95].head(1)
if ent.empty:
continue
e0 = ent.iloc[0]
t0 = e0["ts"]
vto = (pd.Timestamp(e0["vencimiento"]).tz_localize("UTC")
+ pd.Timedelta(hours=8))
if t0 < pd.Timestamp("2020-02-01", tz="UTC") or vto.year > 2025:
continue
hasta_vto = fund[(fund.index > t0) & (fund.index <= vto)].sum()
previo = fund[(fund.index > t0 - pd.Timedelta(days=90))
& (fund.index <= t0)].sum()
filas.append({"contrato": contrato,
"fijado": 100 * np.log(e0["precio"]
/ e0["precio_indice"]),
"funding": 100 * hasta_vto,
"funding_previo": 100 * previo})
return pd.DataFrame(filas).set_index("contrato")
q = trimestres(x, f_alfa)
print(q.round(2).head(8).to_string())
print(f"{len(q)} quarters; locked-in mean {q['fijado'].mean():.2f}%,"
f" funding collected {q['funding'].mean():.2f}%, correlation "
f"{q['fijado'].corr(q['funding']):.2f}")
elige = np.where(q["fijado"] > q["funding_previo"], q["fijado"],
q["funding"])
print(f"rule “the larger of the two at opening”: {elige.mean():.2f}% "
f"per quarter") fijado funding funding_previo
contrato
XBQ-200626 2.04 8.94 2.81
XBQ-200925 2.15 4.49 8.81
XBQ-201225 1.23 8.87 4.37
XBQ-210326 11.98 14.12 8.52
XBQ-210625 1.00 3.76 13.68
XBQ-210924 2.51 4.05 3.73
XBQ-211231 15.55 2.90 4.75
XBQ-220325 3.58 2.27 4.19
23 quarters; locked-in mean 3.97%, funding collected 3.97%, correlation 0.24
rule “the larger of the two at opening”: 5.14% per quarterOver 23 quarters, the mean of the locked-in basis is 3.97% per quarter and that of the funding collected, also 3.97%. Locking in neither wins nor loses on average: it removes uncertainty. The correlation between the two is only 0.24, and the figure shows why: in any given quarter one beats the other by several points. The quarterly expiring in December 2021 locked in 15.55% and that quarter's funding gave 2.90%; the one expiring in June 2020 locked in 2.04% while funding gave 8.94%.
Since the basis in the data carries noise of its own that does not anticipate the next funding, a rule without information from the future exploits it: lock in the basis if it is larger than the funding of the previous 90 days and, if not, stay with the perpetual. It gives 5.14% per quarter. It is exercise 3; before believing it in real markets one would need to check whether, there, the noise of the basis is independent of future funding, and with 23 observations per pair the standard error is large.
Between exchanges: funding differentials and dislocations
Each exchange computes its funding with its own premium. If one systematically pays more than another, one can be short the perpetual of the one that pays more and long that of the one that pays less, without spot: the funding differential.
p = fu[fu["activo"] == "XBQ"].pivot(index="ts", columns="exchange",
values="tasa_funding")
dif = 100 * p.sub(p["alfa"], axis=0).drop(columns="alfa")
print("difference with alfa, percentage points per year:")
print(dif.groupby(dif.index.year).sum(min_count=1).round(2).T
.to_string())
diferencial = p["boreal"] - p["cenit"]
print(f"periods in which boreal pays the same as cenit: "
f"{100 * (diferencial.abs() < 1e-9).mean():.1f}%")difference with alfa, percentage points per year:
ts 2020 2021 2022 2023 2024 2025
exchange
boreal 3.03 1.99 1.05 1.42 0.98 0.86
cenit -1.93 -1.44 -1.59 -1.05 -1.32 -0.87
dedalo 4.19 4.33 -1.72 NaN NaN NaN
periods in which boreal pays the same as cenit: 71.4%boreal pays 3.03 points a year more than alfa in 2020 and 0.86 in 2025; cenit, between 0.87 and 1.93 less every
year; dedalo, 4.19 and 4.33 more in 2020 and 2021. The planted premium of each exchange is constant (half a basis
point more on boreal, half less on cenit), but the differential narrows every year. The reason is the formula's
band: in 71.4% of the periods boreal and cenit pay exactly the same, the fixed basis point. The difference in
premiums only passes into funding when the premium leaves the band, in the weeks of euphoria, and those are less and
less profitable. Exercise 2 computes what is left after costs.
Dislocations are something else: an exchange's spot price separates from the others' for a few hours. There are four planted in the data; the published daily closes reveal two:
cd = pd.read_parquet("cripto3_diario.parquet")
xb = cd[cd["activo"] == "XBQ"]
sp = xb.pivot(index="fecha", columns="exchange", values="cierre_spot")
ok = xb.pivot(index="fecha", columns="exchange",
values="retiradas_abiertas")
indice = sp.where(ok == True).median(axis=1)
desv = 100 * np.log(sp.div(indice, axis=0))
for e in ["alfa", "boreal", "cenit"]:
m = desv[e].abs()
print(f"{e}: standard deviation {desv[e].std():.3f}%, worst day "
f"{m.idxmax():%Y-%m-%d} ({desv[e][m.idxmax()]:+.2f}%)")alfa: standard deviation 0.092%, worst day 2025-10-10 (-3.96%)
boreal: standard deviation 0.035%, worst day 2020-01-23 (+0.14%)
cenit: standard deviation 0.122%, worst day 2020-03-12 (-5.45%)Outside those days, each exchange's spot deviates from the index with a standard deviation of between 0.035% and
0.122%, less than any commission. The two large episodes are that of March 12, 2020 on cenit (−5.45%) and that of
October 10, 2025 on alfa (−3.96%), both in the middle of a sell-off. Exploiting them requires buying on the cheap
exchange and selling on the others at the same time, which can only be done with inventory already in place at
both venues: there is no time to move coins or dollars from one exchange to another while it lasts. And they happen
when order books are thinnest, withdrawals most congested and the risk that the cheap exchange has a real problem is
highest. A dislocation can be an opportunity or the first symptom of a bankruptcy.
Counterparty risk: the bankruptcy of dedalo
dedalo was, until November 2022, the exchange that paid best. The timeline of its bankruptcy is in
cripto3_exchanges.csv and is inspired by that of FTX, without copying it:
Mermaid source
stateDiagram-v2 [*] --> Normal Normal --> Rumors: Nov 6 Rumors --> Suspension: Nov 8 12:00 Suspension --> Halt: Nov 11 14:00 Halt --> Bankruptcy: Nov 11 23:00 Bankruptcy --> [*]
With the suspension, withdrawals are closed and balances stop being convertible; the dedalo spot trades at a growing
discount and drops out of the index; funding turns very negative; at 14:00 on the 11th trading halts and, for the
course, balances and positions are worth zero.
ded = (fu[(fu["exchange"] == "dedalo") & (fu["activo"] == "XBQ")]
.set_index("ts"))
hasta = pd.Timestamp("2022-11-08 08:00", tz="UTC")
c_ded = ded["tasa_funding"][:hasta].sum()
c_alfa = f_alfa[:hasta].sum()
anos = (hasta - ded.index[0]).days / 365.25
print(f"cumulative carry until {hasta:%Y-%m-%d}: dedalo "
f"{100 * c_ded:.1f}%, alfa {100 * c_alfa:.1f}% of notional")
print(f"dedalo premium: {100 * (c_ded - c_alfa) / anos:.2f} points"
" a year")
ded[ded.index >= "2022-11-08"][["precio_indice", "ultimo",
"tasa_funding"]].head(7)cumulative carry until 2022-11-08: dedalo 69.3%, alfa 59.2% of notional
dedalo premium: 3.53 points a year
precio_indice ultimo tasa_funding
ts
2022-11-08 00:00:00+00:00 22086.5 22098.6 0.000100
2022-11-08 08:00:00+00:00 22055.8 22068.4 0.000100
2022-11-08 16:00:00+00:00 20037.7 20012.2 -0.002016
2022-11-09 00:00:00+00:00 19958.3 19190.9 -0.003800
2022-11-09 08:00:00+00:00 19623.3 18367.6 -0.002295
2022-11-09 16:00:00+00:00 21563.9 20084.3 -0.004368
2022-11-10 00:00:00+00:00 19746.2 17699.2 -0.003066Until November 8, 2022, the short of the XBQ perpetual collected 69.3% of notional on dedalo and 59.2% on alfa: a
premium of 3.53 points a year for being on dedalo. The table shows how it ends. On the 8th at 16:00, four hours after
withdrawals were suspended, the dedalo perpetual already trades slightly below the index and funding is −0.20% per
period: now the shorts pay. The next day the discount is 3.8% and funding, −0.38%.
The figure follows the whole episode: the dedalo perpetual glued to the index until the suspension and a discount
that reaches 17% on the 11th; funding, from one basis point before to between −20 and −50 basis points per period
after, with one period at the −50 basis point cap. Nothing in the prices or the funding of the previous days gave a
warning. What is left of a cash-and-carry depending on how the capital had been allocated?
L = 3
capital = 1 + 1 / L # per unit of notional: spot + margin
retirado = ded["tasa_funding"][:"2022-10-31"].sum() # at month end
c = {e: fu[(fu["exchange"] == e) & (fu["activo"] == "XBQ")]
.set_index("ts")["tasa_funding"][:hasta].sum()
for e in ["alfa", "boreal", "cenit"]}
escenarios = {
"all on dedalo, no withdrawals": 0.0,
"all on dedalo, withdrawing every month": retirado,
"a quarter on each exchange": (3 * capital + sum(c.values())
+ retirado) / 4,
"all on alfa": capital + c["alfa"],
}
for k, v in escenarios.items():
print(f"{k:40s} {v / capital:6.2f} times the capital")
riesgo = (c_ded - c_alfa) / anos / capital
print(f"dedalo premium on capital: {100 * riesgo:.1f}% a year")all on dedalo, no withdrawals 0.00 times the capital
all on dedalo, withdrawing every month 0.52 times the capital
a quarter on each exchange 1.22 times the capital
all on alfa 1.44 times the capital
dedalo premium on capital: 2.6% a yearThe four figures are the final capital, as of November 11, 2022, per unit of initial capital, with the short at 3x and
without costs. With everything on dedalo and without withdrawing profits, zero: the spot, the margin and almost three
years of carry. If the carry was withdrawn at the end of each month, 0.52 remains, the carry taken out; the capital is
lost. Split equally across the four exchanges, 1.22: the quarter on dedalo is lost and the rest collects. Everything
on alfa, 1.44. The dedalo premium, 3.53 points of notional a year, is 2.6% a year of capital: it is the annual
probability of bankruptcy that the premium compensated if a bankruptcy destroys the capital deposited. Nobody could
estimate it from prices.
For a carry strategy, the lesson is direct. The exchange is not neutral infrastructure: it is the counterparty of everything deposited on it. Higher funding or basis on an exchange can be the signal that the market is asking it for a premium for that risk. The defenses are those of any credit risk: limits per counterparty, periodic withdrawal of profits, separate custody when the exchange offers it and exchanges with verifiable supervision and asset segregation (lesson 3.7.5 covers the European framework).
What this changes in a carry backtest
- Carry net of the floor. Separate the 10.95% set by the formula from the market premium: the latter is the one that narrows, and the one a strategy tries to capture.
- Margin and liquidations of the short leg. Simulate the margin with the intraday mark and a realistic top-up frequency; a cash-and-carry without margin is not the strategy that can be traded.
- Costs per switch. Four sides each time you enter and exit: a week of carry.
- Lock in or float. The quarterly removes funding uncertainty until expiry, but it has its own rolls and its own margin.
- One counterparty per exchange. A backtest with prices from an exchange that no longer exists includes its carry and not its bankruptcy if the universe is built with the exchanges still alive: it is survivorship bias (lesson 2.5.1).
Exercises
- Repeat the short-leg leverage table for XEQ on
alfa(2x, 3x and 5x, weekly rebalancing). What changes and why?
Solution
bq = h1[h1["activo"] == "XEQ"].set_index("ts")
fe = fu[(fu["exchange"] == "alfa") & (fu["activo"] == "XEQ")]
fe = fe.set_index("ts")["tasa_funding"]
anual_e = fe.sum() / ((fe.index[-1] - fe.index[0]).days / 365.25)
filas = []
for L in (2, 3, 5):
g = bq.groupby(pd.Grouper(freq="7D"))
liq = g["apertura"].first() * (1 + 1 / L) / (1 + MMR)
filas.append({"apal_corto": L,
"rend_capital_pct": 100 * anual_e / (1 + 1 / L),
"liq_semanal": int((g["mark_maximo"].max() >= liq)
.sum())})
pd.DataFrame(filas).set_index("apal_corto").round(1) rend_capital_pct liq_semanal
apal_corto
2 15.1 0
3 17.0 5
5 18.9 25With XEQ the average carry on alfa is higher (the return on capital rises to 15.1% with 2x and to 17.0% with 3x),
but XEQ is more volatile than XBQ and the short leg is liquidated much more often: 5 weeks at 3x (versus 1 for XBQ) and
25 at 5x. The reasonable leverage of the short leg depends on the asset's volatility, not on the carry: with XEQ, 2x
yields more (15.1%) than XBQ at 3x and has no liquidation at all in six years.
- Compute the annual result of a funding differential long the
cenitperpetual and short theborealone (XBQ, same notional), deducting one entry and one exit per year at 5 basis points per side. Is it worth the capital and the two counterparties?
Solution
neto = diferencial.groupby(diferencial.index.year).sum() * 100
neto = neto - 4 * 5 / 1e4 * 100 # enter and exit once a year
neto.round(2).to_dict(){2020: 4.76, 2021: 3.24, 2022: 2.44, 2023: 2.28, 2024: 2.1, 2025: 1.53}The differential net of one entry and one exit per year goes from 4.76 points of notional in 2020 to 1.53 in 2025. But
the capital is not the notional: each leg needs its own margin on its exchange, with 3x on both 67% of notional, so in
2025 it yields about 2.3% on capital, well below a simple cash-and-carry with the short leg at 3x (11.6% on average on
alfa). In addition, there are two counterparties and two margins to monitor, and if one of the legs is liquidated or
its exchange fails, a directional position is left on the other. It only makes sense as a complement to a carry
portfolio already spread across exchanges, not as a strategy of its own.
- Apply the rule “lock in the basis if it is larger than the funding of the previous 90 days” to the six exchange and asset pairs that survive. Does it always beat the perpetual alone? What would make you distrust the result on real data?
Solution
filas = []
for (e, ac), fut in ft[ft["exchange"] != "dedalo"].groupby(
["exchange", "activo"]):
fund = (fu[(fu["exchange"] == e) & (fu["activo"] == ac)]
.set_index("ts")["tasa_funding"])
q2 = trimestres(fut, fund)
fija = q2["fijado"] > q2["funding_previo"]
filas.append({"exchange": e, "activo": ac,
"fijado": q2["fijado"].mean(),
"perpetuo": q2["funding"].mean(),
"regla": np.where(fija, q2["fijado"],
q2["funding"]).mean(),
"fija_pct": 100 * fija.mean()})
pd.DataFrame(filas).set_index(["exchange", "activo"]).round(2) fijado perpetuo regla fija_pct
exchange activo
alfa XBQ 3.97 3.97 5.14 43.48
XEQ 3.92 5.30 6.55 43.48
boreal XBQ 4.44 4.38 5.33 52.17
XEQ 4.18 5.69 7.04 21.74
cenit XBQ 3.13 3.62 4.42 26.09
XEQ 3.58 4.88 6.26 21.74On all six pairs, the rule beats the perpetual alone: from 3.97% to 5.14% per quarter on XBQ on alfa, and between
0.8 and 1.4 points on the others; it locks in the basis in between 22% and 52% of the quarters depending on the pair.
On XEQ, always locking in would do worse than the perpetual (3.92% versus 5.30% on alfa): the basis in the data only
prices in 0.8 times the funding, and with XEQ's high funding that difference weighs. In the data it has a known
explanation: the basis carries noise of its own that does not anticipate the next funding, so choosing the larger of
the two at opening captures that noise without looking at the future. On real data one should distrust it for several
reasons: there is no guarantee that the noise of the basis is independent of upcoming funding (it may price in
information that past funding does not have), 23 quarters per pair are few observations, the rule was chosen after
seeing the data (lesson 2.5.1) and the quarterly has costs and risks of its own (margin until expiry, lower liquidity
than the perpetual).
Footnotes
-
S. He, A. Manela, O. Ross and V. von Wachter, “Fundamentals of Perpetual Futures”, arXiv 2212.06888 (version 1 from December 2022, revised later). The abstract says the deviations are larger than in currency markets, comove across coins and shrink over time; the lesson does not cite figures from the paper. ↩
Each answer is folded under its question
Self-assessment
In calm markets, with the perpetual's premium inside the band, what does the short of a cash-and-carry collect on an exchange that uses Binance's formula?
- AThe fixed interest of 0.01% per period, almost 11% a year of notional: a convention of the formula, not a market price
- BNothing: without a premium there is no funding
- CThe dollar interest rate
- DHalf the premium
Show answer
Answer A. The fixed interest of 0.01% per period, almost 11% a year of notional: a convention of the formula, not a market price
With the premium between −4 and +6 basis points, F = P + (I − P) = I = 0.01% per period: 0.0001 × 3 × 365 = 10.95% a year. Since 2022 the carry of the three surviving exchanges stays close to that floor; what narrows is the bullish premium, with a half-life of 2.5 years in the data.
The short leg of the XBQ cash-and-carry on alfa is held at 10x and rebalanced every week. What does the data say?
- AThat it is the best option: 14.1% on capital with no risk
- BThat there are no liquidations because the position is hedged
- CThat leverage does not change the return
- DThat the return on capital rises from 11.6% (3x) to 14.1%, but the mark would have liquidated the short in 71 of 314 weeks, leaving the spot unhedged
Show answer
Answer D. That the return on capital rises from 11.6% (3x) to 14.1%, but the mark would have liquidated the short in 71 of 314 weeks, leaving the spot unhedged
The hedge offsets the PnL between the two legs, but each leg has its own margin. A 9% or 10% rise within the week liquidates the 10x short even if the spot has gained the same. More leverage yields a little more on capital and multiplies the liquidations.
Basis locked in with the quarterly and funding collected by the perpetual in the same quarter: over 23 XBQ quarters on alfa, how do they relate?
- AThe basis always wins
- BFunding always wins
- CThe same mean (3.97% per quarter) and a correlation of only 0.24: each quarter one beats the other by several points
- DThey are identical by arbitrage
Show answer
Answer C. The same mean (3.97% per quarter) and a correlation of only 0.24: each quarter one beats the other by several points
The basis prices in the expected funding plus a premium, with noise of its own. Locking it in removes the funding uncertainty, but it does not win on average. Choosing the larger of the two at opening (the basis or the funding of the previous 90 days) gives 5.14%: in the data, the noise of the basis does not predict the next funding, and the choice can be made without looking at the future.
boreal pays 3.03 points a year more than alfa in 2020 and 1.05 in 2022, even though the planted difference in its premium is constant. Why does it narrow?
- ABecause of the bankruptcy of dedalo
- BBecause the formula fixes funding at 0.01% while the premium is inside the band: a slightly different premium only shows when it leaves the band, and in quiet years it almost never does
- CBecause boreal lowered its fees
- DBecause of rounding
Show answer
Answer B. Because the formula fixes funding at 0.01% while the premium is inside the band: a slightly different premium only shows when it leaves the band, and in quiet years it almost never does
In 71.4% of the periods boreal and cenit pay exactly the same, the band's basis point. The differential between exchanges lives in the weeks of euphoria, which in the data (and, according to He, Manela, Ross and von Wachter, in real markets) are less and less profitable.
dedalo paid 3.53 points a year more funding than alfa until its bankruptcy. With the short leg at 3x, what annual probability of bankruptcy did that premium compensate?
- ANone: the premium was free
- BAbout 2.6% a year: the premium on capital; a bankruptcy takes the entire capital deposited on the exchange
- C35%
- D0.01%
Show answer
Answer B. About 2.6% a year: the premium on capital; a bankruptcy takes the entire capital deposited on the exchange
With capital of 1 + 1/3 per unit of notional, 3.53 points of notional are 2.6% of capital a year. If bankruptcy destroys all the capital deposited, the premium compensates a probability of bankruptcy of that order, which no price data allowed anyone to estimate before November 8.
Key points
- The perpetual's carry has two parts: the floor of almost 11% a year set by the formula's interest term and a market premium. In the data the bullish premium narrows with a half-life of 2.53 years (planted: 2.5); since 2022 the carry stays close to the floor.
- The short leg needs margin and can be liquidated even if the position is hedged: with 3x and weekly rebalancing, 11.6% a year on capital and one liquidation in six years; with 10x, 14.1% and 71 liquidated weeks.
- The quarterly's basis locks in the carry until expiry. Over 23 quarters, the locked-in mean (3.97%) is the same as the funding collected, with a correlation of 0.24: locking in removes uncertainty, it does not add return.
- Funding differentials between exchanges are eaten by the clamp band (71.4% of periods pay the same) and narrow every year. Price dislocations are rare, brief and require inventory on both exchanges.
- dedalo's premium (3.53 points a year of notional) paid for a bankruptcy risk of about 2.6% a year of capital. With everything on dedalo, everything was lost; spread across four exchanges, a quarter of the capital.
- At FTX, according to the CFTC and SEC complaints, customer funds were diverted to Alameda, which was exempt from the liquidation engine: counterparty risk does not show in prices until it is too late.
Resources
Documentation
- CFTC: complaint against Samuel Bankman-Fried, FTX Trading and Alameda Research (S.D.N.Y., December 13, 2022, PDF)
- CFTC: press release 8638-22 on the complaint (December 13, 2022)
- SEC: press release 2022-219, SEC Charges Samuel Bankman-Fried with Defrauding Investors (December 13, 2022)
- Binance: Introduction to Binance Futures Funding Rates
- He, Manela, Ross and von Wachter: Fundamentals of Perpetual Futures (arXiv 2212.06888)