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.
Characteristics: - Clear directional movement
Best strategies: - Trend following
Characteristics: - Price oscillates between support and resistance
Best strategies: - Range trading (buy support, sell resistance)
Characteristics: - Large, unpredictable swings
Best strategies: - Wider stops or no positions
ADX measures trend strength, not direction.
Readings: - ADX < 20: No trend (ranging)
Implementation: ```python import ta
adx = ta.trend.adx(df['high'], df['low'], df['close'], window=14)
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)
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'
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'
Single indicators give false signals. Combine multiple for reliability.
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
}
position sizing: Full size (100%)
Example strategy: - Wait for price above 50 SMA
position sizing: Reduced (50-75%)
Example strategy: - Identify range high and low
position sizing: Minimum (25-50%) or flat
Example strategy: - Wait for volatility to contract
The most profitable (and dangerous) periods are transitions between regimes.
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
]
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
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
Every day before trading:
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 |
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)
A trend-following system will lose money in ranges. Accept that some regimes aren't suitable for your strategy.
Regimes can last days to months. Don't switch strategies on every fluctuation. Require confirmation.
The shift between regimes is often where money is made or lost. Stay alert during transitions.
Simple rules that capture the essence work better than complex systems that curve-fit to history.
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.
Regime detection, cycles, and Cursor-for-Trading research loops.
Master crypto market regime analysis to identify bull markets, bear markets, and transition phases. Learn how regime changes signal major trading opportunities.
Regime context from funding and OI
Master market regime analysis for crypto trading. Learn to identify trending, ranging, and volatile markets. Adapt your strategy to current conditions for better results.
Understand crypto market cycles: accumulation, bull runs, distribution, and bear markets. Learn to identify phases and trade each.