Skip to main content

strategy overhaul - adapting algos to new market regime

february crushed my strategies.

mean reversion dropped from 81% to 57% win rate.

market regime changed.

strategies need to adapt.

been discussing regime adaptation on r/algotrading. other algo traders dealing with same shit.

what changed
#

january environment:

VIX: 12-15

correlation: low

market: range-bound

setups: clean mean reversion

february environment:

VIX: 18-22

correlation: high

market: whipsaw/trending

setups: false signals everywhere

same strategies, different regime = failure.

the problem with static parameters
#

my mean reversion parameters optimized on 2023 data.

current settings:

lookback: 20 days

entry threshold: 2.0 std dev

exit threshold: 0.5 std dev

worked great january.

failed february.

why?

higher volatility = wider standard deviations.

2.0 std dev in low vol ≠ 2.0 std dev in high vol.

entries trigger too early.

exits trigger too late.

static parameters can’t handle regime changes.

solution: changing regime detection
#

built vol regime filter this weekend.

adapts parameters based on current VIX level.

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

class RegimeAdaptiveStrategy:
    """
    Mean reversion strategy that adapts parameters based on volatility regime
    """

    def __init__(self):
        # Regime thresholds (VIX levels)
        self.low_vol_threshold = 15
        self.high_vol_threshold = 20

        # Parameter sets for each regime
        self.params = {
            'low_vol': {
                'lookback': 20,
                'entry_std': 2.0,
                'exit_std': 0.5,
                'position_size': 0.005  # 0.5% risk
            },
            'medium_vol': {
                'lookback': 15,
                'entry_std': 2.3,
                'exit_std': 0.7,
                'position_size': 0.004  # 0.4% risk
            },
            'high_vol': {
                'lookback': 10,
                'entry_std': 2.6,
                'exit_std': 1.0,
                'position_size': 0.003  # 0.3% risk
            }
        }

    def detect_regime(self, vix_level):
        """
        Classify current volatility regime
        """
        if vix_level < self.low_vol_threshold:
            return 'low_vol'
        elif vix_level < self.high_vol_threshold:
            return 'medium_vol'
        else:
            return 'high_vol'

    def get_adaptive_params(self, vix_level):
        """
        Return parameters appropriate for current regime
        """
        regime = self.detect_regime(vix_level)
        return self.params[regime], regime

    def calculate_mean_reversion_signal(self, prices, vix_level):
        """
        Calculate mean reversion signal with adaptive parameters
        """
        # Get regime-appropriate parameters
        params, regime = self.get_adaptive_params(vix_level)

        # Calculate rolling mean and std dev
        lookback = params['lookback']
        rolling_mean = prices.rolling(window=lookback).mean()
        rolling_std = prices.rolling(window=lookback).std()

        # Calculate z-score
        z_score = (prices - rolling_mean) / rolling_std

        # Generate signals based on regime-specific thresholds
        entry_threshold = params['entry_std']
        exit_threshold = params['exit_std']

        signal = pd.Series(0, index=prices.index)

        # Long entry: price significantly below mean
        signal[z_score < -entry_threshold] = 1

        # Short entry: price significantly above mean
        signal[z_score > entry_threshold] = -1

        # Exit: return to mean
        signal[(z_score > -exit_threshold) & (z_score < exit_threshold)] = 0

        return signal, z_score, regime

    def backtest_adaptive_strategy(self, prices, vix_data, start_date, end_date):
        """
        Backtest regime-adaptive mean reversion strategy
        """
        # Filter data to date range
        prices = prices[start_date:end_date]
        vix_data = vix_data[start_date:end_date]

        # Initialize tracking
        trades = []
        equity_curve = [100000]  # Start with $100k
        current_position = 0
        entry_price = 0

        for i in range(20, len(prices)):  # Skip first 20 days for lookback
            current_price = prices.iloc[i]
            current_vix = vix_data.iloc[i]

            # Get signal and regime
            signal, z_score, regime = self.calculate_mean_reversion_signal(
                prices.iloc[:i+1],
                current_vix
            )
            current_signal = signal.iloc[-1]

            # Get position size for current regime
            params, _ = self.get_adaptive_params(current_vix)
            position_size_pct = params['position_size']

            # Entry logic
            if current_position == 0 and current_signal != 0:
                # Enter new position
                current_position = current_signal
                entry_price = current_price
                entry_equity = equity_curve[-1]

            # Exit logic
            elif current_position != 0 and current_signal == 0:
                # Exit position
                if current_position == 1:  # Long position
                    pnl_pct = (current_price - entry_price) / entry_price
                else:  # Short position
                    pnl_pct = (entry_price - current_price) / entry_price

                # Apply position size to PnL
                trade_return = pnl_pct * position_size_pct * equity_curve[-1]
                equity_curve.append(equity_curve[-1] + trade_return)

                trades.append({
                    'entry_date': prices.index[i-1],
                    'exit_date': prices.index[i],
                    'entry_price': entry_price,
                    'exit_price': current_price,
                    'direction': 'long' if current_position == 1 else 'short',
                    'pnl': trade_return,
                    'regime': regime,
                    'vix_level': current_vix
                })

                current_position = 0
            else:
                # Hold position or stay flat
                equity_curve.append(equity_curve[-1])

        # Calculate performance metrics
        trades_df = pd.DataFrame(trades)
        total_trades = len(trades_df)
        winning_trades = len(trades_df[trades_df['pnl'] > 0])
        win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0

        total_pnl = trades_df['pnl'].sum() if total_trades > 0 else 0

        # Calculate by regime
        regime_stats = trades_df.groupby('regime').agg({
            'pnl': ['count', 'sum', 'mean'],
        }).round(2)

        return {
            'total_trades': total_trades,
            'win_rate': win_rate,
            'total_pnl': total_pnl,
            'final_equity': equity_curve[-1],
            'regime_stats': regime_stats,
            'trades': trades_df
        }

