Quantitative Research · Bybit Perpetuals

XSMOM — Cross-Sectional Momentum

A vol-normalised momentum strategy that ranks the 50 most-traded Bybit perpetuals by 30-day risk-adjusted return, goes long the top-5 winners and short the bottom-5 losers. Extended to 4.75 years of history and stress-tested with Monte Carlo simulation.

+$6,371
Total P&L · 104 biweekly periods
2.67
Profit factor
0.256
Weekly Sharpe (≈1.30 ann.)
-$805
Max drawdown ($)
4.75y
Backtest span (2021→2026)
99.8%
P(profit) — 20k MC paths

01Overview

XSMOM applies a classic cross-sectional momentum framework to crypto perpetuals, with two deliberate asymmetries: a trend-confirmation filter on the long side, and no filter on the short side.

Core idea

In crypto, relative momentum persists over multi-week horizons: coins that have outperformed on a risk-adjusted basis tend to keep outperforming, and the weakest tend to keep underperforming. Ranking by vol-normalised return (return ÷ volatility) rather than raw return avoids buying high-volatility noise — a coin that rose 30% on 60% daily vol is a weaker signal than one that rose 20% on 15% vol.

Why the long filter exists

The vol-normalised metric can rank a coin highly because its volatility collapsed, not because it's trending up. The long side therefore requires the coin to be above its moving average (MA-100 after optimisation) and have a positive raw 30-day return. The short side needs no filter: in a structurally risk-off altcoin regime, the weakest losers keep losing.

What changed after the robustness pass: The backtest was extended from 21 → 104 biweekly periods (Nov 2021 → Aug 2026, ~4.75 years) by paginating deeper Bybit history, and Monte Carlo + walk-forward tests were added. The result that was once dominated by a single August 2026 week (91% of P&L) is now spread across many periods — the single best week is 22.7% of total. Details below.

02How it works

The full pipeline, from raw Bybit data to a ranked long/short book.

① Fetch top-50 USDT perps by 24h volume
② Pull daily OHLCV candles (deep history, ~4y where available)
③ Rank by 30-day vol-normalised return
④ Apply long trend filter / short VNR floor
⑤ Size & simulate the biweekly book

Signal — vol-normalised return (VNR)

For each symbol, over the trailing 30 days ending at the rebalance date:

raw_return   = (close[t] - close[t-30]) / close[t-30]
atr_pct      = ATR(14) / close[t]            # daily vol, as % of price
VNR          = raw_return / (atr_pct × √30)  # risk-adjusted momentum

VNR measures how far price moved per unit of volatility over the window — the standard "vol-normalised" momentum used in academic cross-sectional momentum studies.

Selection rules

  • Longs (top 5 by VNR) — must pass trend filter: close > MA(100) and raw_return > 0. Only coins with genuine upward momentum qualify.
  • Shorts (bottom 5 by VNR) — require VNR ≤ -0.5 (optimised floor). Important finding: with this floor, shorts rarely trigger in this sample — the optimised book is effectively long-only. When shorts are forced (no floor), they lose money over the full history (see Monte Carlo section).
  • Sizing — long weights are linear in VNR (strongest signal → biggest position); shorts are equally weighted.
  • Exit — positions close at the next rebalance, or earlier at a 15% stop-loss.

Optimised parameters (after sweep)

Universe: top 50 perps VNR window: 30 days Trend MA: 100 (was 50) Rebalance: biweekly (was weekly) Stop-loss: 15% (was none) Short VNR floor: -0.5 (was none) Longs: 5 · signal-proportional Shorts: 5 · equal weight Filter: MA(100) + raw return>0 Notional per leg: $100 (unit)

03Results — extended backtest (4.75 years)

Backtest of the optimised configuration over 104 biweekly periods (15 Nov 2021 → 17 Aug 2026). Data: real Bybit daily OHLCV for the top-50 volume perps; deep history is paginated (~4 years where the contract allows), and the universe expands as newer perps accumulate enough history (33 → 49 symbols).

XSMOM equity curve over 4.75 years
Cumulative P&L across 104 biweekly rebalances (2021→2026). The strategy loses in the 2022 bear market and compounds through 2023–2026.
Long vs short decomposition
Side decomposition — with the optimised short floor the short book is effectively dormant (1 trade, -$2).
Drawdown profile
Drawdown profile from running P&L peak. Max DD ≈ $805 (2022 bear + early sample).

