# Cross-Sectional Momentum Crypto Strategy Mid-frequency cross-sectional momentum strategy backtested across 7 cryptocurrencies on 1H OHLCV data. Built from scratch — data pipeline, feature engineering, signal generation, backtest engine, and performance reporting. --- ## Strategy Overview Every hour, all 7 coins are ranked by relative momentum (each coin's return vs the cross-sectional average). The strategy goes **long the top 2** ranked coins and **short the bottom 2** — but only when three independent confirmation filters simultaneously agree. Position sizing adapts dynamically to a daily market regime score that detects whether the broader crypto market is in a bull or bear phase. The core insight: crypto prices are driven by collective sentiment and capital momentum rather than fundamentals. This creates persistent relative strength effects across coins that can be systematically captured. --- ## Universe | Coin | Exchange | |---|---| | AVAXUSDT | Binance | | BNBUSDT | Binance | | BTCUSDT | Binance | | DOGEUSDT | Binance | | ETHUSDT | Binance | | LINKUSDT | Binance | | SOLUSDT | Binance | **Common start date:** 2020-09-22 (determined by AVAXUSDT listing date) **Timeframes used:** 1d, 1h, 15m, 5m --- ## Performance Results | Period | Dates | Return | Final ($10k) | Sharpe | Max DD | Win Rate | Trades | |---|---|---|---|---|---|---|---| | Full Dataset | Sep'20 – Dec'24 | +3,869% | $396,851 | 1.55 | -62.4% | 49.8% | 8,905 | | Train | Sep'20 – Sep'23 | +726% | $82,553 | 1.29 | -62.4% | 49.7% | 6,153 | | Validation | Sep'23 – May'24 | +98% | $19,768 | 2.10 | -20.4% | 49.6% | 1,383 | | **Test** | **May'24 – Dec'24** | **+143%** | **$24,318** | **2.81** | **-34.2%** | **50.6%** | **1,369** | > Sharpe ratio improves from Train (1.29) → Validation (2.10) → Test (2.81). Strategy is profitable in all three periods including the completely unseen test set. > All returns are gross of transaction costs. --- ## Signal Architecture ### Three-Layer Entry Filter All three layers must agree simultaneously — no signal fires if any layer fails. | Layer | Condition | Purpose | |---|---|---| | L1 — Momentum Rank | Long: rank >= 6 of 7 / Short: rank <= 2 of 7 | Only top/bottom coins by relative momentum qualify | | L2 — ATR Expansion | ATR(14) > ATR baseline(20) | Ensures market has energy to sustain directional moves. Avoids ranging markets | | L3 — Volume Confirmation | volume_ratio > 1.0 | Confirms genuine capital participation behind the move | ### Regime Score Daily score (0 to 1) = proportion of 7 coins above their 20-day moving average. - **Above 0.5** → bull regime → long positions scaled up, short positions scaled down - **Below 0.5** → bear regime → short positions scaled up, long positions scaled down - **Distance from 0.5** → total capital deployed (near 0.5 = uncertainty = reduce exposure) ### Exit Framework | Exit | Status | Logic | |---|---|---| | Signal Expiry | ✅ Implemented | Position closes when 3-layer filter no longer fires | | Momentum Deterioration | ✅ Implemented | Exit when both return_6 and return_12 simultaneously turn negative | | ATR Trailing Stop | 🔲 Designed | Exit if price moves against position by X × ATR | | Time Cap | 🔲 Designed | Force exit after 12 candles maximum | --- ## Feature Engineering | Feature | Timeframe | Description | |---|---|---| | upper_wick_ratio | 1h, 15m, 5m | Selling pressure within candle | | lower_wick_ratio | 1h, 15m, 5m | Buying pressure within candle | | body_ratio | 1h, 15m, 5m | Conviction of the candle move | | close_location_value | 1h, 15m, 5m | Where price closed within candle range | | ATR | 1h | 14-period average true range | | ATR_baseline | 1h | 20-period rolling mean of ATR | | volume_ratio | 1h | Current volume vs 20-period rolling mean | | return_6, return_12, return_24 | 1h | Rolling returns for momentum and exit signals | | relative_momentum | 1h | Each coin's return minus cross-sectional mean | | cross_rank | 1h | Ordinal rank of coin by relative momentum (1–7) | | dispersion | 1h | Standard deviation of returns across all 7 coins | | regime_score | 1d | Proportion of coins above 20-day MA | > All rolling features use `shift(1)` before calculation to prevent lookahead bias. --- ## Data Format Parquet files with MultiIndex — level 0 is `symbol`, level 1 is `open_time`. ``` Columns: open, high, low, close, volume Files: 1d.parquet, 1h.parquet, 15m.parquet, 5m.parquet ``` Expected directory structure: ``` project/ ├── step_1.py ├── 1d.parquet ├── 1h.parquet ├── 15m.parquet └── 5m.parquet ``` --- ## How to Run **1. Install dependencies** ```bash pip install pandas numpy matplotlib pyarrow reportlab ``` **2. Place parquet files in the same directory as the script** **3. Run the strategy** ```bash python step_1.py ``` **4. Output** - Prints performance metrics for Train, Validation, Test, and Full Dataset - Displays equity curves and drawdown charts for all periods --- ## Tech Stack | Library | Usage | |---|---| | pandas | Data loading, feature engineering, signal generation | | numpy | Numerical calculations, weight construction | | matplotlib | Equity curves and drawdown charts | | pyarrow | Parquet file reading | | reportlab | PDF report generation | --- ## Key Design Decisions **Fixed universe** — all 7 coins from common start date. Cross-sectional rankings are inconsistent when universe size changes. Fixed universe is the professional standard. **Absolute sum weight normalization** — prevents long and short weights from cancelling each other during normalization. Ensures total gross exposure never exceeds 1.0. **Returns winsorized at ±10%** — AVAXUSDT produced a 474% single-candle return at listing due to near-zero liquidity. This is a data artifact not a tradeable return and is capped accordingly. **Regime neutral fill** — NaN regime values during warmup filled with 0.5 (neutral). Ensures no position is taken with undefined regime rather than corrupting the equity curve with NaN propagation. --- ## Limitations - Transaction costs not modelled — gross Sharpe reported above - Execution assumes fills at open price — real slippage would reduce returns - ATR trailing stop and time cap exits are designed but not yet implemented - 15m and 5m features computed but not yet integrated into execution timing --- ## License MIT