← Back to Home
Beyond Raw Factors - Enhancing Alpha Predictability in Crypto Markets

Beyond Raw Factors - Enhancing Alpha Predictability in Crypto Markets

Building on the foundation that technical indicators work better as continuous alpha factors than binary trading signals, let’s explore some enhancement strategies for Bitcoin return prediction. We test cross-sectional ranking, volatility regime adjustments, Kalman filtering, and factor combinations on a library of technical indicators.

We already established that technical indicators like RSI and MACD, when used as continuous alpha factors rather than binary trading signals, demonstrate significant predictive power for Bitcoin returns. However, raw indicator values suffer from regime changes, non-stationarity, and noise that can be addressed through quantitative enhancement techniques.

This study systematically evaluates five enhancement strategies commonly used in quantitative finance to determine which methods best improve Bitcoin alpha factor performance.

Methodology

Data and Setup

import pandas as pd
import yfinance as yf
import talib
from scipy.stats import spearmanr

# Load Bitcoin data
df = yf.download('BTC-USD', start='2020-01-01', end='2024-01-01')
price = df['Adj Close']
returns = price.pct_change()

# Create forward return targets
forward_returns = pd.DataFrame(index=price.index)
for h in [7, 14, 30]:
    forward_returns[f'fwd_{h}d'] = price.pct_change(h).shift(-h)

Base Alpha Factors

We construct continuous alpha factors from classic technical indicators:

factors = pd.DataFrame(index=price.index)

# RSI as continuous factor (not binary threshold)
factors['rsi_14'] = talib.RSI(price.values, 14)

# MACD components
macd, macd_signal, _ = talib.MACD(price.values)
factors['macd'] = macd
factors['macd_signal'] = macd_signal

# Bollinger Band position
bb_upper, bb_middle, bb_lower = talib.BBANDS(price.values)
factors['bb_position'] = (price - bb_lower) / (bb_upper - bb_lower)

# Momentum and volatility
factors['momentum_20'] = talib.MOM(price.values, 20) / price
factors['atr_ratio'] = talib.ATR(df['High'], df['Low'], price, 14) / price

Information Coefficient Evaluation

We measure predictive power using Information Coefficient—the Spearman correlation between factor values today and future returns:

def calculate_ic(factor_series, forward_returns_series):
    combined = pd.concat([factor_series, forward_returns_series], axis=1).dropna()
    if len(combined) < 30:
        return np.nan
    ic, _ = spearmanr(combined.iloc[:, 0], combined.iloc[:, 1])
    return ic

# Example: Raw RSI IC
ic_rsi_raw = calculate_ic(factors['rsi_14'], forward_returns['fwd_30d'])
print(f"Raw RSI IC: {ic_rsi_raw:.3f}")

Enhancement Strategies

1. Cross-Sectional Ranking

Concept: Convert raw indicator values to rolling percentile ranks, removing level effects and creating stationary time series.

# Cross-sectional ranking enhancement
window = 252  # 1-year rolling window

enhanced_factors = pd.DataFrame(index=price.index)

for factor_name in factors.columns:
    factor = factors[factor_name]
    
    # Rolling percentile rank (0-1)
    rank_factor = factor.rolling(window).rank(pct=True)
    enhanced_factors[f'{factor_name}_rank'] = rank_factor
    
    # Rolling z-score normalization
    mean_rolling = factor.rolling(window).mean()
    std_rolling = factor.rolling(window).std()
    zscore_factor = (factor - mean_rolling) / std_rolling
    enhanced_factors[f'{factor_name}_zscore'] = zscore_factor

Results: This became our dominant strategy with 35% improvement in mean IC.

2. Volatility Regime Adjustments

Concept: Scale factor strength based on Bitcoin’s volatility clustering patterns.

# Volatility regime enhancement
vol_20 = returns.rolling(20).std() * np.sqrt(365)
vol_regime = vol_20 / vol_20.rolling(60).mean()

for factor_name in factors.columns:
    factor = factors[factor_name]
    
    # Scale by inverse volatility (stronger signals in low vol)
    enhanced_factors[f'{factor_name}_vol_adj'] = factor / vol_regime
    
    # Regime-dependent scaling
    regime_factor = factor.copy()
    high_vol_mask = vol_regime > 1.2
    low_vol_mask = vol_regime < 0.8
    
    regime_factor[high_vol_mask] *= 1.5  # Amplify in high vol
    regime_factor[low_vol_mask] *= 0.8   # Dampen in low vol
    enhanced_factors[f'{factor_name}_regime'] = regime_factor

Rationale: Bitcoin’s extreme volatility clustering means factor signals should be interpreted differently in high vs low volatility periods.

3. Kalman Filtering

