Skip to main content
Thrive is live on Product Hunt.Check us out (opens in a new tab)
Trading

Detecting Market Regimes in Crypto

How to detect crypto market regimes — trending, ranging, capitulating — using funding, breadth, and volatility signals that lead price.

•
7 min
Detecting Market Regimes in Crypto

From Thrive on YouTube

Watch the matching lesson

More on YouTube

Welcome to Thrive | Crypto Market Intelligence Platform for Traders

Education only. Not financial advice.

The same strategy that works brilliantly in a trending market fails miserably in a range. Market regime detection helps you adapt before your account pays the price.

This guide covers the indicators and methods to identify market regimes in real-time.

The Three Market Regimes

Characteristics: - Clear directional movement

  • Higher highs/lows (uptrend) or lower highs/lows (downtrend)
  • Moving averages slope consistently
  • Momentum indicators stay extended

Best strategies: - Trend following

  • Breakout trading
  • Momentum strategies
  • Moving average systems

Ranging Markets

Characteristics: - Price oscillates between support and resistance

  • No clear directional bias
  • Moving averages flat and intertwined
  • Mean reversion works

Best strategies: - Range trading (buy support, sell resistance)

  • Mean reversion
  • Options strategies (selling premium)
  • Grid trading

Volatile/Chaotic Markets

Characteristics: - Large, unpredictable swings

  • False breakouts common
  • High ATR relative to price
  • Stops get hit frequently

Best strategies: - Wider stops or no positions

  • Reduced position sizes
  • Volatility trading
  • Wait for regime change

Your Future P&L Starts Here

Every dollar you invest in skill pays compounding dividends forever.

Regime Detection Indicators

ADX (Average Directional Index)

ADX measures trend strength, not direction.

Readings: - ADX < 20: No trend (ranging)

  • ADX 20-40: Developing trend
  • ADX > 40: Strong trend
  • ADX > 60: Extreme trend (watch for exhaustion)

Implementation: ```python import ta

Calculate ADX

adx = ta.trend.adx(df['high'], df['low'], df['close'], window=14)

Classify regime

def get_adx_regime(adx_value): if adx_value < 20: return 'ranging' elif adx_value < 40: return 'weak_trend' else: return 'strong_trend'


### Bollinger Band Width

Measures volatility relative to recent history.

Readings: - Low bandwidth: Consolidation, expect expansion
- High bandwidth: Elevated volatility
- Bandwidth squeeze: Breakout imminent

Calculation: ```python
# Bollinger Band Width
bb_high = ta.volatility.bollinger_hband(df['close'])
bb_low = ta.volatility.bollinger_lband(df['close'])
bb_width = (bb_high - bb_low) / df['close']

# Width percentile
width_percentile = bb_width.rolling(100).rank(pct=True)

ATR Percentile

Compare current volatility to historical range.

# ATR
atr = ta.volatility.average_true_range(df['high'], df['low'], df['close'])

# ATR as percentage of price
atr_pct = atr / df['close']

# Percentile over last 252 periods
atr_percentile = atr_pct.rolling(252).rank(pct=True)

# Regime
def get_volatility_regime(percentile):
 if percentile < 0.2:
 return 'low_volatility'
 elif percentile < 0.8:
 return 'normal_volatility'
 else:
 return 'high_volatility'

Moving Average Relationship

The relationship between fast and slow MAs indicates regime.

sma_20 = df['close'].rolling(20).mean()
sma_50 = df['close'].rolling(50).mean()
sma_200 = df['close'].rolling(200).mean()

# MA slope (20-day)
ma_slope = (sma_20 - sma_20.shift(10)) / sma_20.shift(10)

# MA separation
ma_separation = abs(sma_20 - sma_50) / df['close']

# Regime logic
def get_ma_regime(slope, separation):
 if abs(slope) < 0.01 and separation < 0.02:
 return 'ranging'
 elif slope > 0.02:
 return 'uptrend'
 elif slope < -0.02:
 return 'downtrend'
 else:
 return 'transitioning'

Combined Regime Detection

Single indicators give false signals. Combine multiple for reliability.

Regime Scoring System

def calculate_regime_score(df):
 """
 Returns regime classification with confidence
 """
 scores = {
 'trending': 0,
 'ranging': 0,
 'volatile': 0
 }
 
 # ADX component
 adx = df['adx'].iloc[-1]
 if adx > 25:
 scores['trending'] += 2
 elif adx < 20:
 scores['ranging'] += 2
 
 # Bollinger width component
 bb_percentile = df['bb_width_pct'].iloc[-1]
 if bb_percentile < 0.25:
 scores['ranging'] += 1
 elif bb_percentile > 0.75:
 scores['volatile'] += 2
 
 # MA slope component
 slope = df['ma_slope'].iloc[-1]
 if abs(slope) > 0.02:
 scores['trending'] += 2
 elif abs(slope) < 0.005:
 scores['ranging'] += 1
 
 # ATR component
 atr_percentile = df['atr_percentile'].iloc[-1]
 if atr_percentile > 0.8:
 scores['volatile'] += 2
 elif atr_percentile < 0.3:
 scores['ranging'] += 1
 
 # Determine regime
 regime = max(scores, key=scores.get)
 confidence = scores[regime] / sum(scores.values()) if sum(scores.values()) > 0 else 0
 
 return {
 'regime': regime,
 'confidence': confidence,
 'scores': scores
 }

