DEV Community

Gunnar Thorderson
Gunnar Thorderson

Posted on • Originally published at getregime.com

How to Size Crypto Trading Bot Positions by Market Regime

How to Size Crypto Trading Bot Positions by Market Regime

Position sizing is the most underrated factor in trading bot profitability. Most traders spend 90% of their time optimizing entries and exits, and 10% on sizing. The profitable ones do the opposite.

The core insight: a mediocre strategy with excellent position sizing will outperform an excellent strategy with uniform sizing. And the single best input for dynamic sizing is market regime.

This guide walks through practical implementations you can add to your bot today.

Why Static Position Sizing Fails

Consider a bot that allocates 10% of capital per trade, regardless of conditions:

Regime Win Rate Avg Win Avg Loss 10 Trades @ 10%
Bull 65% +4.2% -2.1% +18.9%
Chop 48% +2.8% -2.5% -1.4%
Bear 35% +3.1% -3.8% -14.7%

Same bot, same sizing, wildly different outcomes. The bot makes money in bull markets and gives it all back (and more) in bear markets. This is the #1 reason trading bots fail in production.

Regime-based sizing solves this by scaling position sizes to match the current market environment.

The Regime Sizing Framework

Step 1: Get the Current Regime

import requests

REGIME_API = "https://getregime.com/api/v1/market/regime"

def get_regime():
    """Fetch current market regime and confidence."""
    resp = requests.get(REGIME_API, headers={
        "Authorization": "Bearer YOUR_API_KEY"
    })
    data = resp.json()
    return data["regime"], data["confidence"]
Enter fullscreen mode Exit fullscreen mode

Step 2: Define Regime Multipliers

The simplest approach — a lookup table:

REGIME_MULTIPLIERS = {
    "bull":  1.0,   # Full size in bull markets
    "chop":  0.5,   # Half size in chop
    "bear":  0.25,  # Quarter size in bear
}

def regime_size(base_size: float, regime: str) -> float:
    return base_size * REGIME_MULTIPLIERS.get(regime, 0.5)
Enter fullscreen mode Exit fullscreen mode

This alone improves risk-adjusted returns by 20-40% in backtests. But we can do better.

Step 3: Confidence-Weighted Sizing

A 90% confidence bull regime is very different from a 55% confidence bull. Use confidence to interpolate:

def confidence_weighted_size(
    base_size: float,
    regime: str,
    confidence: float
) -> float:
    """Scale position size by regime AND confidence."""
    multiplier = REGIME_MULTIPLIERS.get(regime, 0.5)

    # In bull: higher confidence = larger size
    # In bear: higher confidence = SMALLER size (more confident it's bad)
    if regime == "bull":
        # Scale from 0.6x (low conf) to 1.0x (high conf)
        multiplier *= 0.6 + (0.4 * confidence)
    elif regime == "bear":
        # Scale from 0.25x (high conf) to 0.5x (low conf)
        multiplier *= 0.5 - (0.25 * confidence)
    else:
        # Chop: reduce more with higher confidence
        multiplier *= 0.7 - (0.2 * confidence)

    return base_size * multiplier
Enter fullscreen mode Exit fullscreen mode

Step 4: Combine with Volatility Scaling

Regime tells you the market direction; volatility tells you the market intensity. Combining both gives the best results:

import numpy as np

def vol_regime_size(
    base_size: float,
    regime: str,
    confidence: float,
    recent_returns: list[float],
    target_vol: float = 0.02  # 2% daily target
) -> float:
    """Position sizing with regime + volatility scaling."""

    # Regime multiplier
    regime_mult = confidence_weighted_size(1.0, regime, confidence)

    # Volatility scalar (inverse vol targeting)
    realized_vol = np.std(recent_returns) if recent_returns else target_vol
    vol_scalar = min(target_vol / max(realized_vol, 0.001), 2.0)

    # Combined: regime direction + vol intensity
    final_size = base_size * regime_mult * vol_scalar

    # Hard caps
    return min(final_size, base_size * 1.5)  # Never exceed 1.5x base
Enter fullscreen mode Exit fullscreen mode

Advanced: Kelly Criterion with Regime

The Kelly criterion calculates the mathematically optimal bet size given your edge and odds. When combined with regime detection, it becomes extremely powerful:

def kelly_regime_size(
    bankroll: float,
    win_rate: float,
    avg_win: float,
    avg_loss: float,
    regime: str,
    confidence: float,
    max_kelly_fraction: float = 0.25  # Quarter-Kelly for safety
) -> float:
    """Kelly criterion with regime adjustment."""

    # Standard Kelly calculation
    if avg_loss == 0:
        return 0

    b = avg_win / abs(avg_loss)  # Win/loss ratio
    p = win_rate
    q = 1 - p

    kelly = (b * p - q) / b

    if kelly <= 0:
        return 0  # No edge, don't bet

    # Apply fractional Kelly for safety
    kelly *= max_kelly_fraction

    # Regime adjustment on top of Kelly
    regime_mult = {
        "bull":  1.0,
        "chop":  0.6,
        "bear":  0.3,
    }.get(regime, 0.5)

    # Confidence modulation
    regime_mult *= (0.5 + 0.5 * confidence)

    final_fraction = kelly * regime_mult
    return bankroll * min(final_fraction, 0.05)  # Hard cap at 5% of bankroll
