Signals·TA Strategy Implementations·Intermediate

Regime Based Strategy Switching

Implement a market regime detection layer that classifies current market conditions and automatically routes trading logic between trend-following, mean-reversion, and breakout strategy modes based on the detected regime type.

statistical-methodstrading-signalstrading-strategies

Market Regime Switching Strategy

3. Strategy Overview

This section details a market regime-switching strategy that adapts its trading approach based on identified market conditions.

RegimeConditionStrategy Applied
TrendingADX > 25MA Crossover
RangingADX ≤ 25RSI Mean Reversion

Market conditions exhibit varying characteristics, making a single trading strategy suboptimal across all environments. Trend-following strategies yield positive returns during sustained directional movements but incur losses in sideways, range-bound markets. Conversely, mean-reversion strategies perform well during oscillating periods but are unprofitable during strong trends.

Regime detection, facilitated by the Average Directional Index (ADX), enables the application of the most suitable strategy for prevailing market conditions, thereby enhancing overall performance. An ADX value exceeding 25 indicates a trending market with sufficient directional momentum for trend-following strategies. An ADX value at or below 25 identifies a ranging market, characterized by oscillating price behavior where mean-reversion strategies are more appropriate.

3.1. Dependency Installation

Install necessary Python libraries.

[ ]
!pip install pandas numpy plotly
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2)
Requirement already satisfied: plotly in /usr/local/lib/python3.12/dist-packages (5.24.1)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.1)
Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.12/dist-packages (from plotly) (9.1.4)
Requirement already satisfied: packaging in /usr/local/lib/python3.12/dist-packages (from plotly) (26.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)

3.2. Library Imports

Import the required libraries for data manipulation, numerical operations, and plotting.

[ ]
import warnings; warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots

3.3. Synthetic Data Generation

Define a function to generate synthetic price and volume data for backtesting purposes. This function simulates candlestick data over a specified number of periods.

