· 12 min read

Position Sizing in Pine Script: The Complete Guide

How to implement dynamic position sizing in TradingView strategies so your risk stays constant regardless of instrument price, account size, or market volatility.

Bhavin Javia
Bhavin Javia
Founder, BotJockie · 22+ yrs software · 5+ yrs algo dev

Most retail traders use fixed lot sizes. One lot of Nifty futures every trade, or 0.01 BTC every entry. This sounds disciplined but it's not — a fixed lot size means your risk per trade fluctuates with market volatility. When ATR is high, you're risking far more than when ATR is low. Dynamic position sizing solves this by making your risk per trade constant in monetary terms.

The Core Concept: Risk-Based Sizing

The principle is simple: decide what percentage of your account you're willing to lose on any single trade (typically 1–2%), then calculate how many units to buy based on the distance from entry to stop-loss.

The formula:

position_size = (account_size × risk_percent) / (entry_price − stop_price)

If your account is ₹5,00,000, you risk 1% per trade (₹5,000), and your stop is 50 points below entry — you buy 100 units. If the stop is 100 points below entry, you buy 50 units. Your maximum loss is always ₹5,000 regardless of how wide the stop is.

Implementing in Pine Script v6

Pine Script's strategy.entry() accepts a qty parameter. We calculate this dynamically on each signal:

//@version=6
strategy("Risk-Based Position Sizing", 
    default_qty_type=strategy.fixed,
    initial_capital=500000,
    commission_type=strategy.commission.percent,
    commission_value=0.05)

// --- Inputs ---
risk_pct    = input.float(1.0, "Risk per Trade (%)", minval=0.1, maxval=5.0, step=0.1) / 100
atr_period  = input.int(14, "ATR Period")
atr_mult    = input.float(2.0, "Stop ATR Multiplier")

// --- ATR-based stop distance ---
atr_val     = ta.atr(atr_period)
stop_dist   = atr_val * atr_mult

// --- Dynamic position size ---
account_eq  = strategy.equity
risk_amount = account_eq * risk_pct
qty_calc    = math.floor(risk_amount / stop_dist)
qty_safe    = math.max(qty_calc, 1)

// --- Signal (replace with your logic) ---
fast_ma = ta.ema(close, 20)
slow_ma = ta.ema(close, 50)
long_signal  = ta.crossover(fast_ma, slow_ma)
short_signal = ta.crossunder(fast_ma, slow_ma)

// --- Entries ---
stop_long  = close - stop_dist
stop_short = close + stop_dist

if long_signal
    strategy.entry("Long", strategy.long, qty=qty_safe)
    strategy.exit("Long Exit", "Long", stop=stop_long)

if short_signal
    strategy.entry("Short", strategy.short, qty=qty_safe)
    strategy.exit("Short Exit", "Short", stop=stop_short)

Key Implementation Details

Use strategy.equity, not initial_capital

Always calculate your risk amount from strategy.equity (current account value), not strategy.initial_capital. This ensures position sizes scale with your account — growing as you profit, shrinking as you draw down. This is called "fixed fractional" position sizing and it's the standard approach for serious algo traders.

ATR vs. Fixed Pip/Point Stops

Using ATR to set your stop distance is superior to fixed-point stops because it adapts to market volatility. A 50-point stop on Nifty during a low-volatility period is very different from a 50-point stop during a high-volatility event. ATR normalises this — your stops are always proportional to current market conditions.

NSE-Specific Note

For NSE futures, your position size must be a multiple of the lot size. Nifty 50 futures have a lot size of 25. Wrap your qty_safe calculation in math.floor(qty_safe / lot_size) * lot_size to ensure you're always trading whole lots.

Adding a Daily Loss Limit

Position sizing alone doesn't protect you from a bad day where you take 10 consecutive losses. Add a daily loss limit to halt trading if the account drops beyond a threshold in a single session:

// Input at top of script (with other inputs)
max_daily_loss  = input.float(-3.0, "Max Daily Loss (%)", step=0.5)

// Daily loss limit guard
var float day_start_equity = na
is_new_day = ta.change(time("D")) != 0

if is_new_day or na(day_start_equity)
    day_start_equity := strategy.equity

daily_loss_pct  = (strategy.equity - day_start_equity) / day_start_equity * 100
trading_halted  = daily_loss_pct <= max_daily_loss

// Only enter if not halted
if long_signal and not trading_halted
    strategy.entry("Long", strategy.long, qty=qty_safe)

Volatility-Adjusted Sizing (Advanced)

A further refinement is to reduce position size not just based on stop distance but also based on overall market volatility. During high-volatility regimes, even a wide ATR stop might be hit by noise. You can add a volatility scalar:

// Normalised volatility scalar
hist_atr_avg = ta.sma(atr_val, 100)
vol_scalar   = math.min(hist_atr_avg / atr_val, 1.0)  // never exceed standard size

qty_adj      = math.floor(qty_safe * vol_scalar)
qty_final    = math.max(qty_adj, 1)

This reduces position size when current ATR is higher than its 100-bar average — i.e., when the market is more volatile than normal. It's a simple but effective regime filter.

Download the Complete Risk Framework

The BotJockie free Algo Toolkit includes a production-ready Pine Script v6 risk management module with position sizing, daily loss limits, and volatility adjustment — ready to drop into any strategy.

Free Download

Risk Management Framework (.pine)

Production-ready Pine Script v6 risk module. Free, no card required.

Download Free Toolkit →