DEV Community

Cover image for How to Build a Regime-Aware Trading Bot with Freqtrade
Gunnar Thorderson
Gunnar Thorderson

Posted on • Originally published at getregime.com

How to Build a Regime-Aware Trading Bot with Freqtrade

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:

  • Calls the Regime API every 5 minutes for regime classification (bull/bear/chop)
  • Adjusts position sizing based on regime confidence
  • Uses different entry signals per regime (trend-following in bull, mean-reversion in chop)
  • Refuses new longs in bear markets
  • Caches API responses to stay within rate limits

Prerequisites

  • A running Freqtrade installation (docs)
  • A Regime API key (free tier works for testing — register here)
  • Python 3.11+ with requests installed

Step 1: Get Your API Key

curl -X POST https://getregime.com/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@email.com","name":"freqtrade-bot"}'
Enter fullscreen mode Exit fullscreen mode

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

{
  "regime_api_key": "cse_free_abc123...",
  "regime_api_url": "https://getregime.com"
}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Running It

freqtrade trade --strategy RegimeAwareStrategy \
  --config user_data/config.json
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.


Try Regime Intelligence

Regime is a real-time crypto market regime detection API. One endpoint tells you if the market is bull, bear, or chop — so your bot only trades when conditions match your strategy.

Free API access → | See pricing → | API docs →

Top comments (0)