Chapter 32: Cross-Asset Momentum — Global Tactical Asset Allocation
Chapter 32: Cross-Asset Momentum — Global Tactical Asset Allocation
Overview
Cross-Asset Momentum applies momentum strategies across various asset classes: cryptocurrencies, traditional equities, bonds, commodities, and currencies. This approach diversifies alpha sources and reduces correlation with traditional single-asset-class momentum strategies.
Table of Contents
- What is Momentum
- Dual Momentum Strategy
- Cryptocurrency Implementation
- Code Examples
- Backtesting
- Resources
What is Momentum
Momentum is the tendency for assets that have performed well in the past to continue rising, and vice versa. It is one of the most persistent anomalies in financial markets, documented in academic literature since 1993.
Intuition Behind Momentum
Why does momentum work? There are several explanations:
-
Behavioral Factors:
- Investors react slowly to new information
- Herding effect amplifies trends
- Confirmation bias — people seek validation of their positions
-
Structural Factors:
- Institutional investors buy/sell gradually
- Funds follow benchmarks with a lag
- Rebalancing creates predictable flows
-
Risk Premium:
- Momentum assets may carry hidden risk of sharp reversals
- Investors receive a premium for this risk
Time-Series Momentum (TSM)
Time-series momentum compares an asset with itself in the past:
TSM Signal = Asset return over period > 0- Long: if return over period is positive
- Cash/Short: if return over period is negative
Advantages of TSM:
- Simple calculation
- Protection from falling markets (exit to cash)
- Works independently for each asset
def time_series_momentum(prices, lookback=252): """ Calculate time-series momentum
Args: prices: Asset prices lookback: Period in days (252 = 1 year)
Returns: signal: 1 (long) or 0 (cash) """ returns = prices.pct_change(lookback) signal = (returns > 0).astype(int) return signalCross-Sectional Momentum (CSM)
Cross-sectional momentum compares assets with each other:
CSM Signal = Rank of asset among all assets by return- Long: assets in top quartile/decile
- Short: assets in bottom quartile/decile
Advantages of CSM:
- Always have positions (market-neutral possible)
- Uses relative strength
- Diversification across assets
def cross_sectional_momentum(returns_df, top_n=3, bottom_n=3): """ Calculate cross-sectional momentum
Args: returns_df: DataFrame with asset returns top_n: Number of best assets to buy bottom_n: Number of worst assets to short
Returns: signals: DataFrame with signals (-1, 0, 1) """ # Rank assets ranks = returns_df.rank(axis=1, ascending=False)
# Long for top_n, short for bottom_n n_assets = returns_df.shape[1] signals = pd.DataFrame(0, index=returns_df.index, columns=returns_df.columns) signals[ranks <= top_n] = 1 signals[ranks > n_assets - bottom_n] = -1
return signalsDual Momentum Strategy
Combining Approaches
Dual Momentum, developed by Gary Antonacci, combines both types of momentum:
Position = TSM × CSM- Step 1 (Absolute Momentum): Check if asset is better than risk-free rate
- Step 2 (Relative Momentum): Among those passing the filter, select the best
This gives the best of both worlds:
- Protection from falling markets (from TSM)
- Selection of best assets (from CSM)
def dual_momentum(prices_df, risk_free_rate, lookback=252, top_n=3): """ Dual Momentum strategy
Args: prices_df: DataFrame with asset prices risk_free_rate: Risk-free rate (annual) lookback: Period for momentum calculation top_n: Number of assets to buy
Returns: weights: Portfolio weights """ returns = prices_df.pct_change(lookback)
# Step 1: Absolute momentum filter excess_returns = returns - risk_free_rate passed_filter = excess_returns > 0
# Step 2: Relative momentum ranking filtered_returns = returns.where(passed_filter, -np.inf) ranks = filtered_returns.rank(axis=1, ascending=False)
# Select top_n assets weights = pd.DataFrame(0.0, index=prices_df.index, columns=prices_df.columns) weights[ranks <= top_n] = 1.0 / top_n
# If all assets filtered out - go to cash all_filtered = ~passed_filter.any(axis=1) weights.loc[all_filtered] = 0
return weightsDrawdown Protection
One of the main advantages of Dual Momentum is protection from large drawdowns:
| Event | S&P 500 | Dual Momentum |
|---|---|---|
| Dot-com crash (2000-02) | -49% | -10% |
| Financial crisis (2008-09) | -57% | -15% |
| COVID crash (2020) | -34% | -12% |
| Crypto winter (2022) | N/A | -25% |
This is achieved by:
- Exiting to cash with negative absolute momentum
- Switching to defensive assets
- Avoiding the worst performers
Cryptocurrency Implementation
Asset Selection
For the cryptocurrency market, we use the following asset universe:
Cryptocurrencies (Bybit):├── BTCUSDT - Bitcoin├── ETHUSDT - Ethereum├── SOLUSDT - Solana├── BNBUSDT - Binance Coin├── XRPUSDT - Ripple├── ADAUSDT - Cardano├── AVAXUSDT - Avalanche├── DOTUSDT - Polkadot├── MATICUSDT- Polygon├── LINKUSDT - Chainlink└── ATOMUSDT - Cosmos
Stablecoins (risk-free asset):├── USDT - Tether└── USDC - USD CoinSignal Calculation
For cryptocurrencies, shorter periods are used due to high volatility:
# Periods for momentum calculation (in days)LOOKBACK_PERIODS = { 'short': 7, # 1 week 'medium': 30, # 1 month 'long': 90, # 3 months}
# Weights for combined signalPERIOD_WEIGHTS = { 'short': 0.3, 'medium': 0.4, 'long': 0.3,}
def calculate_crypto_momentum(prices, skip_days=1): """ Calculate momentum for cryptocurrencies
Args: prices: DataFrame with prices skip_days: Skip last N days (to avoid mean reversion)
Returns: momentum: Combined momentum signal """ momentum = pd.DataFrame(0.0, index=prices.index, columns=prices.columns)
for name, days in LOOKBACK_PERIODS.items(): # Skip last days shifted = prices.shift(skip_days) returns = shifted.pct_change(days)
momentum += returns * PERIOD_WEIGHTS[name]
return momentumPosition Management
For the cryptocurrency market, risk management is especially important:
def volatility_adjusted_weights(returns, target_vol=0.30): """ Calculate volatility-adjusted weights
Crypto has high volatility, so target_vol = 30% """ # Realized volatility over 30 days realized_vol = returns.rolling(30).std() * np.sqrt(365)
# Raw weights inversely proportional to volatility raw_weights = target_vol / realized_vol
# Cap maximum position size capped_weights = raw_weights.clip(upper=2.0) # Max 2x leverage
return capped_weights
def risk_parity_crypto(returns, max_correlation=0.7): """ Risk parity with correlation adjustment
Cryptocurrencies are often highly correlated, which is important to consider """ # Covariance matrix cov_matrix = returns.rolling(90).cov()
# Volatility of each asset vol = returns.rolling(90).std()
# Correlation matrix corr_matrix = returns.rolling(90).corr()
# Penalize highly correlated assets correlation_penalty = (corr_matrix.mean() / max_correlation).clip(lower=1.0)
# Inverse volatility with correlation penalty inv_vol_weights = 1 / (vol * correlation_penalty) weights = inv_vol_weights / inv_vol_weights.sum()
return weightsCode Examples
Rust Implementation
The rust_momentum_crypto directory contains a modular Rust implementation:
rust_momentum_crypto/├── Cargo.toml├── README.md├── src/│ ├── lib.rs # Main library module│ ├── main.rs # CLI interface│ ├── data/│ │ ├── mod.rs # Data module│ │ ├── bybit.rs # Bybit API client│ │ └── types.rs # Data types (OHLCV, etc)│ ├── momentum/│ │ ├── mod.rs # Momentum module│ │ ├── timeseries.rs # Time-series momentum│ │ ├── crosssection.rs # Cross-sectional momentum│ │ └── dual.rs # Dual momentum│ ├── strategy/│ │ ├── mod.rs # Strategy module│ │ ├── signals.rs # Signal generation│ │ └── weights.rs # Weight calculation│ ├── backtest/│ │ ├── mod.rs # Backtest module│ │ ├── engine.rs # Backtest engine│ │ └── metrics.rs # Performance metrics│ └── utils/│ ├── mod.rs # Utilities│ └── config.rs # Configuration└── examples/ ├── fetch_prices.rs # Fetch data from Bybit ├── calc_momentum.rs # Calculate momentum ├── run_strategy.rs # Run strategy └── backtest.rs # Full backtestSee rust_momentum_crypto/README.md for details.
Quick Start with Rust
# Clone and navigate to the projectcd 32_cross_asset_momentum/rust_momentum_crypto
# Fetch price data from Bybitcargo run --example fetch_prices
# Calculate momentum for all assetscargo run --example calc_momentum
# Run the full strategycargo run --example run_strategy
# Run a complete backtestcargo run --example backtestPython Notebooks
| # | Notebook | Description |
|---|---|---|
| 1 | 01_crypto_universe.ipynb | Select cryptocurrencies for strategy |
| 2 | 02_data_collection.ipynb | Fetch data from Bybit |
| 3 | 03_momentum_signals.ipynb | Calculate momentum signals |
| 4 | 04_time_series_momentum.ipynb | Time-series momentum filter |
| 5 | 05_cross_sectional_momentum.ipynb | Cross-sectional ranking |
| 6 | 06_dual_momentum.ipynb | Combination of TSM + CSM |
| 7 | 07_volatility_targeting.ipynb | Volatility targeting |
| 8 | 08_risk_parity_weights.ipynb | Risk parity allocation |
| 9 | 09_rebalancing.ipynb | Rebalancing logic |
| 10 | 10_backtesting.ipynb | Full backtest |
| 11 | 11_regime_analysis.ipynb | Performance by market regimes |
| 12 | 12_ml_enhancement.ipynb | ML for rebalancing timing |
Backtesting
Key Metrics
- Returns: CAGR, Total Return
- Risk: Volatility, Maximum Drawdown, VaR
- Risk-Adjusted: Sharpe, Sortino, Calmar
- Momentum-Specific: Hit Rate, Average Win/Loss, Turnover
- Comparison: vs Buy&Hold BTC, vs Equal Weight
Typical Results for Cryptocurrencies
| Metric | Buy&Hold BTC | Equal Weight | Dual Momentum |
|---|---|---|---|
| CAGR | 45% | 35% | 55% |
| Volatility | 75% | 60% | 40% |
| Max Drawdown | -85% | -75% | -35% |
| Sharpe Ratio | 0.6 | 0.58 | 1.35 |
| Calmar Ratio | 0.53 | 0.47 | 1.57 |
Note: Historical results do not guarantee future performance
Rebalancing Rules
Rebalancing Schedule:├── Weekly (Sunday 00:00 UTC)├── Optional: daily during high volatility└── Account for exchange fees
Rebalancing Bands:├── Trade only if weight deviation > 10%├── Significantly reduces turnover└── Maintains approximate target allocation
Signal Decay:├── Fresh signal = full weight├── Aging signal = reduced weight└── Prevents whipsaws at signal boundaryResources
Books
- Dual Momentum Investing (Gary Antonacci)
- Quantitative Momentum (Wesley Gray)
- Expected Returns (Antti Ilmanen)
Academic Papers
- Time Series Momentum (Moskowitz, Ooi, Pedersen)
- Value and Momentum Everywhere (Asness, Moskowitz, Pedersen)
- Momentum in Cryptocurrency Markets (Liu, Tsyvinski, Wu)
Related Chapters
- Chapter 22: Deep Reinforcement Learning — RL for trading
- Chapter 28: Regime Detection with HMM — Market regime detection
- Chapter 36: Crypto DEX Arbitrage — Cryptocurrency exchange arbitrage
Dependencies
Python
pandas>=1.5.0numpy>=1.23.0matplotlib>=3.6.0seaborn>=0.12.0scipy>=1.10.0requests>=2.28.0empyrical>=0.5.5 # For performance metricspyfolio>=0.9.2 # For tearsheetsRust
reqwest = "0.12" # HTTP clienttokio = "1.0" # Async runtimeserde = "1.0" # Serializationchrono = "0.4" # Time handlingndarray = "0.16" # ArraysDifficulty Level
Intermediate
Required knowledge:
- Momentum factors
- Asset allocation
- Risk parity
- Portfolio construction
- Cryptocurrency markets