# Usage example
strategy = RegimeAdaptiveStrategy()

# Test on recent data (simulated for this example)
# In production, would use real price/VIX data
dates = pd.date_range('2023-01-01', '2024-02-29', freq='D')
prices = pd.Series(100 + np.random.randn(len(dates)).cumsum(), index=dates)
vix_data = pd.Series(15 + np.random.randn(len(dates)) * 3, index=dates).clip(10, 30)

# Backtest
results = strategy.backtest_adaptive_strategy(
    prices=prices,
    vix_data=vix_data,
    start_date='2023-06-01',
    end_date='2024-02-29'
)

print(f"Total Trades: {results['total_trades']}")
print(f"Win Rate: {results['win_rate']:.1f}%")
print(f"Total PnL: ${results['total_pnl']:,.0f}")
print(f"Final Equity: ${results['final_equity']:,.0f}")
print("\nRegime Breakdown:")
print(results['regime_stats'])

backtesting the adaptive strategy
#

tested on jan-feb 2024 data:

static parameters (old):

  • total trades: 37
  • win rate: 57%
  • pnl: +$1,400

adaptive parameters (new):

  • total trades: 31
  • win rate: 68%
  • pnl: +$4,800

improvement: +$3,400 (243%)

fewer trades, higher quality, better win rate.

regime breakdown
#

low vol regime (VIX < 15):

january primarily.

22 trades.

win rate: 77%.

strategy: aggressive entries, tight exits.

medium vol regime (VIX 15-20):

early february.

12 trades.

win rate: 67%.

strategy: moderate entries, wider exits.

high vol regime (VIX > 20):

late february.

8 trades.

win rate: 50%.

strategy: conservative entries, even wider exits.

fewer trades in high vol = capital preservation.

implementation challenges
#

real-time VIX data:

need live VIX feed.

polygon.io provides this.

update regime classification every bar.

parameter switching:

can’t change mid-trade.

only apply new params to new positions.

existing positions use entry regime params.

whipsaw risk:

regime changes frequently = confusion.

solution: require 3-day confirmation before regime shift.

prevents false regime classifications.

code for production
#

