Market Regime Detection
Regime labels summarize observable trend and volatility states. They are model outputs with uncertainty, not natural market categories; measure whether they add held-out value to the downstream strategy.
Regime Types
| Regime |
EMA Slope (50, 5-bar) |
BB Width |
Price Action |
| Trending Up |
Positive normalized slope |
Expanding/steady |
Objective HH/HL or directional-return state |
| Trending Down |
Negative normalized slope |
Expanding/steady |
Objective LH/LL or directional-return state |
| Ranging |
Near-zero normalized slope |
Middle distribution |
Oscillation under predeclared rule |
| Squeeze |
Near-zero normalized slope |
Low percentile, optional BB inside KC |
Relative compression; direction unknown |
| Volatile / Chop |
Slope flipping run-to-run |
Very wide |
Whipsaws both directions |
Detection Inputs (all in the free indicator whitelist)
EMA slope candidate — compute over a predeclared horizon and normalize by price or ATR:
ema50_slope_pct = (ema50[-1] - ema50[-5]) / ema50[-5] * 100
Derive positive, negative, and near-zero bands from training data or rolling historical percentiles for the instrument/timeframe; freeze them before evaluation.
BB Width = (bb_upper - bb_lower) / bb_middle * 100 — rolling-100-period percentile:
> 80th pctl → expansion, supports a trend read
< 20th pctl → squeeze candidate; no direction or imminent break is implied
- mid-band → ranging
ATR Ratio = atr_14 / rolling_median(atr_14, 20):
Use rolling percentiles or training-set thresholds; ATR ratio measures volatility, not whether price is trending or choppy.
BB Inside KC — if bb_lower > kc_lower AND bb_upper < kc_upper, the Bollinger band is inside the Keltner channel → compression. Pair with low BB Width to confirm squeeze.
Donchian Position — (close - donchian_lower) / (donchian_upper - donchian_lower):
- near 0 or 1 with EMA slope agreeing → confirmed trend continuation
- mid-band (0.4–0.6) with flat slope → ranging
Composite Classification
| EMA Slope |
BB Width |
ATR Ratio |
BB-in-KC |
Regime |
| Outside trained near-zero band |
Expanding |
Elevated percentile |
No |
Trending candidate |
| Inside trained near-zero band |
Low percentile |
Low percentile |
Yes |
Squeeze candidate |
| Inside trained near-zero band |
Middle percentile |
Middle percentile |
No |
Range candidate |
| Unstable sign |
Very high percentile |
Very high percentile |
No |
Volatile candidate |
| Mixed |
Mixed |
Mixed |
Mixed |
Transitional/uncertain |
The table is a seed specification only. Do not count correlated inputs as independent agreement or map them directly to confidence/size. Calibrate a classifier, report posterior/calibration if available, otherwise report features plus classified, uncertain, or no trade.
Regime Change Signals
| Signal |
Meaning |
| EMA50 5-bar slope crosses through ±0.15% |
Range → trend or trend → range pivot |
| BB Width expansion exceeds trained percentile/rate |
Volatility expansion; direction unknown |
| BB Width contracts to <20th pctl + BB inside KC |
Squeeze forming |
| ATR Ratio jumps above 1.5x off a low base |
Vol expansion, expect direction reveal |
| Price closes outside Donchian-20 boundary |
Breakout from prior range |
Strategy Selection Matrix
| Regime |
Use These Skills |
Avoid |
| Trending |
moving-average-crossover, pullback-trading, fibonacci-trading, trailing-stop |
mean-reversion, range-trading |
| Ranging |
mean-reversion, bollinger-bands, supply-demand-zones |
momentum-trading, MA crossover |
| Squeeze |
breakout-trading (arm BOTH directions, wait for break) |
range-trading (about to break) |
| Volatile / Chop |
wider stops, smaller size, prefer to sit out |
all trend-following, all mean-reversion |
| Transitional |
use only strategies validated in this state or wait |
untested regime substitution |
Workflow
Pull indicators (get_indicators returns one indicator per call — fetch each separately):
get_indicators(indicator_code="ema_50", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)
get_indicators(indicator_code="bbands", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)
get_indicators(indicator_code="atr_14", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)
get_indicators(indicator_code="keltner_20", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)
get_indicators(indicator_code="donchian_20", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)
Compute the four inputs in one execute block:
execute python3 -c "
import numpy as np
ema50 = [<ema50 array>]
bb_lower, bb_upper, bb_middle = [<...>], [<...>], [<...>]
kc_lower, kc_upper = [<...>], [<...>]
atr = [<atr_14 array>]
don_lo, don_hi = [<...>], [<...>]
close = [<...>]
slope_pct = (ema50[-1] - ema50[-5]) / ema50[-5] * 100
bb_width = [(u - l) / m * 100 for u, l, m in zip(bb_upper, bb_lower, bb_middle)]
bb_width_pctl = sum(1 for w in bb_width if w <= bb_width[-1]) / len(bb_width) * 100
atr_ratio = atr[-1] / np.median(atr[-20:])
bb_in_kc = bb_lower[-1] > kc_lower[-1] and bb_upper[-1] < kc_upper[-1]
don_pos = (close[-1] - don_lo[-1]) / (don_hi[-1] - don_lo[-1])
print(f'slope_pct={slope_pct:.3f} bb_width_pctl={bb_width_pctl:.1f} atr_ratio={atr_ratio:.2f} bb_in_kc={bb_in_kc} don_pos={don_pos:.2f}')
"
Classify using thresholds frozen from training data. Report label, uncertainty, numeric inputs, data window, and which downstream strategies have validated conditional results.
Visualize (optional, only if user-facing report needs it):
draw_chart_analysis(action="create", drawing={
"type": "highlight",
"points": [
{"time": <regime_start>, "price": <candle_high>},
{"time": <current_time>, "price": <candle_low>}
],
"options": {"text": "TRENDING bullish (EMA50 slope +0.84%)"}
})
Evidence and Validation
- Treat the setup as a testable hypothesis, not a prediction. Define thresholds, entry, invalidation, and exit before evaluating outcomes.
- Calibrate on the same instrument, venue, session, and timeframe. Use closed candles and a held-out or walk-forward sample; record every variant tried.
- Include spread, fees, slippage, borrow or funding, partial fills, and latency. Reject the setup when net expectancy is not positive or depends on one narrow parameter.
- Return observed inputs, missing data, cost assumptions, entry, invalidation, exit, and a valid, watch, or no-trade status.
- Research basis: Ang & Bekaert supports regime-dependent volatility and correlation, while backtest-overfitting research cautions against selecting thresholds after repeated trials.
Key Rules
- Use regime detection only when it improves the downstream strategy versus a no-regime baseline.
- Treat transitional/uncertain states according to predeclared policy; never map them to arbitrary size.
- The inputs are correlated; feature count does not prove accuracy or reduce false calls.
- Model higher/lower-timeframe conflicts explicitly; the higher timeframe does not automatically override.
- Compute EMA slope via
execute; do NOT call ADX / DMI / Supertrend (API-billed, not in the free whitelist)
Related Skills
- multi-timeframe-analysis — regime on HTF sets context; MTF refines entries within the regime
- bollinger-bands — BB Width is a key detection input; BB strategies adapt to detected regime
- trailing-stop — trail method per regime (TRENDING: Donchian/chandelier; RANGING: structural; SQUEEZE: don't enter)
1---2name: market-regime-detection3description: Identify current market regime (trending, ranging, squeeze, or volatile) to select appropriate trading strategies. Uses only no-cost indicators (EMA slope, BB Width, ATR Ratio, Donchian) — avoids API-billed indicators like ADX/DMI/Supertrend.4license: Apache-2.05---67# Market Regime Detection89Regime labels summarize observable trend and volatility states. They are model outputs with uncertainty, not natural market categories; measure whether they add held-out value to the downstream strategy.1011## Regime Types1213| Regime | EMA Slope (50, 5-bar) | BB Width | Price Action |14| ------------ | --------------------- | --------- | ----------------------------------------- |15| **Trending Up** | Positive normalized slope | Expanding/steady | Objective HH/HL or directional-return state |16| **Trending Down** | Negative normalized slope | Expanding/steady | Objective LH/LL or directional-return state |17| **Ranging** | Near-zero normalized slope | Middle distribution | Oscillation under predeclared rule |18| **Squeeze** | Near-zero normalized slope | Low percentile, optional BB inside KC | Relative compression; direction unknown |19| **Volatile / Chop** | Slope flipping run-to-run | Very wide | Whipsaws both directions |2021## Detection Inputs (all in the free indicator whitelist)2223**EMA slope candidate** — compute over a predeclared horizon and normalize by price or ATR:24```25ema50_slope_pct = (ema50[-1] - ema50[-5]) / ema50[-5] * 10026```27Derive positive, negative, and near-zero bands from training data or rolling historical percentiles for the instrument/timeframe; freeze them before evaluation.2829**BB Width** = `(bb_upper - bb_lower) / bb_middle * 100` — rolling-100-period percentile:30- `> 80th pctl` → expansion, supports a trend read31- `< 20th pctl` → squeeze candidate; no direction or imminent break is implied32- mid-band → ranging3334**ATR Ratio** = `atr_14 / rolling_median(atr_14, 20)`:35Use rolling percentiles or training-set thresholds; ATR ratio measures volatility, not whether price is trending or choppy.3637**BB Inside KC** — if `bb_lower > kc_lower AND bb_upper < kc_upper`, the Bollinger band is inside the Keltner channel → compression. Pair with low BB Width to confirm squeeze.3839**Donchian Position** — `(close - donchian_lower) / (donchian_upper - donchian_lower)`:40- near 0 or 1 with EMA slope agreeing → confirmed trend continuation41- mid-band (0.4–0.6) with flat slope → ranging4243## Composite Classification4445| EMA Slope | BB Width | ATR Ratio | BB-in-KC | Regime |46| ---------------- | -------- | --------- | -------- | -------------------- |47| Outside trained near-zero band | Expanding | Elevated percentile | No | **Trending candidate** |48| Inside trained near-zero band | Low percentile | Low percentile | Yes | **Squeeze candidate** |49| Inside trained near-zero band | Middle percentile | Middle percentile | No | **Range candidate** |50| Unstable sign | Very high percentile | Very high percentile | No | **Volatile candidate** |51| Mixed | Mixed | Mixed | Mixed | **Transitional/uncertain** |5253The table is a seed specification only. Do not count correlated inputs as independent agreement or map them directly to confidence/size. Calibrate a classifier, report posterior/calibration if available, otherwise report features plus `classified`, `uncertain`, or `no trade`.5455## Regime Change Signals5657| Signal | Meaning |58| ----------------------------------------------- | -------------------------------------- |59| EMA50 5-bar slope crosses through ±0.15% | Range → trend or trend → range pivot |60| BB Width expansion exceeds trained percentile/rate | Volatility expansion; direction unknown |61| BB Width contracts to <20th pctl + BB inside KC | Squeeze forming |62| ATR Ratio jumps above 1.5x off a low base | Vol expansion, expect direction reveal |63| Price closes outside Donchian-20 boundary | Breakout from prior range |6465## Strategy Selection Matrix6667| Regime | Use These Skills | Avoid |68| --------------- | --------------------------------------------------- | --------------------------------------- |69| **Trending** | moving-average-crossover, pullback-trading, fibonacci-trading, trailing-stop | mean-reversion, range-trading |70| **Ranging** | mean-reversion, bollinger-bands, supply-demand-zones | momentum-trading, MA crossover |71| **Squeeze** | breakout-trading (arm BOTH directions, wait for break) | range-trading (about to break) |72| **Volatile / Chop** | wider stops, smaller size, prefer to sit out | all trend-following, all mean-reversion |73| **Transitional** | use only strategies validated in this state or wait | untested regime substitution |7475## Workflow76771. **Pull indicators** (`get_indicators` returns one indicator per call — fetch each separately):78 ```79 get_indicators(indicator_code="ema_50", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)80 get_indicators(indicator_code="bbands", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)81 get_indicators(indicator_code="atr_14", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)82 get_indicators(indicator_code="keltner_20", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)83 get_indicators(indicator_code="donchian_20", symbol=<symbol>, exchange=<exchange>, interval=<interval>, count=120)84 ```85862. **Compute the four inputs** in one execute block:87 ```88 execute python3 -c "89 import numpy as np90 ema50 = [<ema50 array>]91 bb_lower, bb_upper, bb_middle = [<...>], [<...>], [<...>]92 kc_lower, kc_upper = [<...>], [<...>]93 atr = [<atr_14 array>]94 don_lo, don_hi = [<...>], [<...>]95 close = [<...>]9697 slope_pct = (ema50[-1] - ema50[-5]) / ema50[-5] * 10098 bb_width = [(u - l) / m * 100 for u, l, m in zip(bb_upper, bb_lower, bb_middle)]99 bb_width_pctl = sum(1 for w in bb_width if w <= bb_width[-1]) / len(bb_width) * 100100 atr_ratio = atr[-1] / np.median(atr[-20:])101 bb_in_kc = bb_lower[-1] > kc_lower[-1] and bb_upper[-1] < kc_upper[-1]102 don_pos = (close[-1] - don_lo[-1]) / (don_hi[-1] - don_lo[-1])103104 print(f'slope_pct={slope_pct:.3f} bb_width_pctl={bb_width_pctl:.1f} atr_ratio={atr_ratio:.2f} bb_in_kc={bb_in_kc} don_pos={don_pos:.2f}')105 "106 ```1071083. **Classify** using thresholds frozen from training data. Report label, uncertainty, numeric inputs, data window, and which downstream strategies have validated conditional results.1091104. **Visualize** (optional, only if user-facing report needs it):111 ```112 draw_chart_analysis(action="create", drawing={113 "type": "highlight",114 "points": [115 {"time": <regime_start>, "price": <candle_high>},116 {"time": <current_time>, "price": <candle_low>}117 ],118 "options": {"text": "TRENDING bullish (EMA50 slope +0.84%)"}119 })120 ```121122## Evidence and Validation123124- Treat the setup as a testable hypothesis, not a prediction. Define thresholds, entry, invalidation, and exit before evaluating outcomes.125- Calibrate on the same instrument, venue, session, and timeframe. Use closed candles and a held-out or walk-forward sample; record every variant tried.126- Include spread, fees, slippage, borrow or funding, partial fills, and latency. Reject the setup when net expectancy is not positive or depends on one narrow parameter.127- Return observed inputs, missing data, cost assumptions, entry, invalidation, exit, and a valid, watch, or no-trade status.128- Research basis: [Ang & Bekaert](https://www.nber.org/papers/w7056) supports regime-dependent volatility and correlation, while [backtest-overfitting research](https://escholarship.org/uc/item/9tq3327h) cautions against selecting thresholds after repeated trials.129130## Key Rules131132- Use regime detection only when it improves the downstream strategy versus a no-regime baseline.133- Treat transitional/uncertain states according to predeclared policy; never map them to arbitrary size.134- The inputs are correlated; feature count does not prove accuracy or reduce false calls.135- Model higher/lower-timeframe conflicts explicitly; the higher timeframe does not automatically override.136- Compute EMA slope via `execute`; do NOT call ADX / DMI / Supertrend (API-billed, not in the free whitelist)137138## Related Skills139140- **multi-timeframe-analysis** — regime on HTF sets context; MTF refines entries within the regime141- **bollinger-bands** — BB Width is a key detection input; BB strategies adapt to detected regime142- **trailing-stop** — trail method per regime (TRENDING: Donchian/chandelier; RANGING: structural; SQUEEZE: don't enter)