Skip to main content

regime detection - walk-forward validation improving accuracy

regime detection upgraded.

walk-forward validation running.

accuracy improving.

the problem
#

static regime parameters:

optimized on historical data.

degrade in live trading.

overfitting risk.

solution needed: adaptive parameters that update.

walk-forward framework
#

concept:

train on window 1 (90 days).

validate on window 2 (30 days).

roll forward 30 days.

repeat.

prevents overfitting:

never training on future data.

parameters update regularly.

NexusFi algo trading discussions helped narrow this down - walk-forward vs fixed backtest debate.

implementation
#

import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from datetime import datetime, timedelta

class WalkForwardRegimeDetector:
    def __init__(self, train_days=90, test_days=30, roll_days=30):
        self.train_days = train_days
        self.test_days = test_days
        self.roll_days = roll_days
        self.models = {}
        self.performance_log = []

    def prepare_features(self, df, lookback=20):
        """
        Extract regime features from market data
        """
        # Volatility features
        df['returns'] = df['close'].pct_change()
        df['realized_vol'] = df['returns'].rolling(lookback).std() * np.sqrt(252)
        df['vol_trend'] = df['realized_vol'] - df['realized_vol'].shift(lookback)

        # Volume features
        df['volume_ratio'] = df['volume'] / df['volume'].rolling(lookback).mean()
        df['volume_trend'] = df['volume'].pct_change(lookback)

        # Correlation features (SPX vs sector ETFs)
        df['correlation'] = df['returns'].rolling(lookback).corr(df['sector_returns'])
        df['corr_trend'] = df['correlation'] - df['correlation'].shift(lookback)

        # Momentum features
        df['momentum_20'] = df['close'].pct_change(20)
        df['momentum_60'] = df['close'].pct_change(60)

        # VIX features
        df['vix_regime'] = pd.cut(df['VIX'], bins=[0, 15, 20, 100], labels=[0, 1, 2])
        df['vix_change'] = df['VIX'].pct_change(5)

        return df.dropna()

    def assign_regime_labels(self, df):
        """
        Label historical data with regime classifications
        Based on realized future performance
        """
        # Look forward 10 days to classify regime
        df['future_sharpe'] = (
            df['returns'].shift(-10).rolling(10).mean() /
            df['returns'].shift(-10).rolling(10).std()
        )

        # Classify regimes
        conditions = [
            (df['future_sharpe'] > 0.5) & (df['realized_vol'] < 0.15),  # Low vol favorable
            (df['future_sharpe'] > 0.3) & (df['realized_vol'] < 0.25),  # Med vol favorable
            (df['future_sharpe'] < -0.3) | (df['realized_vol'] > 0.30), # High vol unfavorable
        ]
        choices = ['low_vol_favorable', 'med_vol_favorable', 'unfavorable']
        df['regime'] = np.select(conditions, choices, default='neutral')

        return df

    def walk_forward_validate(self, market_data):
        """
        Walk-forward validation across entire dataset
        """
        results = []

        start_date = market_data.index[0]
        end_date = market_data.index[-1]
        current_date = start_date + timedelta(days=self.train_days)

        while current_date + timedelta(days=self.test_days) <= end_date:
            # Training window
            train_start = current_date - timedelta(days=self.train_days)
            train_end = current_date
            train_data = market_data[train_start:train_end]

            # Test window
            test_start = train_end
            test_end = test_start + timedelta(days=self.test_days)
            test_data = market_data[test_start:test_end]

            # Train model
            model = self.train_regime_model(train_data)

            # Validate on test set
            accuracy, predictions = self.validate_model(model, test_data)

            results.append({
                'train_start': train_start,
                'train_end': train_end,
                'test_start': test_start,
                'test_end': test_end,
                'accuracy': accuracy,
                'predictions': predictions
            })

            # Roll forward
            current_date += timedelta(days=self.roll_days)

        return pd.DataFrame(results)

    def train_regime_model(self, train_data):
        """
        Train RandomForest classifier on regime detection
        """
        features = ['realized_vol', 'vol_trend', 'volume_ratio',
                   'correlation', 'corr_trend', 'momentum_20',
                   'vix_regime', 'vix_change']

        X = train_data[features]
        y = train_data['regime']

        model = RandomForestClassifier(
            n_estimators=100,
            max_depth=8,
            min_samples_split=20,
            min_samples_leaf=10,
            random_state=42
        )

        model.fit(X, y)
        return model

    def validate_model(self, model, test_data):
        """
        Validate trained model on test set
        """
        features = ['realized_vol', 'vol_trend', 'volume_ratio',
                   'correlation', 'corr_trend', 'momentum_20',
                   'vix_regime', 'vix_change']

        X_test = test_data[features]
        y_test = test_data['regime']

        predictions = model.predict(X_test)
        accuracy = (predictions == y_test).mean()

        return accuracy, predictions