Concept: Apply state-space modeling to denoise indicators while preserving signal.

from pykalman import KalmanFilter

def kalman_denoise(series, transition_cov=0.01, observation_cov=1.0):
    clean_series = series.dropna()
    if len(clean_series) < 10:
        return pd.Series(index=series.index, dtype=float)
    
    kf = KalmanFilter(
        transition_matrices=[1],
        observation_matrices=[1], 
        initial_state_mean=clean_series.iloc[0],
        initial_state_covariance=1,
        observation_covariance=observation_cov,
        transition_covariance=transition_cov
    )
    
    state_means, _ = kf.filter(clean_series.values)
    result = pd.Series(index=series.index, dtype=float)
    result.loc[clean_series.index] = state_means.flatten()
    return result

# Apply different Kalman configurations
for factor_name in factors.columns:
    # Conservative smoothing
    enhanced_factors[f'{factor_name}_kalman'] = kalman_denoise(
        factors[factor_name], transition_cov=0.05, observation_cov=1.0
    )

Unexpected Result: Kalman filtering generally degraded performance, highlighting that Bitcoin’s “noise” may actually contain predictive information.

4. Factor Combinations

Concept: Create new factors by combining existing indicators in theoretically motivated ways.

# Multi-timeframe RSI
enhanced_factors['rsi_multi_tf'] = (
    factors['rsi_7'] * 0.3 + factors['rsi_14'] * 0.7
)

# RSI-momentum divergence
enhanced_factors['rsi_momentum_divergence'] = (
    factors['rsi_14'].pct_change(14) - factors['momentum_20']
)

# Volatility-momentum interaction
enhanced_factors['vol_momentum_interaction'] = (
    factors['momentum_20'] * (1 / vol_20)
)

# MACD acceleration
enhanced_factors['macd_acceleration'] = factors['macd'].diff(5)

Results

Strategy Performance Comparison

# Evaluate all enhancement strategies
strategies = {
    'Raw': factors.columns,
    'Cross-Sectional': [c for c in enhanced_factors.columns if '_rank' in c or '_zscore' in c],
    'Volatility-Regime': [c for c in enhanced_factors.columns if '_regime' in c or '_vol_adj' in c],
    'Kalman': [c for c in enhanced_factors.columns if '_kalman' in c],
    'Combinations': ['rsi_multi_tf', 'rsi_momentum_divergence', 'vol_momentum_interaction']
}

strategy_performance = {}
for strategy_name, factor_list in strategies.items():
    ics = []
    for factor in factor_list:
        for horizon in [7, 14, 30]:
            if strategy_name == 'Raw':
                ic = calculate_ic(factors[factor], forward_returns[f'fwd_{horizon}d'])
            else:
                ic = calculate_ic(enhanced_factors[factor], forward_returns[f'fwd_{horizon}d'])
            
            if not np.isnan(ic):
                ics.append(abs(ic))
    
    strategy_performance[strategy_name] = np.mean(ics) if ics else 0

print("Strategy Performance (Mean |IC|):")
for strategy, performance in sorted(strategy_performance.items(), 
                                   key=lambda x: x[1], reverse=True):
    improvement = (performance / strategy_performance['Raw'] - 1) * 100
    print(f"{strategy:>18}: {performance:.4f} ({improvement:+.1f}%)")
Pasted image 20250803033034.png

Top Performing Enhanced Factors

Factor Strategy IC (30d) Improvement
macd_rank Cross-Sectional 0.271 +93%
macd_signal_zscore Cross-Sectional 0.254 +112%
atr_ratio_rank Cross-Sectional 0.162 +62%
rsi_14_regime Volatility-Regime 0.205 +86%

Key Findings

  1. Cross-sectional ranking dominates with 35% mean improvement
  2. Volatility regime adjustments provide meaningful gains (+12%)
  3. Kalman filtering generally hurts performance (-21% to +12%)
  4. Factor combinations show moderate success
  5. Longer horizons (30d) show stronger factor performance

Conclusion

This systematic evaluation of alpha factor enhancement strategies yields clear actionable insights for Bitcoin quantitative research:

  1. Cross-sectional ranking should be the default enhancement for Bitcoin factors
  2. Volatility regime awareness provides additional alpha
  3. Sophisticated signal processing (Kalman filtering) can harm performance
  4. Simple, theoretically motivated enhancements often outperform complex ones

The 35% improvement in predictive power from cross-sectional ranking represents a substantial advance in Bitcoin alpha factor research, transforming good factors into genuinely powerful predictive signals.

For practitioners, these results suggest that relative positioning and regime awareness matter more than absolute indicator values in cryptocurrency markets. The failure of Kalman filtering serves as a reminder that sophisticated techniques must be validated empirically rather than assumed to improve performance.