Headline metrics (104 periods)

Total P&L+$6,371.26
Long P&L+$6,373.31
Short P&L-$2.05 (1 trade — floor dormant)
Periods (biweekly)104
Avg P&L / period+$61.26
Win rate (trades)34.2% (139W / 267L)
Avg win+$73.34
Avg loss-$14.32
Profit factor2.67
Max drawdown-$804.63
Sharpe (weekly, 0% Rf)0.256
Sharpe (annualised ×√26)≈1.30
Total trades406 (all but 1 long)
Positive periods48 / 104 (46%)

Per-year P&L

YearPeriodsP&LPos. periods
20213-$81.70/3
202214-$355.95/14
202323+$1,038.712/23
202424+$1,846.011/24
202523+$2,013.712/23
202617+$1,893.28/17

The strategy is a momentum book that needs a bid: it loses in the 2022 bear market and the 2021 sample, and compounds in 2023–2026. Expect underperformance in sustained downtrends — this is regime-dependent, not regime-neutral.

Period-by-period equity path (104 periods)

Rebalance dateUniverseLongsShortsPeriod P&LCumulative

04Monte Carlo & robustness

The headline P&L is only meaningful if it survives scrutiny. Two complementary tests: a jackknife (how dependent is the total on any single period?) and a 20,000-path moving-block bootstrap (what does the distribution of outcomes look like, preserving regime clustering?).

Jackknife — single-period dependence

We remove each period in turn and recompute total P&L. The most influential periods:

PeriodP&L% of totalTotal without
2026-08-17+$1,44522.7%+$4,926
2025-09-29+$1,28820.2%+$5,083
2024-10-28+$78912.4%+$5,582
2023-11-27+$4987.8%+$5,873
2023-10-16+$4086.4%+$5,963

The original 21-period backtest had a single week worth 91% of P&L. After extending to 104 periods, the best week is 22.7% — removing it leaves +$4,926, still strongly positive. No single period breaks the result.

Block bootstrap — outcome distribution

20,000 simulated paths, each a resample of the period-return series in blocks of 3 (preserves short-run autocorrelation / regime clustering):

PercentileTotal P&LWeekly Sharpe
P5+$2,0170.128
P25+$3,8860.203
P50 (median)+$5,3810.250
P75+$7,0270.290
P95+$9,5590.355

Median path +$5,381 (close to the observed +$6,371); the entire P5→P95 range is positive. P(total > 0) = 99.8% across all 20,000 paths.

Drawdown risk under simulation

P(MaxDD ≥ $100)
100%
P(MaxDD ≥ $300)
98%
P(MaxDD ≥ $500)
61%
P(MaxDD ≥ $1,000)
4%

Median simulated max drawdown ≈ $546; P90 ≈ $852. Realistic expectation: the book will draw down $300–$850 at some point. Drawdowns are the cost of a 46%-positive momentum book with fat right-tail winners.

Robustness matrix — does the config matter?

ConfigP&LSharpePFMaxDDShorts
Optimised (OPT)+$6,3540.2562.66-$8051
Long-only+$6,4220.2522.61-$8030
As-designed L/S (no floor)+$5,6710.2461.55-$486750
Original baseline (weekly/noSL)+$5,8750.2311.93-$5750

Four very different parameterisations all end positive over 4.75 years — the edge is not an artifact of one lucky config.

Walk-forward split-half (OPT config)

HalfPeriodsP&LSharpePFMaxDD
First (2021–2023)52+$9680.1321.54-$805
Second (2024–2026)52+$5,3860.3423.67-$284

The edge strengthened sharply in the second half (Sharpe 0.13 → 0.34). The first half includes the 2022 bear (where the book loses) and the early deep-history sample. This concentration is the honest caveat: the strategy's best years are the most recent ones, so forward-looking expectations should be anchored to the second-half economics, not the full-sample average.

Honest limitations of the Monte Carlo: the bootstrap resamples the observed period returns, so it inherits whatever is in the sample — it measures how robust the distribution of outcomes is to path-order, not whether the underlying edge will persist. It does not correct for: in-sample parameter optimisation, the universe being defined by today's top-50 (delisted coins are excluded — a mild survivorship bias), or fees/slippage. Treat "P(profit) 99.8%" as "given this sample's return distribution, near-certain positive" — not as "this strategy will print money going forward."