# Usage example
if __name__ == "__main__":
    # Load market data
    market_data = load_market_data('2020-01-01', '2024-09-12')

    # Prepare features and labels
    market_data = prepare_features(market_data)
    market_data = assign_regime_labels(market_data)

    # Initialize walk-forward detector
    detector = WalkForwardRegimeDetector(
        train_days=90,
        test_days=30,
        roll_days=30
    )

    # Run walk-forward validation
    results = detector.walk_forward_validate(market_data)

    print(f"Average accuracy: {results['accuracy'].mean():.2%}")
    print(f"Accuracy std: {results['accuracy'].std():.2%}")
    print(f"Min accuracy: {results['accuracy'].min():.2%}")
    print(f"Max accuracy: {results['accuracy'].max():.2%}")

results so far
#

average accuracy: 73% (up from 68% static)

accuracy std: 8.2% (down from 12.4%)

min accuracy: 61% (up from 52%)

max accuracy: 87% (down from 94%)

what this means:

more consistent predictions.

fewer extreme outliers.

better live performance.

key improvements
#

1. feature selection

realized vol + vol trend = critical.

correlation + corr trend = valuable.

vix regime + vix change = helpful.

2. regularization

max_depth=8 prevents overfitting.

min_samples controls variance.

3. rolling updates

parameters adapt every 30 days.

captures regime shifts faster.

4. validation methodology

never training on future data.

realistic performance estimates.

comparing to static approach
#

static parameters (march):

68% accuracy historical.

58% accuracy live (10% degradation).

walk-forward (september):

73% accuracy historical.

71% accuracy live (2% degradation).

5x improvement in live performance gap.

production integration
#

daily process:

  1. fetch latest 90 days data
  2. retrain model if 30 days elapsed
  3. generate regime predictions
  4. adjust strategy parameters
  5. log performance

automated via cron.

runs 8:30am PST before market open.

impact on trading
#

september results:

regime detection accuracy: 71%

trade acceptance rate: 61%

win rate: 75%

correlation evident:

better regime detection → better trade selection → higher win rate.

next improvements
#

1. ensemble approach

combine RandomForest + XGBoost + LSTM.

vote on regime classification.

2. online learning

update model daily instead of monthly.

faster adaptation.

3. confidence scoring

output probability distributions.

filter low-confidence predictions.

tonight (sep 12, 2:56am)
#

regime detection upgraded.

walk-forward validation running.

accuracy: 73% avg, 71% live.

5x improvement vs static.

production integration automated.

trading results improved.


2:56am thursday. regime detection walk-forward validation implemented. 73% avg accuracy (up from 68% static). live performance 71% (2% degradation vs 10% before). rolling 90-day train, 30-day test windows. parameters update monthly. september trading benefiting - 75% win rate month-to-date.

-AK

Related

regime detection improvements - faster market adaptation working
week 3 april going strong. adaptive strategy crushing it. been refining regime detection logic. current performance (apr 1-17) # trades: 29
looking ahead - september expectations, post-summer reset
august almost done. september starts next week. time to set expectations. what august taught # 1. volume drives everything
regime detection filtering framework - how i adapt to market conditions
august forcing me to rely on filters. figured worth explaining how regime detection works. learned a lot from options selling regime discussions on NexusFi about adapting to conditions. the problem # strategies don’t work in all conditions.
walk-forward optimization - how i avoid overfitting my strategies
overfitting = #1 way algos fail in production. backtest looks amazing. live trading implodes. walk-forward optimization prevents this. been discussing validation techniques on NexusFi algo trading threads and walk-forward is the gold standard.
week 3 april - stability maintained at full size
week 3 april done. third week at full size $1,500. stability holding. week 3 trades (apr 15-19) # monday 4/15: 2 trades, 2 wins. +$1,560
ramping to full size - confidence building week by week
first week april done. ramping to full size. confidence building. week 1 april trades (apr 1-5) # monday 4/1: 3 trades, 2 wins. +$520 ($800 size)