Lesson 2.3.7 · 65 min
Crypto-specific strategies: momentum, funding arbitrage and basis
Crypto trades every day around the clock, moves as a block and has two sources of carry of its own: the funding of perpetuals and the basis of dated futures. This lesson measures the common factor of fifteen synthetic coins, tests a planted weekly cross-sectional momentum with next-day execution and costs, and introduces the cash-and-carry idea, which level 3 develops.
- Module
- 2.3 · Strategy families
- Duration
- 65 min
- Prerequisites
- 1.2.6 · Crypto: spot, perpetuals, funding and custody, 1.3.5 · Crypto and on-chain data, 1.4.4 · Universes without survivorship bias: delistings, mergers and symbol changes
- Author
- Jesús Cuesta
- Updated
Level 2 lessons
2.1 · Statistics and econometrics for trading
- 2.1.1 Hypothesis tests, p-values, bootstrap and regression with robust standard errors
- 2.1.2 Stationarity: ADF, KPSS, differencing and fractional differencing
- 2.1.3 ARMA and ARIMA models: autocorrelation (ACF) and partial autocorrelation (PACF)
- 2.1.4 Volatility models: ARCH, GARCH, EGARCH and GJR with the arch library
- 2.1.5 Cointegration: Engle-Granger, Johansen and the half-life of mean reversion
- 2.1.6 Kalman filter for dynamic hedge ratios
- 2.1.7 Regime detection: hidden Markov models and structural breaks
- 2.1.8 The multiple testing problem: Bonferroni, FDR and the “factor zoo”
2.2 · Anatomy of a trading system
2.3 · Strategy families
- 2.3.1 Trend following and momentum
- 2.3.2 Mean reversion: RSI and Bollinger, intraday reversal and gaps
- 2.3.3 Pairs trading and statistical arbitrage: distance, cointegration and Kalman
- 2.3.4 Carry in currencies, futures and bonds; seasonality and calendar effects
- 2.3.5 Factors (value, quality, low volatility, size) and event strategies
- 2.3.6 Introduction to market making: inventory, spread and adverse selection (Avellaneda-Stoikov)
- 2.3.7 Crypto-specific strategies: momentum, funding arbitrage and basis
2.4 · Backtesting: engines and paradigms
- 2.4.1 Vectorized backtest from scratch: matrices, lag, costs and limits
- 2.4.2 Event-driven backtest from scratch: loop, portfolio, orders and fills
- 2.4.3 vectorbt: massive parameter sweeps and portfolio simulation
- 2.4.4 backtesting.py and backtrader: two teaching engines and their status
- 2.4.5 NautilusTrader: architecture, data, strategies and the same code in backtest and live
- 2.4.6 LEAN/QuantConnect and Zipline Reloaded; bt for asset allocation
- 2.4.7 Cost modeling: commissions, bid-ask spread, impact, financing and securities borrowing
- 2.4.8 Fill modeling: limit orders, queue, partial fills, gaps and stops
2.5 · Biases and validation
- 2.5.1 Backtest biases: look-ahead, survivorship, selection and narrative
- 2.5.2 In-sample and out-of-sample: anchored and rolling walk-forward
- 2.5.3 Combinatorial purged cross-validation for strategies
- 2.5.4 Deflated Sharpe Ratio and Probability of Backtest Overfitting (PBO)
- 2.5.5 Monte Carlo: trade resampling, block bootstrap and signal permutations
- 2.5.6 Parameter sensitivity: plateaus versus peaks, robustness across assets and periods
- 2.5.7 Synthetic data for stress testing: GBM, GARCH and bootstrapping
- 2.5.8 “Honest backtest” checklist before moving to paper trading
2.6 · Performance metrics
A market that never closes and moves as a block
The crypto market has three features that change how a strategy is designed (lessons 1.2.6 and 1.3.5). It never closes: the daily “session” is a convention (here, the close at 00:00 UTC) and weekends count. It has derivatives of its own: the perpetual, a future with no expiry anchored to spot with periodic funding payments, and dated futures whose price departs from spot by the basis. And almost all coins rise and fall at the same time.
Mermaid source
flowchart TB C[Crypto market] --> F[Common factor] C --> M[Cross-sect. momentum] C --> D[Derivatives] D --> P[Perpetual: funding] D --> T[Future: basis] P --> K[Cash-and-carry] T --> K
import numpy as np
import pandas as pd
cd = pd.read_parquet("cripto2_diario.parquet")
P = cd.pivot(index="fecha", columns="moneda", values="cierre")
r = P.pct_change(fill_method=None)
resumen = pd.DataFrame({
"desde": P.apply(lambda s: s.first_valid_index()).dt.date,
"hasta": P.apply(lambda s: s.last_valid_index()).dt.date,
"vol_anual_pct": 100 * np.log1p(r).std() * np.sqrt(365),
})
print(resumen.round(0).sort_values("vol_anual_pct").to_string())
print("worst day of KJT and KLU:",
(100 * r[["KJT", "KLU"]].min()).round(1).to_dict()) desde hasta vol_anual_pct
moneda
KAL 2018-01-01 2025-12-31 67.0
KMV 2019-07-01 2025-12-31 73.0
KCX 2018-01-01 2025-12-31 74.0
KGN 2018-01-01 2025-12-31 75.0
KES 2018-01-01 2025-12-31 78.0
KFL 2018-01-01 2025-12-31 81.0
KNX 2020-09-15 2025-12-31 85.0
KRM 2018-01-01 2025-12-31 98.0
KHX 2018-01-01 2025-12-31 99.0
KBR 2018-01-01 2025-12-31 103.0
KIO 2018-01-01 2025-12-31 103.0
KLU 2018-01-01 2022-11-30 108.0
KDL 2018-01-01 2025-12-31 109.0
KOB 2021-03-01 2025-12-31 111.0
KJT 2018-01-01 2022-05-27 138.0
worst day of KJT and KLU: {'KJT': -61.7, 'KLU': -45.5}Twelve coins trade for all eight years; KMV, KNX and KOB start later, and KJT and KLU stop trading in 2022
after losing 99% and 90% in a few days (their worst day, −61.7% and −45.5%). Annual volatility ranges from 67% for
KAL, the largest and most liquid, to 138% for KJT: between three and seven times that of a stock index. A universe
built today from the coins that exist would have neither KJT nor KLU.
The common factor
The crypto market can be summarized in an equal-weighted index of the coins trading each day. A regression of each coin on that index gives its beta and its , the fraction of its daily variance explained by the common factor:
mercado = r.mean(axis=1)
filas = {}
for mon in r:
d = pd.concat([r[mon], mercado], axis=1).dropna()
b, a = np.polyfit(d.iloc[:, 1], d.iloc[:, 0], 1)
filas[mon] = {"beta": b, "r2": d.corr().iloc[0, 1] ** 2}
fc = pd.DataFrame(filas).T.sort_values("r2")
corr = r.corr().to_numpy()
media_corr = corr[np.triu_indices_from(corr, 1)].mean()
completas = r.dropna()
valores = np.linalg.eigvalsh(np.corrcoef(completas.T.to_numpy()))
print(f"mean correlation: {media_corr:.2f}; first component: "
f"{valores[-1] / valores.sum():.0%} of the variance")
fc.round(2).Tmean correlation: 0.58; first component: 70% of the variance
KJT KBR KHX KOB KIO KMV KES KFL KRM KCX KLU KDL KGN KNX KAL
beta 0.91 0.94 0.91 1.31 1.08 0.86 0.85 0.91 1.12 0.86 1.06 1.37 0.94 1.15 0.89
r2 0.40 0.40 0.42 0.54 0.54 0.59 0.59 0.62 0.63 0.67 0.71 0.75 0.75 0.77 0.84The average pairwise correlation is 0.58 and the first principal component explains 70% of the variance of the
fifteen. The against the index ranges from 0.40 for KJT and KBR to 0.84 for KAL: for the large coin, more
than four fifths of what happens each day is the whole market; for those with more idiosyncratic volatility, less than
half. Betas are between 0.85 and 1.37. For a strategy, the consequence is direct: a long-only portfolio is, above all,
a bet on the common factor, and the cross-sectional signal can only be isolated with a long-short portfolio.
Weekly cross-sectional momentum
The planted hypothesis is a short-term momentum: the coins that have risen most over the last four weeks keep doing slightly better than the rest the following week. Liu, Tsyvinski and Wu (2022) find in real data that a three-factor model (market, size and momentum) explains the cross-section of crypto returns; Liu and Tsyvinski (2021) document a strong time series momentum.
The signal's timing matters. The signal is computed with Sunday's close (28-day return up to that close) and the position is taken at Monday's close, the next session:
Mermaid source
flowchart LR D[Sunday close] --> S[28-day signal] S --> L[Monday order] L --> H[7 days]
from scipy import stats
lunes = [i for i, f in enumerate(P.index)
if f.dayofweek == 0 and 29 <= i < len(P) - 8]
logp = np.log(P)
ic = {"at Sunday close": [], "at Monday close": []}
for i in lunes:
senal = logp.iloc[i - 1] - logp.iloc[i - 29]
for clave, ent in [("at Sunday close", i - 1),
("at Monday close", i)]:
fut = logp.iloc[ent + 7] - logp.iloc[ent]
ok = senal.notna() & fut.notna()
ic[clave].append(stats.spearmanr(senal[ok], fut[ok])[0])
ic = pd.DataFrame(ic)
pd.DataFrame({"ic_medio": ic.mean(),
"t": ic.mean() / ic.std() * np.sqrt(len(ic)),
"semanas_pos_pct": 100 * (ic > 0).mean()}).round(3) ic_medio t semanas_pos_pct
at Sunday close 0.068 4.820 59.223
at Monday close 0.069 4.842 60.922The weekly rank IC is +0.068 executing at Sunday's close (the signal's) and +0.069 at Monday's, with a t of 4.8 in both cases: the planted signal (expected IC ≈ +0.05) is clearly recovered, and it is positive in 59% to 61% of weeks. That next-day execution does not make it worse is a property of this particular signal: the planted return is spread evenly across the whole week, and the Monday-to-Monday window also captures almost all of it. A signal whose effect were concentrated in the first few hours would not survive it.
The portfolio buys the five coins with the highest signal and sells the five with the lowest, equally weighted, enters at Monday's close and is rebuilt every week. The cost is an assumption of the lesson: 10 basis points per side on traded notional, between commission and slippage, in the order of what a retail client taking liquidity on a large exchange with liquid coins pays. Short positions are assumed to be in perpetuals, so they would also pay or receive funding, which is ignored here.
COSTE = 10 / 1e4
pesos = pd.DataFrame(0.0, index=P.index, columns=P.columns)
for i in lunes:
senal = (logp.iloc[i - 1] - logp.iloc[i - 29]).dropna()
orden = senal.rank()
w = (pd.Series(0.0, index=P.columns)
.add((orden > len(orden) - 5) / 5, fill_value=0)
.sub((orden <= 5) / 5, fill_value=0))
pesos.iloc[i + 1:i + 8] = w.to_numpy() # from Tuesday
pesos = pesos.where(P.notna(), 0.0)
giro = pesos.diff().abs().sum(axis=1)
ls = (pesos * r.fillna(0)).sum(axis=1) - COSTE * giro
ls = ls[ls.index >= P.index[lunes[0] + 1]]
ew = mercado.reindex(ls.index)
def metricas(x):
eq = (1 + x).cumprod()
return pd.Series({
"rent_anual_pct": 100 * x.mean() * 365,
"vol_anual_pct": 100 * x.std() * np.sqrt(365),
"sharpe": x.mean() / x.std() * np.sqrt(365),
"max_dd_pct": 100 * (eq / eq.cummax() - 1).min(),
"beta": np.polyfit(ew, x, 1)[0]})
coste_anual = 100 * COSTE * giro[ls.index].mean() * 365
print(f"annual commission cost: {coste_anual:.2f} %")
pd.DataFrame({"long_short_neto": metricas(ls),
"indice_equiponderado": metricas(ew)}).round(2)annual commission cost: 5.80 %
long_short_neto indice_equiponderado
rent_anual_pct 46.89 30.46
vol_anual_pct 39.94 70.79
sharpe 1.17 0.43
max_dd_pct -41.45 -84.27
beta -0.05 1.00Net of commissions, the portfolio returns 46.9% a year with a volatility of 39.9%: a Sharpe of 1.17 and a beta of −0.05 against the index, which is almost zero. Commissions, with high weekly turnover, take 5.8 points a year. The equal-weighted index returns 30.5% with a Sharpe of 0.43 and a maximum drawdown of 84%: that is the common factor's premium in this data, not the signal's.
The Sharpe of 1.17 is not a promise for real crypto: it is what a planted signal with an IC of 0.07 gives across fifteen synthetic coins. What is useful is to compare it with what was planted. With an average difference of 1.96 between the normal rank scores of the two legs, the generator promises about 70 points a year before costs, and the portfolio earns about 53 (46.9 plus 5.8 of commissions). With a volatility of 40% over eight years, the standard error of the mean annual return is about 14 points: the difference is little more than one standard error, consistent with chance. And the maximum drawdown of 41%, in a market-neutral portfolio, calls for a look at the worst days:
peores = ls.nsmallest(3)
print((100 * peores).round(1).to_string())
d = peores.index[0]
aporte = (pesos.loc[d] * r.fillna(0).loc[d]).nsmallest(2)
print("worst day, by coin (% of capital):",
(100 * aporte).round(1).to_dict())
m = aporte.index[0]
print(f"{m} that day: {100 * r.loc[d, m]:+.0f} %, "
f"weight {pesos.loc[d, m]:.2f}")
eq = (1 + ls).cumprod()
dd = eq / eq.cummax() - 1
print("maximum drawdown:", eq[:dd.idxmin()].idxmax().date(), "to",
dd.idxmin().date(), f"({100 * dd.min():.1f} %)")fecha
2022-05-03 -16.5
2023-08-22 -10.6
2022-06-21 -10.0
worst day, by coin (% of capital): {'KOB': -15.6, 'KES': -1.2}
KOB that day: +78 %, weight -0.20
maximum drawdown: 2021-03-08 to 2022-06-26 (-41.4 %)The worst day is May 3, 2022: KOB, which the portfolio held short with 20% of capital, rises 78% and the short leg
loses 15.6% of capital in one session; the rest of the positions barely count. The next two worst days, of −10.6% and
−10.0%, are of the same magnitude, and the maximum drawdown, 41.4%, runs from March 8, 2021 to June 26, 2022. The
generator clips idiosyncratic innovations at ±5 standard deviations, and even so, with the volatility of these coins,
there are days of up to +84% according to the data README. It is the risk specific to the short leg: a short position
in a small, volatile asset can lose in one day what the signal earns in months. Per-coin weight limits, stops on the
short leg and inverse-volatility weighting are the usual answer, and all of them change the result.
The figure shows the portfolio's equity curve against the index's, on a log scale. The portfolio rises strongly in 2018 and 2019, flattens in 2020 and 2021, falls in the first half of 2022 and rises again from 2023. The index, in contrast, loses more than half in 2018 and in 2022 and gains in the bullish stretches: two almost independent curves, as the beta says.
Perpetual funding
A perpetual does not expire. To keep its price from drifting away from spot, every 8 hours longs pay shorts a
percentage of notional, the funding, if the perpetual trades above the index, and the other way round if it trades
below (lesson 1.2.6). When the market is euphoric and many want to be long with leverage, funding rises.
cripto2_funding.parquet has the rate for each period of KAL, KBR and KCX:
fu = pd.read_parquet("cripto2_funding.parquet")
fu["fecha"] = fu["ts"].dt.tz_convert(None).dt.normalize()
diario = fu.groupby(["fecha", "moneda"])["tasa_funding"].sum().unstack()
anual = 100 * diario * 365 # % per year, not compounded
tabla = pd.DataFrame({
"media_pct": anual.mean(),
"dias_negativo_pct": 100 * (diario < 0).mean(),
"peor_30d_pct": anual.rolling(30).mean().min(),
"mejor_30d_pct": anual.rolling(30).mean().max(),
})
r7 = np.log(P[anual.columns]).diff(7)
tabla["corr_con_r7"] = [anual[c].corr(r7[c]) for c in anual]
print(tabla.round(2))
print((anual.groupby(anual.index.year).mean()).round(1).T) media_pct dias_negativo_pct peor_30d_pct mejor_30d_pct corr_con_r7
moneda
KAL 11.52 35.63 -36.30 52.86 0.84
KBR 11.40 42.09 -36.93 63.70 0.89
KCX 14.04 34.60 -37.91 63.72 0.89
fecha 2018 2019 2020 2021 2022 2023 2024 2025
moneda
KAL 1.4 15.2 13.1 11.3 2.6 20.3 18.9 9.4
KBR 5.9 9.0 9.4 14.3 14.5 12.3 9.8 16.0
KCX 5.8 20.8 15.5 13.8 9.0 16.4 12.4 18.7Average funding, annualized without compounding, is 11.4% to 14.0%: on average, longs pay shorts somewhat more than
the 0.01% base rate per period. But it is not stable. Between 35% and 42% of days it is negative; the worst 30-day
average is −36% to −38% annualized, and the best, +53% to +64%. The correlation with the previous 7 days' return is
0.84 to 0.89: it rises after rallies, which is the planted formula
( per period, plus noise). By year, KAL's ranges from 1.4% in 2018 to 20.3% in
2023.
Cash-and-carry: collecting funding without betting on price
The cash-and-carry buys the coin on spot and sells the same amount in the perpetual. The two legs offset each other: if the price rises, spot gains and the perpetual loses by the same amount. What is left is the funding the short leg collects when it is positive (and pays when it is negative), minus the costs of entering and exiting both legs.
Mermaid source
flowchart TB K[Capital] --> S[Long spot] K --> M[Short margin] M --> P[Short perpetual] P --> F[Collects funding] S --> N[Price neutral] P --> N
f_kal = diario["KAL"]
cobrado = f_kal.groupby(f_kal.index.year).sum() * 100
print("funding collected by the short, % of notional per year:")
print(cobrado.round(1).to_string())
# capital: 1 in spot + margin of the short perpetual
# (1/3 of notional: 3x leverage)
rend_capital = f_kal.sum() / (1 + 1 / 3) / (len(f_kal) / 365)
print(f"annual return on capital: {100 * rend_capital:.1f} %"
" (before costs)")
subida_30d = (P["KAL"] / P["KAL"].rolling(30).min() - 1).max()
print(f"largest 30-day rise in KAL: {100 * subida_30d:.0f} %")funding collected by the short, % of notional per year:
fecha
2018 1.4
2019 15.2
2020 13.1
2021 11.3
2022 2.6
2023 20.3
2024 18.9
2025 9.4
annual return on capital: 8.6 % (before costs)
largest 30-day rise in KAL: 116 %The short in the KAL perpetual collects between 1.4% (2018) and 20.3% (2023) of notional each year, and no year is
negative. But the capital required is not just the spot notional: the short perpetual needs margin. With 3x leverage
on that leg (a third of notional), the return on capital drops to 8.6% a year, before costs. And the last line is the
risk: KAL rose as much as 116% in 30 days. With margin of a third of notional, a rise like that eats it several
times over; it has to be topped up in time, by selling part of the spot or with cash, or the exchange liquidates the
short leg.
The basis of dated futures
The quarterly future expires on a fixed date and converges to spot at expiry. Its annualized difference from spot is the basis: . Buying spot and selling the future locks in that basis at entry: at expiry both legs are worth the same and the result is the initial difference, whatever happens to the price. In the data, the basis is formed from funding: about 0.8 times the annualized 7-day funding plus 2%.
ba = pd.read_parquet("cripto2_basis.parquet")
f7 = (diario.rolling(7).mean() * 365).stack().rename("funding_7d")
f7.index.names = ["fecha", "moneda"]
ba = ba.join(f7, on=["fecha", "moneda"]).dropna()
for mon, g in ba.groupby("moneda"):
b, a = np.polyfit(g["funding_7d"], g["basis_anual"], 1)
print(f"{mon}: basis = {a:.3f} + {b:.2f} × funding_7d,"
f" corr {g['basis_anual'].corr(g['funding_7d']):.2f}")
# one cash-and-carry per contract: opened 80-90 days before expiry
entrada = ba[ba["dias_al_vencimiento"].between(80, 90)]
entrada = entrada.groupby(["moneda", "contrato"]).first()
fijado = 100 * entrada["basis_anual"]
print("\nbasis locked in at entry (% per year) by coin:")
print(fijado.groupby("moneda").describe()[
["count", "mean", "min", "max"]].round(1))KAL: basis = 0.025 + 0.80 × funding_7d, corr 0.97
KBR: basis = 0.012 + 0.80 × funding_7d, corr 0.99
KCX: basis = 0.012 + 0.81 × funding_7d, corr 0.97
basis locked in at entry (% per year) by coin:
count mean min max
moneda
KAL 33.0 17.1 -25.9 58.6
KBR 33.0 8.1 -49.0 74.2
KCX 33.0 15.2 -30.0 84.4The regression recovers the planted relationship: a slope of 0.80 to 0.81 on 7-day funding and a correlation of 0.97
to 0.99. The intercept comes out between 1.2% and 2.5% (truth 2%). For the cash-and-carry with a future, what matters
is the second table: opening 80-90 days before expiry, the average locked-in basis is 17.1% a year in KAL, 8.1% in
KBR and 15.2% in KCX. The minimum is negative in all three: in panic quarters the future trades below spot, and the
cash-and-carry would have to be done in reverse (sell spot and buy the future), which requires borrowing the coin.
Unlike the perpetual, the basis is locked in at entry: if spot and the future are bought and sold at those prices, the
result at expiry is that basis, whatever funding does in the meantime.
Costs, commissions and what the backtest does not see
- Commissions per side. Exchange fees depend on the client's volume and on whether it provides or takes liquidity; a weekly long-short pays four sides per coin and week. The lesson's 10 basis point assumption takes several points a year.
- Weekends and hours. There is no official close: the “closing” price is whatever the vendor chooses (lesson 1.3.5). Two sources with different cutoff times give different signals.
- Collapses.
KJTandKLUlose 99% and 90% in a few days and stop trading. A universe built from today's coins erases them (lesson 1.4.4); a point-in-time one includes them, and a portfolio long in them pays for it. - Liquidations and leverage. Crypto derivatives trade with margin and automatic liquidation; a wick lasting a few minutes can close a position that in daily data was never in danger.
- Counterparty. The money is on the exchange. Diversifying across exchanges and periodically withdrawing profits are part of risk management, not an operational detail.
Exercises
- Repeat the weekly rank IC (execution at Monday's close) with signal windows of 7, 14, 28 and 56 days. Which gives the best IC? What does the result tell you about searching for the optimal window?
Solution
def ic_ventana(n):
v = []
for i in lunes:
if i - 1 - n < 0:
continue
senal = logp.iloc[i - 1] - logp.iloc[i - 1 - n]
fut = logp.iloc[i + 7] - logp.iloc[i]
ok = senal.notna() & fut.notna()
v.append(stats.spearmanr(senal[ok], fut[ok])[0])
v = np.array(v)
return v.mean(), v.mean() / v.std() * np.sqrt(len(v))
pd.DataFrame({n: ic_ventana(n) for n in [7, 14, 28, 56]},
index=["ic_medio", "t"]).T.round(3) ic_medio t
7 0.034 2.414
14 0.057 3.893
28 0.069 4.847
56 0.069 4.597The 28-day window, the planted one, gives the highest t (4.85), but the 56-day window ties with it in mean IC (+0.069, t = 4.60): they overlap, and the 56-day window fully contains the planted one. The 14-day window comes in at +0.057 and the 7-day one at +0.034. Without knowing the truth, nobody could tell from these numbers whether the right window is 28 or 56 days. In real data there is no known “true” window: trying several and keeping the best is a multiple test (lesson 2.1.8), and the winner's IC will be inflated. Here the difference between the two best is noise.
- A conditional cash-and-carry only holds the position when the average funding of the last 7 days, annualized,
exceeds 5%, and pays 10 basis points per side on each leg on entry and exit. Compute its annual result in
KALversus holding it all the time.
Solution
f7_kal = diario["KAL"].rolling(7).mean() * 365
dentro = (f7_kal > 0.05).shift(1, fill_value=False) # decided yesterday
cambios = dentro.astype(int).diff().abs().fillna(0)
coste = cambios * 2 * 2 * 10 / 1e4 # 2 legs × entry or exit
anos = len(f_kal) / 365
siempre = f_kal.sum() / anos
condicional = ((f_kal * dentro).sum() - coste.sum()) / anos
print(f"always in: {100 * siempre:.1f} % of notional per year")
print(f"conditional: {100 * condicional:.1f} % per year "
f"({int(cambios.sum())} entries and exits, "
f"{100 * dentro.mean():.0f} % of the time in)")always in: 11.5 % of notional per year
conditional: 4.7 % per year (183 entries and exits, 60 % of the time in)Holding the cash-and-carry all the time returns 11.5% of notional a year; the conditional version, only 4.7%. It is in 60% of the time, but it enters and exits 183 times in eight years, and each change pays four sides of 10 basis points: about 9 points a year in costs. Moreover, funding is persistent but noisy, and a rule with a fixed threshold enters just after rallies and exits just after drops. A rule with a band (enter above one level and exit below a lower one) would reduce the changes.
- The backtest of the
KALcash-and-carry collects net funding every year. List what would have to happen on a real exchange for it to lose money, even if funding were the one in the data.
Solution
A fast price rise could consume the margin of the short perpetual and the exchange could liquidate it before it was topped up: the hedge would disappear and the spot would be left unhedged, or would be sold at a loss to pay. Real costs (commissions, bid-ask spread on entry and exit, slippage in liquidations) could be higher than assumed. The perpetual could trade for days at a large premium or discount to the index, so that the short leg would not exactly offset spot. The exchange could freeze withdrawals or fail with the spot and the margin inside. And the funding in the data might not be the one that exchange pays: each one computes its own with its own index and its own formula.
Each answer is folded under its question
Self-assessment
The average correlation between the fifteen coins is 0.58 and the first principal component explains 70% of the variance. What does it imply for a cross-sectional momentum strategy?
- AThat it is useless
- BThat buying the winners is enough
- CThat it should be leveraged more
- DThat it has to be built long-short: buying some and selling others removes almost all of the common factor, which would otherwise dominate the result
Show answer
Answer D. That it has to be built long-short: buying some and selling others removes almost all of the common factor, which would otherwise dominate the result
A long-only portfolio of the winners is, above all, a bet on the crypto market. The equal-weighted long-short version, with similar betas in both legs, isolates the cross-sectional part, which is where the planted signal is.
The momentum signal is computed with Sunday's close. Why does the lesson execute at Monday's close and not at Sunday's?
- ABecause exchanges close on Sunday
- BBecause the order needs Sunday's closing data to be computed; executing at that same price assumes trading at an instant that has already passed
- CBecause there is more volume on Monday
- DBy convention
Show answer
Answer B. Because the order needs Sunday's closing data to be computed; executing at that same price assumes trading at an instant that has already passed
It is the same principle as throughout the level: a closing data point can be acted on after that close. In crypto, which has no auction or official close, the “close” is an agreed time (00:00 UTC here) and some time always passes between the calculation and the order.
A perpetual pays funding of 0.01% every 8 hours. What is its approximate annual equivalent?
- A0.01%
- B3.65%
- CAbout 11%
- DAbout 33%
Show answer
Answer C. About 11%
Three payments a day for 365 days: 0.0001 × 3 × 365 ≈ 0.1095, about 11% a year without compounding. It is the order of magnitude of the average funding of the lesson's three coins (from 11.4% to 14.0%).
What distinguishes a cash-and-carry with a quarterly future from one with a perpetual?
- AWith the quarterly future the carry is locked in at entry (the basis converges to zero at expiry); with the perpetual the funding of each period is collected, which changes and can turn negative
- BNothing
- CThe quarterly has no counterparty risk
- DThe perpetual does not need margin
Show answer
Answer A. With the quarterly future the carry is locked in at entry (the basis converges to zero at expiry); with the perpetual the funding of each period is collected, which changes and can turn negative
Buying spot and selling the future locks in the price difference, which disappears at expiry. The perpetual does not expire: funding is recalculated every 8 hours and in sell-offs the short can end up paying. In both cases there is margin, possible liquidations and exchange risk.
In the data,
KJTloses 99% in five days and stops trading. What error would a backtest that only uses the coins that exist today make?- ANone
- BA time zone error
- CA cost error
- DSurvivorship bias: it would remove from history the coins that collapsed, and a strategy long in them would look better than it was
Show answer
Answer D. Survivorship bias: it would remove from history the coins that collapsed, and a strategy long in them would look better than it was
It is the bias of lesson 1.4.4 in its most extreme form. In crypto, collapses of coins and exchanges are not rare, and a universe built from the current list erases them.
Key points
- Crypto coins share a very strong common factor: an average correlation of 0.58 and a first component that explains 70% of the variance in the data.
- The planted weekly cross-sectional momentum is recovered with a rank IC of +0.069 (t ≈ 4.8); the long-short net of 10 bp per side has a Sharpe of 1.17, but a maximum drawdown of 41% and a worst day of −16.5% because of a single short position.
- A perpetual's annualized funding averages between 11% and 14% in the data, rises after rallies and can be negative for weeks.
- The cash-and-carry (long spot, short derivative) collects the funding or the basis with no net price exposure, but requires margin on the short leg and is exposed to the exchange.
- The quarterly future's basis tracks funding (≈ 0.8 × funding + 2%) and locks in the carry until expiry.
- Coin collapses, liquidations and exchange failures are risks that a backtest on clean prices does not see.