---
title: "2.3.7 Crypto: momentum, funding and basis"
description: "Weekly cross-sectional momentum, the common factor that dominates crypto and the funding and basis carry, with costs, crashes and counterparty risk."
url: "https://pythonparatrading.com/en/python-trading-course/level-2/crypto-momentum-funding-basis-strategies/"
lang: "en"
image: "https://pythonparatrading.com/og/en/python-trading-course/level-2/crypto-momentum-funding-basis-strategies.png"
---

# 2.3.7 · 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.

- Course: [Level 2: Systematic strategies and rigorous backtesting](https://pythonparatrading.com/en/python-trading-course/level-2/)
- Module: 2.3 · Strategy families
- Duration: 65 min
- Prerequisites: [1.2.6 · Crypto: spot, perpetuals, funding and custody](https://pythonparatrading.com/en/python-trading-course/level-1/crypto-spot-perpetuals-and-funding/), [1.3.5 · Crypto and on-chain data](https://pythonparatrading.com/en/python-trading-course/level-1/crypto-and-on-chain-data/), [1.4.4 · Universes without survivorship bias: delistings, mergers and symbol changes](https://pythonparatrading.com/en/python-trading-course/level-1/survivorship-bias-and-point-in-time-data/)

## By the end of this lesson

- Measure how much of each coin's movement the common factor of the crypto market explains.
- Test a weekly cross-sectional momentum with rank IC and a long-short portfolio executed in the session after the signal, with commissions.
- Compute the annualized funding of a perpetual, its relationship with recent returns and what a cash-and-carry earns.
- Relate the basis of a quarterly future to funding and explain why it locks in the carry until expiry.
- List the risks the backtest does not see: crashes, margin liquidations and exchange failure.

## 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
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
```

<aside class="callout" data-kind="aviso">

The data is **synthetic**: fifteen fictitious coins (`KAL`, `KBR`…) from 2018 to 2025 with a strong common factor,
two documented collapses and a **planted weekly cross-sectional momentum** (each Monday, an extra daily return of
0.10% per unit of score of the previous 28 days' return). The funding and basis of three coins follow known formulas.
None of this is evidence about real crypto; the cited literature is, with its own limitations.

</aside>

```python
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())
```
```output
             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 $R^2$, the fraction of its daily variance explained by the common factor:

```python
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).T
```
```output
mean 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.84
```

The average pairwise correlation is 0.58 and the first principal component explains 70% of the variance of the
fifteen. The $R^2$ 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.

*Fig. — Share of each coin's daily variance explained by the equal-weighted crypto market index (R²).* Fifteen horizontal bars sorted from high to low: KAL 0.84, KNX 0.77, KGN 0.75, KDL 0.75, KLU 0.71, KCX 0.67, KRM 0.63, KFL 0.62, KES 0.59, KMV 0.59, KIO 0.54, KOB 0.54, KHX 0.42, KBR 0.40, KJT 0.40. KAL, the large coin, highlighted, depends most on the common factor.

## 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
flowchart LR
  D[Sunday close] --> S[28-day signal]
  S --> L[Monday order]
  L --> H[7 days]
```

```python
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)
```
```output
                 ic_medio      t  semanas_pos_pct
at Sunday close     0.068  4.820           59.223
at Monday close     0.069  4.842           60.922
```

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

```python
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)
```
```output
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.00
```

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

```python
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} %)")
```
```output
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.

*Fig. — Weekly cross-sectional momentum (five against five, net of 10 bp per side) against the equal-weighted index.* Two daily equity curves from 2018 to 2025 on a log scale, base 100. The equal-weighted index falls to 16 in 2018-2020, rises strongly in 2020 and 2021, falls again in 2022 and ends at 161. The long-short, highlighted, rises strongly in 2018 and 2019, flattens in 2020 and 2021, falls in the first half of 2022 (on 3 May, KOB, held short, rises 78% in a day) and rises again from 2023 to end at 2,159.

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

```python
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)
```
```output
        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.7
```

Average 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
($0.0001 + 0.0006 \tanh(r_7 / 0.15)$ per period, plus noise). By year, `KAL`'s ranges from 1.4% in 2018 to 20.3% in
2023.

