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
requestsinstalled
Step 1: Get Your API Key
``bash
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:
`json
{
"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:
`python
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.
`python
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:
`python
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:
`python
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
`bash
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 available in the Regime GitHub repo. It includes all the code above plus logging, error handling, and a configuration guide.
Building something with Regime data? Reply on Twitter/X or drop a comment in the Freqtrade Discord. We're actively helping builders integrate.