Four Core Sizing Methods
1. Fixed Lots
Always trade 1 lot regardless of capital. Simple but doesn't scale with account growth.
2. Fixed % Risk
Risk a fixed % of equity per trade. The most widely recommended approach for retail traders.
3. ATR-Based Sizing
Size based on current volatility. Reduces lots in choppy markets, increases in trending ones.
4. Kelly Criterion
Mathematically optimal fraction based on win rate and payoff ratio. Always use half-Kelly in practice.
PositionSizer Class
import math import pandas as pd class PositionSizer: def __init__(self, lot_size: int = 50, max_lots: int = 10): self.lot_size = lot_size self.max_lots = max_lots # ── Method 1: Fixed lots ────────────────────────────────── def fixed_lots(self, n: int = 1) -> int: return min(n, self.max_lots) # ── Method 2: Fixed % risk ──────────────────────────────── def fixed_pct_risk( self, equity : float, entry_price : float, stop_price : float, risk_pct : float = 1.0, # risk 1% of equity per trade ) -> int: """ lots = (equity × risk_pct%) / (|entry - stop| × lot_size) """ risk_per_lot = abs(entry_price - stop_price) * self.lot_size if risk_per_lot == 0: return 1 risk_rs = equity * (risk_pct / 100) lots = int(risk_rs / risk_per_lot) return max(1, min(lots, self.max_lots)) # ── Method 3: ATR-based sizing ──────────────────────────── def atr_based( self, equity : float, atr : float, risk_pct : float = 1.0, atr_multiplier: float = 1.5, # stop = entry ± 1.5×ATR ) -> int: stop_distance = atr * atr_multiplier risk_rs = equity * (risk_pct / 100) lots = int(risk_rs / (stop_distance * self.lot_size)) return max(1, min(lots, self.max_lots)) # ── Method 4: Kelly Criterion ───────────────────────────── def kelly( self, equity : float, win_rate : float, # 0.0–1.0 avg_win : float, # in points avg_loss : float, # in points (positive number) fraction : float = 0.5, # half-Kelly for safety ) -> int: """ Kelly fraction = W/L - (1-W)/G where W = win rate, L = avg loss, G = avg win """ if avg_loss == 0 or win_rate <= 0: return 1 payoff_ratio = avg_win / avg_loss kelly_f = win_rate - ((1 - win_rate) / payoff_ratio) kelly_f = max(0, kelly_f) * fraction # half-Kelly # Convert fraction to lots based on account size capital_to_risk = equity * kelly_f lots = int(capital_to_risk / (avg_loss * self.lot_size)) return max(1, min(lots, self.max_lots))
Understanding Half-Kelly
W = win rate · avg_win = average winning trade · avg_loss = average losing trade
# Strategy stats from last 100 trades: win_rate = 0.55 # 55% win rate avg_win = 80 # 80 pts avg winner avg_loss = 50 # 50 pts avg loser payoff = avg_win / avg_loss # = 1.6 kelly_f = win_rate - (1 - win_rate) / payoff # kelly_f = 0.55 - 0.45/1.6 = 0.55 - 0.28 = 0.27 (27%) half_kelly = kelly_f / 2 # = 0.135 (13.5% of equity) equity = 500_000 capital_at_risk = equity * half_kelly # = ₹67,500 lots = int(capital_at_risk / (avg_loss * 50)) # 50=lot_size print(f"Kelly lots: {lots}") # = 27 lots
Full Kelly maximises long-run growth mathematically but produces extreme drawdowns (30–50%) in the short term. Half-Kelly reduces growth by only ~13% but cuts volatility almost in half. Most professionals use quarter-Kelly for live systems.
Sizing Method Comparison
| Method | Complexity | Best for | Risk |
|---|---|---|---|
| Fixed Lots | Trivial | Getting started, testing logic | Doesn't adapt to account size |
| Fixed % Risk | Low | Most retail traders | Low — well-controlled |
| ATR-Based | Medium | Volatile instruments (Gold, BankNifty) | Medium — volatility can spike |
| Half-Kelly | Medium | Strategies with strong stat edge | Medium — needs reliable win-rate estimates |
