Add a Regime Filter to Any Freqtrade Strategy in 10 Minutes

A regime filter is a single gate you add on top of your existing Freqtrade entry logic: before a trade opens, the bot checks whether the current market regime — bull, bear, or chop — matches the conditions your strategy is built for, and skips the entry if it doesn't. Your Freqtrade bot otherwise runs the same entry logic in every market, which is why strategies that backtest brilliantly blow up live: the backtest period was one regime, and the live period is another.

This guide shows you how to add Regime's market-condition API to any Freqtrade strategy. No complicated setup. No new dependencies beyond requests. Ten minutes of work for a permanent improvement to your bot's risk management — and there's a free, complete strategy file at the end.

Why Your Freqtrade Bot Needs a Regime Filter

Freqtrade's populate_entry_trend function generates buy signals based on technical indicators — RSI, MACD, Bollinger Bands, moving averages, whatever your strategy uses. The problem: those indicators don't know context.

An RSI of 30 means something completely different in a bull market (buying opportunity) versus a bear market (could drop to 15). A MACD crossover in a trending market leads to a profitable trade. The same crossover in chop leads to a whipsaw loss.

Without regime awareness, your bot makes two systematic errors:

  • Enters trades in hostile regimes — buying breakouts in bear markets, selling dips in bull markets
  • Uses uniform sizing — risking the same amount in high-confidence trends as in uncertain chop
  • Both errors compound. Gating entries on regime doesn't change your edge — it just stops your edge from firing when the environment is wrong for it.

    The placement trap: don't call the API in populate_*

    The mistake almost everyone makes first: putting the regime fetch in populate_indicators or populate_entry_trend. Those run vectorized over your whole history — thousands of candles per call, expected to be pure and fast. An HTTP request there either hammers the API thousands of times in a backtest or blocks the bot loop.

    The correct place for an external call is confirm_trade_entry — it runs once, on the live bot, at the moment a trade is about to open. Cache the answer for a few minutes so a busy multi-pair bot makes a handful of calls per hour.

    The Regime API Endpoint for Freqtrade

    Regime provides a dedicated endpoint optimized for Freqtrade bots:

    curl -s https://getregime.com/api/v1/freqtrade/regime \
      -H "Authorization: Bearer YOUR_API_KEY" | jq

    Response:

    {
      "regime": "bear",
      "confidence": 0.72,
      "action": "exit_or_hedge",
      "fear_greed": 31,
      "btc_price": 61240,
      "updated_at": "2026-06-08T14:00:00.000Z"
    }

    The action field maps directly to Freqtrade behavior:

    ActionRegimeWhat Your Bot Should Do
    full_positionBullNormal operation, full position sizes
    reduce_positionChopSmaller positions, skip marginal setups
    exit_or_hedgeBearCut risk, tighten stops, sit out longs

    This endpoint works with any tier: a free key returns a 15-minute-delayed read (enough to gate a bot trading majors); a Pro key makes it real-time. Grab a key (no card) at getregime.com and set it in your environment — never hard-code it:

    export REGIME_API_KEY="your_key_here"

    Step 1: Add the Regime Fetcher

    Add this to your strategy file. It fetches the current regime and caches it for 5 minutes to stay within rate limits:

    import os
    import requests
    import time
    from freqtrade.strategy import IStrategy, DecimalParameter
    from freqtrade.persistence import Trade
    
    class MyRegimeFilteredStrategy(IStrategy):
        # --- Regime filter settings ---
        regime_enabled = True
        regime_min_confidence = 0.6
        _regime_cache = None
        _regime_cache_time = 0
        _regime_cache_ttl = 300  # 5 minutes
    
        def get_regime(self) -> dict:
            """Fetch current market regime with caching."""
            now = time.time()
            if self._regime_cache and (now - self._regime_cache_time) < self._regime_cache_ttl:
                return self._regime_cache
    
            try:
                resp = requests.get(
                    'https://getregime.com/api/v1/freqtrade/regime',
                    headers={'Authorization': f"Bearer {os.environ['REGIME_API_KEY']}"},
                    timeout=5
                )
                resp.raise_for_status()
                self._regime_cache = resp.json()
                self._regime_cache_time = now
            except Exception as e:
                # On failure, return the last known regime so the bot doesn't freeze
                if self._regime_cache:
                    return self._regime_cache
                return {
                    'regime': 'unknown',
                    'confidence': 0.5,
                    'action': 'reduce_position'
                }
    
            return self._regime_cache

    Key design decisions:

    Step 2: Filter Entries by Regime

    Override confirm_trade_entry to check the regime before every trade:

        def confirm_trade_entry(
            self, pair: str, order_type: str, amount: float,
            rate: float, time_in_force: str, current_time, entry_tag, side, **kwargs
        ) -> bool:
            if not self.regime_enabled:
                return True
    
            regime = self.get_regime()
            action = regime.get('action', 'reduce_position')
            confidence = regime.get('confidence', 0.5)
    
            # Block all entries in a bear regime
            if action == 'exit_or_hedge':
                self.log(f"REGIME FILTER: Blocking entry on {pair} — "
                         f"regime={regime['regime']}, action=exit_or_hedge")
                return False
    
            # Block low-confidence entries
            if confidence < self.regime_min_confidence:
                self.log(f"REGIME FILTER: Blocking entry on {pair} — "
                         f"confidence={confidence:.0%} < {self.regime_min_confidence:.0%}")
                return False
    
            return True
    
        def log(self, msg):
            """Simple logger — replace with your logging setup."""
            print(f"[RegimeFilter] {msg}")

    That's it. Every entry signal your strategy generates now passes through a regime gate. In bear or low-confidence regimes, the bot sits on its hands instead of churning through losing trades.

    Step 3: Scale Position Size by Confidence

    For more sophisticated risk management, adjust position size based on regime confidence. Override custom_stake_amount:

        def custom_stake_amount(
            self, current_time, current_rate, proposed_stake,
            min_stake, max_stake, leverage, entry_tag, side, **kwargs
        ) -> float:
            if not self.regime_enabled:
                return proposed_stake
    
            regime = self.get_regime()
            confidence = regime.get('confidence', 0.5)
            action = regime.get('action', 'reduce_position')
    
            # Scale stake by confidence
            if action == 'full_position' and confidence >= 0.75:
                scale = 1.0        # Full size in high-confidence bull
            elif action == 'full_position':
                scale = 0.75       # Slightly reduced in moderate bull
            elif action == 'reduce_position':
                scale = 0.5        # Half size in chop
            else:
                scale = 0.25       # Quarter size (shouldn't reach here due to entry filter)
    
            adjusted = proposed_stake * scale
            adjusted = max(adjusted, min_stake)
            adjusted = min(adjusted, max_stake)
    
            self.log(f"REGIME SIZE: stake {proposed_stake:.4f} -> "
                     f"{adjusted:.4f} (scale={scale}, regime={regime['regime']}, "
                     f"confidence={confidence:.0%})")
    
            return adjusted

    This creates a smooth risk curve: full size in confident bull markets, half size in chop, quarter size in uncertain conditions. Your bot's equity curve becomes dramatically smoother.

    Step 4: Add Regime-Aware Exit Logic (Optional)

    You can also use the regime to manage exits. For example, tighten trailing stops when the regime shifts bearish:

        def custom_stoploss(
            self, pair: str, trade: Trade, current_time,
            current_rate: float, current_profit: float, after_fill, **kwargs
        ) -> float:
            regime = self.get_regime()
            action = regime.get('action', 'full_position')
    
            if action == 'exit_or_hedge' and current_profit > 0.02:
                # In bear regime with profit, tighten stop to lock gains
                return -0.03  # 3% trailing stop
            elif action == 'full_position' and current_profit > 0.05:
                # In bull regime with good profit, give room to run
                return -0.08  # 8% trailing stop
    
            return -0.10  # Default 10% stop

    In bear markets, you lock in profits quickly. In bull markets, you let winners run. Simple logic, massive impact on returns.

    Get the complete strategy file

    Rather than copy snippets, take the full, runs-out-of-the-box version. Download the free Regime Filter Freqtrade strategy — a complete RegimeFilterStrategy you drop into user_data/strategies/, with the cache, the fail-mode switch, the confidence gate, and a placeholder entry signal you replace with your own. Inherit from it and your existing strategy keeps the regime gate for free.

    Pro Tier: Webhook Alerts on Regime Transitions

    The endpoint above is enough for filtering. But if you want to be notified the instant the regime changes — so you can manually review positions or trigger automation — the Pro tier ($49/mo) adds webhook support:

    # Register a webhook for regime transitions
    curl -X POST https://getregime.com/api/v1/webhooks \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://your-server.com/regime-change", "event": "regime_transition"}'

    You'll receive a POST whenever the regime shifts (e.g., bull to chop, chop to bear). This is useful for triggering Freqtrade's /forcesell command via its REST API when the market turns hostile.

    FAQ

    Where do I call the regime API in Freqtrade?

    In confirm_trade_entry, not in populate_indicators or populate_entry_trend. Those are vectorized over history and must stay pure and fast; confirm_trade_entry runs once at trade time, which is the right place for an external call.

    Does the endpoint need an API key?

    Yes — pass Authorization: Bearer YOUR_KEY. A free key works (15-minute-delayed read), and a Pro key makes it real-time. Get one with no card at getregime.com.

    Won't this use up my API rate limit?

    No, if you cache. One regime read covers every pair for the cache window (default 5 minutes), so a busy bot makes only a few calls per hour — well within the free tier.

    What happens if the Regime API is down?

    The fetcher returns the last known regime, or a conservative reduce_position default if there's no cache yet — so the bot degrades gracefully instead of freezing.

    Does the filter place trades for me?

    No. It only blocks entries that don't match the regimes you allow. Your own entry logic still decides what to trade. It's a filter, not a signal generator — and it's informational, not financial advice.


    Get the free strategy file and a Regime API key at getregime.com/freqtrade-strategy. Informational tool, not financial advice; regime classification is probabilistic, not a guarantee.