Enter fullscreen mode Exit fullscreen mode

Full Integration Example

Here's a complete position sizing module that pulls live regime data and computes optimal sizes:

import requests
import numpy as np
from dataclasses import dataclass

@dataclass
class SizingResult:
    raw_size_usd: float
    regime: str
    confidence: float
    regime_multiplier: float
    vol_scalar: float
    final_size_usd: float
    reason: str

class RegimeSizer:
    """Production position sizer with regime awareness."""

    def __init__(self, api_key: str, base_pct: float = 0.10):
        self.api_key = api_key
        self.base_pct = base_pct  # 10% of bankroll per trade
        self.api_url = "https://getregime.com/api/v1"

    def get_market_context(self):
        headers = {"Authorization": f"Bearer {self.api_key}"}
        regime = requests.get(
            f"{self.api_url}/market/regime", headers=headers
        ).json()
        overview = requests.get(
            f"{self.api_url}/market/overview", headers=headers
        ).json()
        return regime, overview

    def compute_size(
        self,
        bankroll: float,
        symbol: str,
        side: str,  # "long" or "short"
        recent_returns: list[float] = None
    ) -> SizingResult:
        regime_data, overview = self.get_market_context()

        regime = regime_data["regime"]
        confidence = regime_data["confidence"]

        # Base size
        raw_size = bankroll * self.base_pct

        # Regime multiplier
        base_mult = {"bull": 1.0, "chop": 0.5, "bear": 0.25}
        r_mult = base_mult.get(regime, 0.5)

        # Penalize counter-regime trades
        if side == "long" and regime == "bear":
            r_mult *= 0.5  # Extra penalty for longs in bear
        elif side == "short" and regime == "bull":
            r_mult *= 0.5  # Extra penalty for shorts in bull

        # Confidence scaling
        r_mult *= (0.6 + 0.4 * confidence)

        # Volatility scaling
        vol_scalar = 1.0
        if recent_returns:
            realized = np.std(recent_returns)
            target = 0.02
            vol_scalar = min(target / max(realized, 0.001), 2.0)

        final = raw_size * r_mult * vol_scalar
        final = min(final, bankroll * 0.15)  # Never risk >15%
        final = max(final, 0)

        reason = (
            f"{regime}@{confidence:.0%} -> "
            f"{r_mult:.2f}x regime, {vol_scalar:.2f}x vol"
        )

        return SizingResult(
            raw_size_usd=raw_size,
            regime=regime,
            confidence=confidence,
            regime_multiplier=r_mult,
            vol_scalar=vol_scalar,
            final_size_usd=round(final, 2),
            reason=reason
        )

# Usage
sizer = RegimeSizer(api_key="rk_your_key_here")
result = sizer.compute_size(
    bankroll=10000,
    symbol="BTC",
    side="long"
)
print(f"Size: ${result.final_size_usd} ({result.reason})")
# Size: $742.50 (bear@73% -> 0.30x regime, 0.95x vol)
Enter fullscreen mode Exit fullscreen mode

Regime Sizing Rules of Thumb

Based on backtesting across 3+ years of crypto data:

Rule Rationale
Cut bear size by 75% Bear markets have 2-3x more false signals
Cut chop size by 50% Range-bound markets whipsaw trend strategies
Never exceed 1.5x base in bull Euphoria leads to overextension
Reduce by extra 50% for counter-regime trades Shorting in bull or longing in bear needs extra caution
Use confidence > 0.7 as "high conviction" Below 0.7, regime may be transitioning
Minimum size = 0.1x base Stay in the market to capture reversals

Drawdown-Based Scaling

Add a drawdown circuit breaker on top of regime sizing:

def drawdown_adjusted_size(
    base_size: float,
    current_equity: float,
    peak_equity: float
) -> float:
    """Quadratic drawdown scaling — cut faster as losses mount."""
    drawdown = 1 - (current_equity / peak_equity)

    if drawdown <= 0.05:
        return base_size  # No adjustment under 5% DD

    # Quadratic curve: 10% DD -> 0.75x, 20% DD -> 0.25x
    scale = max(1 - (drawdown * drawdown * 25), 0.1)
    return base_size * scale
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

  1. Using regime as a binary on/off switch — Don't stop trading entirely in bear markets. Reduce size and adapt strategy, but stay active to catch the reversal.

  2. Not accounting for regime transition lag — Regime changes don't happen instantly. Use confidence as a gradient, not regime label as a binary.

  3. Over-fitting sizes to historical regimes — Keep your multipliers simple (1.0 / 0.5 / 0.25). Complex regime-specific sizing curves tend to overfit.

  4. Ignoring correlation across positions — If you have 5 long positions in a bull market, your effective position is 5x your intended size. Scale individual sizes down when holding multiple correlated positions.

Next Steps

  1. Get a free API key at getregime.com
  2. Read the quickstart at getregime.com/quickstart
  3. Install the SDK: npm install getregime or use the REST API directly
  4. Explore intelligence endpoints for crowd positioning, divergences, and regime history

Position sizing isn't glamorous, but it's the difference between a bot that compounds and one that blows up. Regime-based sizing gives you the edge.


Regime provides real-time market regime detection for crypto trading bots. Start free — 30 req/min, no credit card.


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)