Adapting Strategies to Regimes

position sizing: Full size (100%)

  • Stop placement: Below swing lows (uptrend) or above swing highs (downtrend)
  • Entry method: Pullbacks to moving averages
  • Exit method: Trailing stops, MA crosses

Example strategy: - Wait for price above 50 SMA

  • Enter on pullback to 20 SMA
  • Stop below recent swing low
  • Trail stop with 20 SMA

Ranging Regime Playbook

position sizing: Reduced (50-75%)

  • Stop placement: Beyond range boundaries
  • Entry method: At support/resistance
  • Exit method: At opposite boundary

Example strategy: - Identify range high and low

  • Buy at range low with confirmation
  • Sell at range high
  • Stop loss if range breaks

Volatile Regime Playbook

position sizing: Minimum (25-50%) or flat

  • Stop placement: Very wide or no hard stops
  • Entry method: Only on extreme readings
  • Exit method: Quick profits, don't hold

Example strategy: - Wait for volatility to contract

Regime Transition Detection

The most profitable (and dangerous) periods are transitions between regimes.

  • ADX declining from high levels
  • Failed breakouts
  • Lower highs AND higher lows forming
  • Decreasing momentum
  • Bollinger squeeze (low width)
  • Increasing volume
  • Break of range with follow-through
  • ADX turning up from low levels

Signs of Volatile → Normal

  • Decreasing ATR
  • Smaller daily ranges
  • Stabilizing price action
  • Volume normalization

Machine Learning Approach

Feature Engineering

features = [
 'adx',
 'adx_change', # ADX momentum
 'bb_width',
 'bb_width_percentile',
 'atr_pct',
 'atr_percentile',
 'ma_slope_20',
 'ma_slope_50',
 'ma_separation',
 'volume_ratio', # Volume vs average
 'range_pct', # Daily range as % of price
 'close_vs_ma50', # Price relative to MA
]

Simple Classifier

from sklearn.ensemble import RandomForestClassifier

def train_regime_classifier(df, features, labels):
 """
 Train a classifier to detect market regimes
 """
 X = df[features].dropna()
 y = labels.loc[X.index]
 
 model = RandomForestClassifier(
 n_estimators=100,
 max_depth=5,
 random_state=42
 )
 
 model.fit(X, y)
 return model

# Generate labels (can be manual or rule-based initially)
# Then refine with model predictions

Hidden Markov Models

More sophisticated approach that captures regime persistence:

from hmmlearn import hmm

def fit_regime_hmm(returns, n_regimes=3):
 """
 Fit Hidden Markov Model to detect regimes
 """
 model = hmm.GaussianHMM(
 n_components=n_regimes,
 covariance_type="full",
 n_iter=100
 )
 
 model.fit(returns.values.reshape(-1, 1))
 regimes = model.predict(returns.values.reshape(-1, 1))
 
 return model, regimes

Practical Implementation

Daily Regime Check

Every day before trading:

  1. Calculate current ADX
  2. Check Bollinger Band width percentile
  3. Note ATR relative to history
  4. Assess MA alignment and slope
  5. Combine into regime classification
  6. Adjust strategy accordingly

Regime Dashboard

Track these metrics:

Metric Current 7-Day Avg Signal
ADX 28 25 Trending
BB Width %ile 35% 45% Normal
ATR %ile 55% 60% Normal
MA Slope +1.5% +1.2% Uptrend
Regime Trending

Alert System

Set alerts for regime changes:

def check_regime_change(current_regime, previous_regime):
 if current_regime!= previous_regime:
 send_alert(f"Regime change: {previous_regime} → {current_regime}")
 log_regime_change(current_regime)

Common Mistakes

1. Forcing Trades in Wrong Regime

A trend-following system will lose money in ranges. Accept that some regimes aren't suitable for your strategy.

2. Switching Strategies Too Quickly

Regimes can last days to months. Don't switch strategies on every fluctuation. Require confirmation.

3. Ignoring Transition Periods

The shift between regimes is often where money is made or lost. Stay alert during transitions.

4. Over-Optimizing Regime Rules

Simple rules that capture the essence work better than complex systems that curve-fit to history.

FAQs

How long do market regimes typically last? Varies widely. Trending regimes can last weeks to months. Ranges can persist for extended periods. Volatile regimes are usually shorter but more intense.

Should I trade all regimes? No. Most strategies work in specific regimes. It's better to sit out unfavorable regimes than force trades.

Can regimes change intraday? Yes, especially on lower timeframes. Daily regime assessment is usually sufficient for swing trading. Day traders may need more frequent checks.

What's the best indicator for regime detection? ADX combined with Bollinger Band width covers most situations. Add ATR percentile for a more complete picture.