05Parameter optimisation sweep

50+ configurations tested one dimension at a time against the baseline (30d VNR · MA=50 · weekly · 5L/5S · no stop · no short floor · ma+rawpos). Rankings by Sharpe. In-sample optimisation — results are optimistic; treat as directional, not as expected live performance. The extended-history numbers on this page come from the applied optimised config re-run over the full 104-period sample (the sweep itself ran on the first 24 weeks available).

Rebalance frequency biggest lever

ScheduleP&LSharpePF
weekly (baseline)+$4090.1821.23
biweekly+$3,8150.3282.53

Extending the hold from 7→14 days lets winners compound and cuts rebalance whipsaw. This leg ran without the short floor, so shorts were still active here.

Stop-loss best Sharpe

StopP&LSharpePFMaxDD
none (baseline)+$4100.1821.23-$180
15%+$8300.3711.60-$126
25%+$5970.2621.37-$170

15% is the single best Sharpe improvement: it caps catastrophic single-coin blow-ups while letting winners run.

Trend-filter MA period

MAP&LSharpePF
none (raw-pos only)+$3640.1561.20
20+$3550.1501.19
50 (baseline)+$4110.1831.23
100+$5250.2211.32
200+$2890.1461.21

MA=100 is the sweet spot — a slower regime filter that rejects more false-positive longs in chop.

Short VNR floor

FloorP&LSharpePFTrades
none (baseline)+$4100.1821.23239
-0.3+$4980.1981.38167
-0.5+$5550.1991.49119
-0.75+$5550.1991.49119
-1.0+$5550.1991.49119

The floor improves PF by culling weak/mean-reverting shorts — but note the robustness finding: over the full 4.75y sample, forcing shorts on (no floor) is a net drag (-$205 over 750 trades). The floor's value is largely "don't short at all."

Signal window (VNR days)

WindowP&LSharpeMaxDD
14+$4720.144-$510
21+$480.019-$180
30 (baseline)+$4120.183-$178
45+$1170.040-$329
60-$137-0.058-$272

30 days has the best Sharpe / drawdown balance. 14d has higher raw P&L but 2.9× the drawdown.

Portfolio size & concentration

ConfigP&LSharpe
3L/3S+$2870.194
5L/5S (baseline)+$4100.182
7L/7S+$6670.193
cap 50% / 40% / 30%+$393 / +$393 / +$387≈0.177 / 0.175

Concentration caps hurt: signal-proportional sizing is an alpha source, not a risk bug.

Combined configurations

ConfigP&LSharpeWRPF
Baseline (30d/MA50/weekly)+$4100.18245.6%1.23
Best-of-each-dimension (30d/MA100/biweekly/15%SL/floor-0.5)+$2,4880.28541.2%2.43
Applied optimised config (re-run over 104 periods)+$6,3710.25634.2%2.67

Full 50-row sweep data available at sweep_data.json in this repo.

06Implementation

Pure-Python, stdlib-only backtest (no pandas/numpy). Fetches real data from the Bybit V5 public REST API, paginating for deep history, and caches it locally.

Repository layout

MOMSXperp/
├── config.py         # strategy parameters (optimised)
├── run.py            # backtest entry point
├── analysis.py       # ranking, selection, sizing, P&L engine
├── fetch.py          # Bybit V5 OHLCV fetch + paginated deep history
├── refetch_deep.py   # re-fetch deep history for all symbols
├── report.py         # extended diagnostics
├── charts.py         # matplotlib equity/side/drawdown charts
├── sweep.py          # 8-dimension parameter sweep
├── montecarlo.py     # jackknife + block-bootstrap robustness
├── robustness.py     # multi-config matrix + walk-forward split
├── xsmom_results.json       # optimised backtest (104 periods)
├── xsmom_mc_results.json    # Monte Carlo output
├── xsmom_robustness.json    # robustness matrix
├── xsmom_sweep_results.json # full sweep output
└── site/             # this Cloudflare Pages site
    └── assets/       # charts

Run it yourself

# backtest (cached data; --force to re-fetch deep history)
python3 -m xsmom.run

# deep re-fetch of all symbols (one-time)
python3 -m xsmom.refetch_deep

# Monte Carlo robustness (20k paths)
python3 -m xsmom.montecarlo

