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"]
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)
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
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
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
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)
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
Common Mistakes
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.
Not accounting for regime transition lag — Regime changes don't happen instantly. Use confidence as a gradient, not regime label as a binary.
Over-fitting sizes to historical regimes — Keep your multipliers simple (1.0 / 0.5 / 0.25). Complex regime-specific sizing curves tend to overfit.
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
- Get a free API key at getregime.com
- Read the quickstart at getregime.com/quickstart
-
Install the SDK:
npm install getregimeor use the REST API directly - 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.
Top comments (0)