Detecting Market Regimes in Crypto: Trending, Ranging, and Volatile States
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
Trending Markets
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
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
Trending Regime Playbook
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
- Use options for defined risk
- Trade reduced size
- Focus on capital preservation
Regime Transition Detection
The most profitable (and dangerous) periods are transitions between regimes.
Signs of Trending → Ranging
- ADX declining from high levels
- Failed breakouts
- Lower highs AND higher lows forming
- Decreasing momentum
Signs of Ranging → Trending
- 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:
- Calculate current ADX
- Check Bollinger Band width percentile
- Note ATR relative to history
- Assess MA alignment and slope
- Combine into regime classification
- 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.


![AI Crypto Trading - The Complete Guide [2026]](/_next/image?url=%2Fblog-images%2Ffeatured_ai_crypto_trading_bots_guide_1200x675.png&w=3840&q=75&dpl=dpl_EE1jb3NVPHZGEtAvKYTEHYxKXJZT)
![Crypto Trading Signals - The Ultimate Guide [2026]](/_next/image?url=%2Fblog-images%2Ffeatured_ai_signal_providers_1200x675.png&w=3840&q=75&dpl=dpl_EE1jb3NVPHZGEtAvKYTEHYxKXJZT)