# multi-config robustness matrix + walk-forward
python3 -m xsmom.robustness

# extended diagnostics / charts
python3 -m xsmom.report
python3 -m xsmom.charts

# full parameter sweep (2-3 min)
python3 -m xsmom.sweep

Data is cached under xsmom/data_cache/ so repeat runs are instant. No API key required for public market data.

Key engineering choices

  • Deep history via pagination — Bybit returns max 1,000 candles per request, so fetch.py pages backwards with the end timestamp to reach ~4 years where the contract allows (majors back to 2020–2021; newly-listed perps have less). This extended the backtest from 21 → 104 periods.
  • Per-symbol history depth — each rebalance uses only symbols with enough data (universe grows over the backtest). No look-ahead: signal uses only candles up to the rebalance date.
  • Stop-loss semantics — a position's realised return is floored at -15% (a hard stop before the next rebalance), which tames the worst blow-up weeks.
  • Monte Carlo — moving-block bootstrap (block=3) preserves regime clustering/autocorrelation; 20,000 paths. Jackknife quantifies single-period dependence.
  • Robustness matrix — 4 configs (optimised / long-only / as-designed L/S / baseline) run over the same 104-period window plus a walk-forward split-half, so conclusions don't depend on one parameterisation.
  • Fees not modelled — perp taker fees (~0.055%/side) would shave a small % off returns; with 406 trades the drag is modest but non-zero.

07Caveats & honest limitations

Read this before acting on anything on this page. This is what a critical reviewer should weigh.

1 · The edge is regime-dependent, not regime-neutral. The book loses in the 2022 bear market (-$356) and the short 2021 sample, and compounds in 2023–2026. Walk-forward: first half (2021–23) Sharpe 0.13, second half (2024–26) Sharpe 0.34. This is a momentum book that needs a bid — in a sustained crypto bear it will lose money, and the full-sample Sharpe overstates what to expect going forward.
2 · The "short side" is effectively absent. With the -0.5 VNR floor + biweekly schedule, shorts triggered only once in 104 periods (-$2). When shorts are forced on (no floor, as originally designed), they lose money over the full history (-$205 over 750 trades, worst in 2023 at -$306). The robustness matrix shows long-only ≈ optimised, and as-designed L/S is the worst of the four configs. The strategy's real economics are long momentum; the short leg is untested as a positive contributor here.
3 · In-sample optimisation = overfitting risk. Every parameter (MA period, rebalance frequency, stop-loss, short floor) was chosen because it looked best on data that is also the test set. With 50+ configs tried, some will look good by chance. The sweep is a research map, not a forward-tested edge. The extended-history re-run and Monte Carlo make the result less fragile to path-order, but do not remove in-sample selection bias.
4 · Universe survivorship bias. The universe is defined by today's top-50 by volume, then history is fetched for those symbols. Delisted coins and coins that fell out of the top-50 are excluded from the backtest, which flatters long momentum. This is inherent to backtesting current-listed perps and should be assumed to inflate results.
5 · Fees, slippage, and execution. Not modelled. Perp taker fees (~0.055%/side × 2 sides × 406 trades) plus slippage on small caps would reduce net P&L, especially given the low win rate (34%) relies on a few large winners. The 15% stop is assumed to fill at exactly -15%; in a gap move it can fill worse.
What would strengthen this analysis: (a) true out-of-sample / walk-forward parameter selection (optimise on train, test on held-out years), (b) monthly re-fetch to keep the backtest current as history accrues, (c) explicit fee + slippage modelling, (d) a regime overlay (e.g. only run longs when BTC > its 200-day MA — would cut the 2022 losses), (e) a dedicated, properly-designed short-side study rather than a dormant floor.

08Repository & data

Everything is open for review. Clone, re-run, and challenge the numbers.

View on GitHub Download sweep data (JSON) Download Monte Carlo (JSON)

Raw data files

  • xsmom_results.json — full optimised backtest (104 periods, per-trade detail).
  • xsmom_mc_results.json — Monte Carlo: jackknife + 20k-path bootstrap.
  • xsmom_robustness.json — 4-config robustness matrix + walk-forward split.
  • xsmom_sweep_results.json — all 50 sweep configurations with weekly P&L series.
  • sweep_data.json — condensed sweep tables used to build this page.
  • config.py — the exact optimised parameter set.