Regime Detection

πŸ¦‰ AthenaAI is an AI-powered ensemble machine-learning trading bot for PocketOption, built on BinaryOptionsToolsV2 β†’ Get it on GitLab
πŸ’¬ Need help? Join the Chipa Discord Β· ⚑ Go no-code with ChipaEditor

RegimeDetector (in src/core/regime.py) classifies the current market into one of four regimes on every trade-loop iteration. The regime label feeds three consumers:

  • the expiry selector (trending β†’ longer expiries, volatile β†’ shorter),
  • the adaptive strategy (per-regime win-rate tracking and blocking),
  • the optional volatile-regime skip gate in the signal pipeline (skip_volatile_regime, off by default).

The four regimes

Defined in src/utils/enums.py and returned by RegimeDetector.detect:

RegimeMeaning
VOLATILEReturn volatility is high β€” choppy, reversal-prone market
TRENDING_UPClear upward price drift
TRENDING_DOWNClear downward price drift
RANGINGNone of the above β€” sideways / quiet market (also the default when there is not enough data)

The algorithm

detect(candles, window=30) is a single static method β€” there is no state to persist. The window comes from BotConfig.regime_window (default 30; see regime-window).

  1. If fewer than window candles exist, return RANGING immediately.

  2. Take the closes of the last 30 candles and compute per-bar fractional returns:

    rets = np.diff(closes) / (closes[:-1] + 1e-10)
  3. Fit a straight line through the closes (np.polyfit(arange, closes, 1)); the slope is the raw trend in price units per candle.

  4. Compute vol = np.std(rets) β€” the standard deviation of returns (a dimensionless fraction).

  5. Normalize the trend so the threshold is price-level independent:

    rel_trend = trend / (closes[-1] + 1e-10) * window

    Dividing by the last close converts the slope to a fractional change per candle; multiplying by window scales it to the fractional change over the whole 30-candle window. So rel_trend = 0.0003 means the fitted line moved about 0.03% of the price level (~3.5 pips on EURUSD at 1.17) across the window.

  6. Classify, in this order (volatility wins over trend):

    if vol > 0.0008:            return Regime.VOLATILE
    if rel_trend > 0.0003:      return Regime.TRENDING_UP
    if rel_trend < -0.0003:     return Regime.TRENDING_DOWN
    return Regime.RANGING
CheckThresholdRegime
1stvol > 0.0008 (0.08% per-bar return std)VOLATILE
2ndrel_trend > 0.0003 (+0.03% over the window)TRENDING_UP
3rdrel_trend < -0.0003TRENDING_DOWN
elseβ€”RANGING

Because the volatility check comes first, a strongly trending but very choppy market is labeled VOLATILE, not trending.

Calibration note: 1-minute EURUSD

The thresholds are a v6 recalibration specifically for 1-minute EURUSD candles. The code comment records that the old values (0.005 for volatility, 0.002 for trend) were far too aggressive at this timeframe β€” per-bar volatility on 1-minute EURUSD almost never reaches 0.5%, so effectively every candle window was classified RANGING and the regime-dependent logic (expiry scoring, adaptive blocking) never differentiated.

If you trade a different asset or timeframe, expect to re-tune these constants: higher-volatility assets (crypto, indices) or longer timeframes produce larger per-bar returns and will read VOLATILE almost constantly with the current 0.0008 cutoff. The thresholds are hard-coded in src/core/regime.py β€” there is no environment variable or config field for them.

Relationship to the volatility feature

The regime detector’s vol is the same quantity as the feature engine’s volatility feature (index 6), but computed over a different window: the regime uses regime_window (30 candles) while the feature uses feature_window (20 candles). The expiry selector reads the 20-candle feature value with its own thresholds (0.003 / 0.001), independent of the regime label it also receives.

See also

πŸ’‘ Want to discuss how AthenaAI makes decisions β€” or design your own logic like this without writing code? Join the Chipa Discord and give ChipaEditor a try.