*Fig. — Annualised daily funding against the previous 7-day return in KAL, KBR and KCX.* Twenty points, means of groups by 7-day return, from -28% to +26%, against annualised funding, from -48% to 72%. The points follow a highlighted S-curve, the generator's hyperbolic-tangent formula: funding rises with the recent return and saturates at the extremes.

## 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
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
```

```python
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} %")
```
```output
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.

<aside class="callout" data-kind="riesgo">

The cash-and-carry has no price risk on paper, but it has three real risks. **Liquidation**: the short perpetual leg
loses when the price rises, and if margin is not topped up in time the exchange closes it by force and the hedge
disappears at exactly the worst moment. **Negative funding**: in sell-offs the short pays. And **counterparty risk**:
the spot and the margin sit on the exchange; if it fails or freezes withdrawals, the whole position is trapped. Level 3
covers how to manage these risks.

</aside>

## 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**: $b = \ln(F/S) \times 365 / \text{days}$. 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%.

```python
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))
```
```output
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.4
```

The 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.** `KJT` and `KLU` lose 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

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

```python
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)
```
```output
    ic_medio      t
7      0.034  2.414
14     0.057  3.893
28     0.069  4.847
56     0.069  4.597
```

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

2. 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 `KAL`
   versus holding it all the time.

**Solution.**

```python
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)")
```
```output
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.

3. The backtest of the `KAL` cash-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.

## Self-assessment

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

   - A. That it is useless
   - B. That buying the winners is enough
   - C. That it should be leveraged more
   - 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

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

2. The momentum signal is computed with Sunday's close. Why does the lesson execute at Monday's close and not at Sunday's?

   - A. Because exchanges close on Sunday
   - 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
   - C. Because there is more volume on Monday
   - D. By convention

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

3. A perpetual pays funding of 0.01% every 8 hours. What is its approximate annual equivalent?

   - A. 0.01%
   - B. 3.65%
   - C. About 11%
   - D. About 33%

   *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%).

4. What distinguishes a cash-and-carry with a quarterly future from one with a perpetual?

   - 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
   - B. Nothing
   - C. The quarterly has no counterparty risk
   - D. The perpetual does not need margin

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

5. In the data, `KJT` loses 99% in five days and stops trading. What error would a backtest that only uses the coins that exist today make?

   - A. None
   - B. A time zone error
   - C. A cost error
   - D. Survivorship bias: it would remove from history the coins that collapsed, and a strategy long in them would look better than it was

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

## Resources

### Research

- [Volatility Targeting in Python](https://pythonparatrading.com/en/volatility-targeting-2/) · Research-0004 · 7 min

### Documentation

- [Liu and Tsyvinski (2021): Risks and Returns of Cryptocurrency, Review of Financial Studies 34(6)](https://doi.org/10.1093/rfs/hhaa113) · doi.org
- [Liu, Tsyvinski and Wu (2022): Common Risk Factors in Cryptocurrency, Journal of Finance 77(2)](https://doi.org/10.1111/jofi.13119) · doi.org
- [pandas: window functions (rolling, ewm) and their methods, such as corr](https://pandas.pydata.org/docs/reference/window.html) · pandas.pydata.org

### Data

- [Level 2 data: fifteen synthetic coins, daily (Parquet)](https://pythonparatrading.com/curso/datos/nivel-2/cripto2_diario.parquet) · Download · cripto2_diario.parquet
- [Level 2 data: perpetual funding every 8 hours (Parquet)](https://pythonparatrading.com/curso/datos/nivel-2/cripto2_funding.parquet) · Download · cripto2_funding.parquet
- [Level 2 data: basis of the quarterly futures (Parquet)](https://pythonparatrading.com/curso/datos/nivel-2/cripto2_basis.parquet) · Download · cripto2_basis.parquet

Previous lesson: [2.3.6 · Introduction to market making: inventory, spread and adverse selection (Avellaneda-Stoikov)](https://pythonparatrading.com/en/python-trading-course/level-2/market-making-avellaneda-stoikov/)

Next lesson: [2.4.1 · Vectorized backtest from scratch: matrices, lag, costs and limits](https://pythonparatrading.com/en/python-trading-course/level-2/vectorized-backtest-from-scratch/)