[ ]
def generate_data(periods):
    start_date = pd.to_datetime("2024-01-01 00:00:00+00:00")
    datetime_index = pd.date_range(start_date, periods=periods, freq="1min", tz="UTC")
    price_data = []; last_close = 42000
    for i in range(periods):
        open_price  = last_close + np.random.normal(0, last_close * 0.0005)
        close_price = open_price + np.random.normal(0, last_close * 0.005)
        body_high   = max(open_price, close_price)
        body_low    = min(open_price, close_price)
        high_price  = max(body_high + abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
        low_price   = min(body_low  - abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
        if high_price < low_price: high_price, low_price = low_price, high_price
        price_data.append({"open": max(1,int(open_price)), "high": max(1,int(high_price)),
                            "low":  max(1,int(low_price)),  "close": max(1,int(close_price))})
        last_close = close_price
    df = pd.DataFrame(price_data, index=datetime_index)
    df.index.name = "datetime"
    df["volume"] = np.random.uniform(100.0, 500.0, periods)
    df["datetime"] = df.index.to_series()
    return df.reset_index(drop=True)

3.4. Data Instantiation

Generate a DataFrame containing synthetic market data for analysis.

[ ]
df = generate_data(500)

3.5. Regime-Based Strategy Implementation

Implement the regime_based_strategy function. This function calculates the ADX to determine the market regime (trending or ranging) and then applies the appropriate sub-strategy (Moving Average crossover for trending, RSI mean reversion for ranging) to generate trading signals.

The ADX calculation involves:

  • Up/Down Movement: Measuring upward and downward price movements.
  • True Range (TR): The greatest of the current high minus current low, current high minus previous close absolute, and current low minus previous close absolute.
  • Average True Range (ATR): A smoothed moving average of the True Range.
  • Positive Directional Movement (+DM) and Negative Directional Movement (-DM): Components of directional movement.
  • Positive Directional Indicator (+DI) and Negative Directional Indicator (-DI): Ratios of smoothed +DM and -DM to ATR.
  • Directional Index (DX): The absolute difference between +DI and -DI, divided by their sum.
  • Average Directional Index (ADX): A smoothed moving average of DX.

The sub-strategies implemented are:

  • Trend-Following (MA Crossover): A buy signal is generated when a short-period Moving Average crosses above a long-period Moving Average. A sell signal is generated when it crosses below.
  • Mean-Reversion (RSI): A buy signal is generated when the Relative Strength Index (RSI) falls below a predefined oversold threshold (e.g., 30). A sell signal is generated when the RSI rises above an overbought threshold (e.g., 70).
[ ]
def regime_based_strategy(
    df: pd.DataFrame,
    adx_window: int = 14,
    adx_threshold: float = 25.0,
) -> pd.DataFrame:
    df = df.copy().sort_values("datetime", ignore_index=True)
    up   = df["high"] - df["high"].shift(1)
    down = df["low"].shift(1) - df["low"]
    tr   = pd.concat([df["high"]-df["low"],
                      (df["high"]-df["close"].shift(1)).abs(),
                      (df["low"] -df["close"].shift(1)).abs()], axis=1).max(axis=1)
    pdm  = np.where((up>down)&(up>0), up, 0.0)
    ndm  = np.where((down>up)&(down>0), down, 0.0)
    atr  = pd.Series(tr).rolling(adx_window).sum()
    pdi  = 100 * pd.Series(pdm).rolling(adx_window).sum() / atr
    ndi  = 100 * pd.Series(ndm).rolling(adx_window).sum() / atr
    dx   = 100 * (pdi-ndi).abs() / (pdi+ndi).replace(0, np.nan)
    df["adx"] = dx.rolling(adx_window).mean().values
    df["regime"] = np.where(df["adx"] > adx_threshold, "trending", "ranging")

    trend_signal = np.where(df["close"].rolling(5).mean() > df["close"].rolling(20).mean(), 1, -1)
    delta = df["close"].diff()
    rsi   = 100 - 100/(1 + delta.clip(lower=0).rolling(14).mean() /
                           (-delta.clip(upper=0)).rolling(14).mean().replace(0, np.nan))
    range_signal = np.where(rsi < 30, 1, np.where(rsi > 70, -1, 0))

    df["signal"] = np.where(df["regime"] == "trending", trend_signal, range_signal)
    return df

3.6. Strategy Application and Signal Generation

Apply the defined regime-based strategy to the generated market data. The resulting DataFrame will include the calculated ADX, identified market regime, and the corresponding trading signals.

The selection of the trading signal is dynamic: if the market is identified as "trending" by the ADX, signals are generated based on the Moving Average crossover strategy. If the market is identified as "ranging", signals are generated based on the RSI mean-reversion strategy. This automated signal selection eliminates the need for manual switching between sub-strategies.

[ ]
df_signals = regime_based_strategy(df)

3.7. Regime and Signal Distribution Analysis

Display the distribution of identified market regimes and generated trading signals.

[ ]
print("--- Regime Distribution ---")
print(df_signals["regime"].value_counts())
print("\n--- Signal Distribution ---")
print(df_signals["signal"].value_counts())
--- Regime Distribution ---
regime
trending    338
ranging     162
Name: count, dtype: int64

--- Signal Distribution ---
signal
-1    179
 1    171
 0    150
Name: count, dtype: int64

3.8. Strategy Visualization

Generate an interactive candlestick chart displaying price action, generated buy/sell signals, and the ADX indicator with the regime threshold.

[ ]
buy_signals  = df_signals[df_signals["signal"] ==  1]
sell_signals = df_signals[df_signals["signal"] == -1]

fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
    subplot_titles=["Price + Signals", "ADX (Regime Detector)"],
    row_heights=[0.65, 0.35])
fig.add_trace(go.Candlestick(x=df_signals["datetime"],
    open=df_signals["open"], high=df_signals["high"],
    low=df_signals["low"],   close=df_signals["close"], name="Price"), row=1, col=1)
fig.add_trace(go.Scatter(x=buy_signals["datetime"],  y=buy_signals["low"]  * 0.999,
    mode="markers", marker=dict(symbol="triangle-up",   size=10, color="green"), name="Buy (+1)"), row=1, col=1)
fig.add_trace(go.Scatter(x=sell_signals["datetime"], y=sell_signals["high"] * 1.001,
    mode="markers", marker=dict(symbol="triangle-down", size=10, color="red"),   name="Sell (−1)"), row=1, col=1)
fig.add_trace(go.Scatter(x=df_signals["datetime"], y=df_signals["adx"],
    mode="lines", name="ADX", line=dict(color="purple", width=1)), row=2, col=1)
fig.add_hline(y=25, line_dash="dash", line_color="orange", row=2, col=1, annotation_text="ADX 25 (regime threshold)")
fig.update_layout(title_text="Market Regime Switching Strategy",
    xaxis_rangeslider_visible=False, height=700, yaxis=dict(autorange=True))
fig.show()

Conclusion

This notebook demonstrates a market regime-switching strategy that dynamically adapts its trading approach based on identified market conditions using the Average Directional Index (ADX). By switching between a trend-following (MA Crossover) strategy in trending markets and a mean-reversion (RSI) strategy in ranging markets, the strategy aims to improve performance across various market environments. The visualization effectively shows the strategy's signals in conjunction with market price and ADX values.