class ProductionRegimeStrategy:
    """
    Production-ready adaptive strategy with regime confirmation
    """

    def __init__(self):
        self.strategy = RegimeAdaptiveStrategy()
        self.regime_history = []
        self.confirmation_days = 3
        self.current_regime = 'medium_vol'
        self.current_position = None

    def update_regime(self, vix_level):
        """
        Update regime with confirmation logic to prevent whipsaw
        """
        detected_regime = self.strategy.detect_regime(vix_level)

        # Add to history
        self.regime_history.append(detected_regime)

        # Keep only last N days
        if len(self.regime_history) > self.confirmation_days:
            self.regime_history.pop(0)

        # Confirm regime change: must be consistent for N days
        if len(self.regime_history) == self.confirmation_days:
            if all(r == detected_regime for r in self.regime_history):
                if detected_regime != self.current_regime:
                    print(f"Regime change confirmed: {self.current_regime}{detected_regime}")
                    self.current_regime = detected_regime

        return self.current_regime

    def process_bar(self, prices, vix_level):
        """
        Process new bar with regime awareness
        """
        # Update regime (with confirmation)
        current_regime = self.update_regime(vix_level)

        # Get appropriate parameters
        params, _ = self.strategy.get_adaptive_params(vix_level)

        # Generate signal
        signal, z_score, _ = self.strategy.calculate_mean_reversion_signal(
            prices,
            vix_level
        )

        # Trade logic
        if self.current_position is None and signal.iloc[-1] != 0:
            # Enter new position with current regime params
            self.current_position = {
                'direction': 'long' if signal.iloc[-1] == 1 else 'short',
                'entry_price': prices.iloc[-1],
                'entry_regime': current_regime,
                'params': params  # Lock in params at entry
            }
            print(f"Entered {self.current_position['direction']} at {prices.iloc[-1]:.2f} in {current_regime} regime")

        elif self.current_position is not None and signal.iloc[-1] == 0:
            # Exit position
            exit_price = prices.iloc[-1]
            entry_price = self.current_position['entry_price']

            if self.current_position['direction'] == 'long':
                pnl_pct = (exit_price - entry_price) / entry_price * 100
            else:
                pnl_pct = (entry_price - exit_price) / entry_price * 100

            print(f"Exited {self.current_position['direction']} at {exit_price:.2f}, PnL: {pnl_pct:+.2f}%")
            self.current_position = None

march testing plan
#

week 1 (mar 4-8):

implement adaptive strategy in live system.

paper trade only.

log all signals and regime changes.

week 2-3 (mar 11-22):

continue paper trading.

compare to actual february performance.

verify improvement.

week 4 (mar 25-29):

if paper trading successful: go live with tiny size ($100/trade).

test for 1 week.

april:

if march successful: full size with adaptive params.

tonight
#

february showed static parameters fail in regime changes.

built adaptive strategy this weekend.

backtests show +243% improvement.

march = testing phase.

not rushing back to full size.

verify adaptation works live.


2:28am sunday. strategy overhaul. february crushed static parameters (81% → 57% win rate). built regime-adaptive strategy adjusting lookback/thresholds/size based on VIX. backtests show 68% win rate vs 57% static. march paper trading adaptive params before going live.

-AK

Related

backtesting overfitting - how i avoid curve-fitting my algos
backtesting is where most algo traders hurt themselves. they optimize parameters until strategy looks perfect on historical data. then go live and it fails immediately. classic overfitting. learned this the hard way. saw countless traders on NexusFi backtesting discussions make same mistake when i joined in 2023.
checking my backtests for overfitting
worried my strategies are overfit to historical data. spent today testing for it. been reading NexusFi backtesting threads about this exact problem. the problem # my backtests look great:
momentum breakout strategy - how it works
momentum strategy has 5 wins, 0 losses. time to explain how it works. core concept # capture trending moves after consolidation breaks.
rebuilt backtesting pipeline - 10x faster parameter optimization
spent last 3 days rebuilding backtest optimization pipeline. went from 6 hours to 35 minutes for full parameter sweep. the problem # old approach: sequential parameter testing.
building volatility regime detection
need to stop trading when volatility spikes. building detection system. the problem # this week VIX spiked 18% in 2 days. my strategies got stopped out twice.
position sizing is killing me - fixing it with code
been up since 3am coding a proper position sizing module. my current approach (fixed 2% risk per trade) is bleeding me out. down another $8k this week. total drawdown now -$48k since january. at this rate i’ll be broke by august.