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.

Fig. — Funding collected each year by the short of an XBQ cash-and-carry on the four exchanges, in % of notional (dedalo's 2022, up to its bankruptcy)
Level 3 lessons
  1. 3.1 · Futures in depth

    1. 3.1.1 Main futures contracts
    2. 3.1.2 Futures curve: basis, cost of carry, contango and roll yield
    3. 3.1.3 Roll management, margins, SPAN and implied leverage
    4. 3.1.4 Diversified futures systems: Carver's approach
  2. 3.2 · Futures spreads

    1. 3.2.1 Calendar spreads, butterflies and condors
    2. 3.2.2 Intercommodity spreads: crack, spark and crush
    3. 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
    4. 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
    5. 3.2.5 Spread data and backtesting: listed, synthetic and legged
    6. 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
  3. 3.3 · Advanced forex

    1. 3.3.1 FX microstructure: OTC, ECN, last look and sessions
    2. 3.3.2 Carry and crash risk, momentum and value in currencies; baskets
    3. 3.3.3 Currency risk in multi-currency portfolios; execution via OANDA, IBKR or MT5
  4. 3.4 · Option theory and pricing

    1. 3.4.1 Payoffs, put-call parity and arbitrage bounds
    2. 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
    3. 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
    4. 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
    5. 3.4.5 Smile, skew and term structure; volatility surface with SVI
    6. 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
    7. 3.4.7 Advanced models: Heston, SABR and local volatility
  5. 3.5 · Option structures in Python

    1. 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
    2. 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
    3. 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
    4. 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
    5. 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
  6. 3.6 · Systematic volatility and options strategies

    1. 3.6.1 Systematic premium selling: put-write, strangles and their tail risk
    2. 3.6.2 Delta hedging, gamma scalping and dispersion
    3. 3.6.3 VIX and VSTOXX strategies: volatility futures and ETPs
    4. 3.6.4 Options backtesting: data, fills and event adjustments
  7. 3.7 · Crypto derivatives and specific strategies

    1. 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
    2. 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
    3. 3.7.3 Crypto options, on-chain data and DeFi
    4. 3.7.4 ccxt for trading on multiple exchanges
    5. 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:

Long spotShort perpetualShort quarterlyPerp. other exch.Variable fundingLocked-in basisTwo counterparties
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.
IN [01]python
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")
OUT [01]The perpetual's carry by exchange and 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 year

The 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 (0.01 %×3×3650.01\,\% \times 3 \times 365, 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?

IN [02]python
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())
OUT [02]How much the bullish premium narrows
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, tanh⁡(r7/0.15)>0.5\tanh(r_7/0.15) > 0.5), 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 LL on that leg, the capital per unit of notional is 1+1/L1 + 1/L 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:

IN [03]python
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)
OUT [03]Short-leg leverage: return and liquidations
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           60
Fig. — XBQ cash-and-carry on alfa by short-leg leverage: annual return on capital before costs and weeks in which the mark would have liquidated the short (weekly rebalancing, 2020-2025)

The 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:

IN [04]python
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")
OUT [04]What entering and exiting costs
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 exit

About 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 ln⁡(F/S)×365/days\ln(F/S) \times 365/\text{days}:

IN [05]python
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")
OUT [05]The basis measured on the quarterlies
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 points

The 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:

IN [06]python
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")
OUT [06]Lock in the basis or collect the funding, quarter by 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 quarter
Fig. — Basis locked when opening a cash-and-carry with the quarterly future against the funding the perpetual would have collected over the same quarter (alfa, XBQ, 23 quarters)

Over 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.

IN [07]python
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}%")
OUT [07]Each exchange's funding versus alfa's
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:

IN [08]python
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}%)")
OUT [08]Dislocations between exchanges in the published data
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:

Nov 6Nov 8 12:00Nov 11 14:00Nov 11 23:00NormalRumorsSuspensionHaltBankruptcy
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.

IN [09]python
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)
OUT [09]The carry of dedalo and its end
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.003066

Until 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%.

Fig. — Dedalo's planted bankruptcy: premium of its XBQ perpetual's last price over the index (top) and funding of each 8-hour period (bottom), 5 to 11 November 2022

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?

IN [10]python
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")
OUT [10]Four ways to spread counterparty risk
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 year

The 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

  1. Repeat the short-leg leverage table for XEQ on alfa (2x, 3x and 5x, weekly rebalancing). What changes and why?
Solution
IN [11]python
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)
OUT [11]Cash-and-carry on XEQ with the short leg at 3x
            rend_capital_pct  liq_semanal
apal_corto
2                       15.1            0
3                       17.0            5
5                       18.9           25

With 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.

  1. Compute the annual result of a funding differential long the cenit perpetual and short the boreal one (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
IN [12]python
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()
OUT [12]The boreal-cenit differential with costs
{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.

  1. 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
IN [13]python
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)
OUT [13]The lock-in-or-float rule on the six pairs
                 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.74

On 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

  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. ↩

Each answer is folded under its question

Self-assessment

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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