How to Build a Regime-Aware Trading Bot with Freqtrade

Most Freqtrade strategies treat every market condition the same. An EMA crossover fires in a bull market? Buy. Same crossover in a grinding bear? Also buy. Then you wonder why your backtest looked great but live trading bled out over three months.

The fix isn't a better indicator. It's knowing when to trade and when to sit out.

This tutorial shows you how to plug market regime detection into a Freqtrade strategy so your bot automatically adapts — aggressive in bull markets, defensive in chop, and sidelined in bear conditions.

What You'll Build

A Freqtrade strategy that:

Prerequisites

Step 1: Get Your API Key

curl -X POST https://getregime.com/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","name":"freqtrade-bot"}'

Save the apiKey from the response. Add it to your Freqtrade config:

{
  "regime_api_key": "cse_free_abc123...",
  "regime_api_url": "https://getregime.com"
}

Step 2: The Regime Client

Add this to your strategy file — it handles API calls, caching, and fallback:

import requests
from datetime import datetime, timedelta, timezone

class RegimeClient:
    def __init__(self, api_key: str, base_url: str = "https://getregime.com"):
        self.api_key = api_key
        self.base_url = base_url
        self._cache = None
        self._cache_time = None
        self._cache_ttl = timedelta(minutes=5)

    def get_regime(self) -> dict:
        now = datetime.now(timezone.utc)
        if self._cache and self._cache_time and (now - self._cache_time) < self._cache_ttl:
            return self._cache

        try:
            resp = requests.get(
                f"{self.base_url}/api/v1/market/regime",
                headers={"X-API-Key": self.api_key},
                timeout=10
            )
            resp.raise_for_status()
            self._cache = resp.json()
            self._cache_time = now
            return self._cache
        except Exception:
            return self._cache or {"regime": "unknown", "confidence": 0}

The 5-minute cache matches the API's data refresh rate. If the API is unreachable, the last known regime is used — your bot doesn't crash because of a network blip.

Step 3: Regime-Aware Entry Logic

Here's the core idea: different market conditions call for different entry strategies.

from freqtrade.strategy import IStrategy
import talib.abstract as ta

class RegimeAwareStrategy(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = '5m'
    stoploss = -0.05
    can_short = False

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.regime_client = RegimeClient(
            api_key=config.get('regime_api_key', ''),
            base_url=config.get('regime_api_url', 'https://getregime.com')
        )

    def populate_indicators(self, dataframe, metadata):
        dataframe['ema_8'] = ta.EMA(dataframe, timeperiod=8)
        dataframe['ema_21'] = ta.EMA(dataframe, timeperiod=21)
        dataframe['bb_upper'] = ta.BBANDS(dataframe, timeperiod=20)[0]
        dataframe['bb_lower'] = ta.BBANDS(dataframe, timeperiod=20)[2]
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        regime_data = self.regime_client.get_regime()
        regime = regime_data.get('regime', 'unknown')
        confidence = regime_data.get('confidence', 0)

        if regime == 'bull' and confidence > 0.6:
            # Trend-following: EMA crossover
            dataframe.loc[
                (dataframe['ema_8'] > dataframe['ema_21']) &
                (dataframe['rsi'] < 70),
                'enter_long'
            ] = 1

        elif regime == 'chop':
            # Mean-reversion: Bollinger bounce
            dataframe.loc[
                (dataframe['close'] < dataframe['bb_lower']) &
                (dataframe['rsi'] < 35),
                'enter_long'
            ] = 1

        # Bear regime: no entries. Bot sits out.
        return dataframe

Step 4: Regime-Based Position Sizing

Don't just change your signals — change your conviction:

    def custom_stake_amount(self, current_time, current_rate, proposed_stake,
                           min_stake, max_stake, leverage, entry_tag, side, **kwargs):
        regime_data = self.regime_client.get_regime()
        regime = regime_data.get('regime', 'unknown')
        confidence = regime_data.get('confidence', 0)

        multiplier = {
            'bull': 1.0,      # Full size
            'chop': 0.5,      # Half size
            'bear': 0.0,      # No new positions
        }.get(regime, 0.25)

        # Scale by confidence: 74% confidence = 74% of multiplier
        adjusted = proposed_stake  multiplier  min(confidence, 1.0)
        return max(min_stake, min(adjusted, max_stake))

Step 5: Dynamic Stop-Loss

Tighten stops in choppy markets, let winners run in trends:

    def custom_stoploss(self, current_time, current_rate, current_profit,
                       after_fill, **kwargs):
        regime_data = self.regime_client.get_regime()
        regime = regime_data.get('regime', 'unknown')

        if regime == 'bull':
            return -0.08  # Wide stop for trending markets
        elif regime == 'chop':
            return -0.03  # Tight stop for ranging markets
        else:
            return -0.02  # Very tight in bear — protect capital

Running It

freqtrade trade --strategy RegimeAwareStrategy \
  --config user_data/config.json

Check your logs for regime state:

2026-04-12 08:00:05 - RegimeAwareStrategy - Regime: bear (74% confidence, grinding)
2026-04-12 08:00:05 - RegimeAwareStrategy - Position multiplier: 0.0 — sitting out

Free vs Pro Tier

The free tier gives you regime classification with a 15-minute delay — fine for backtesting and development. For live trading where you need real-time regime shifts and signal breakdowns, Pro at $49/mo removes the delay and adds the /signals/all endpoint for individual signal weights.

The dedicated Freqtrade endpoint (/api/v1/freqtrade/regime) returns a simplified response optimized for bot consumption.

What the Data Actually Looks Like

As of today, Regime has classified 9,285 market snapshots across 266 regime transitions. The current bear phase has been grinding for 59 hours at 74% confidence. That's the kind of sustained directional read that keeps your bot from buying dips in a downtrend.

The regime detector uses 10 signals: funding rates, open interest changes, Fear & Greed, DeFi TVL flows, stablecoin supply, macro divergences (DXY, VIX), and volume-weighted price action. It's not a single indicator — it's a composite classifier.

Full Strategy Template

A complete, production-ready strategy file is open-source at github.com/Thordersonjg/freqtrade-regime-filter — drop-in, with logging, fail-open/fail-closed handling, and a config guide. Grab it plus a free API key (auto-provisioned, no card) at getregime.com/freqtrade-strategy.


Building something with Regime data? Reply on Twitter/X or drop a comment in the Freqtrade Discord. We're actively helping builders integrate.