Position Sizing
Position sizing determines how much capital to allocate to each trade. Getting this right is often more important than entry timing.
How Quantity Works
The quantity parameter in ctx.order.market() represents a fraction of your total capital:
| Quantity | Meaning |
|---|---|
1 | 100% of capital (full position) |
0.5 | 50% of capital |
0.25 | 25% of capital |
-1 | Full short position |
-0.5 | 50% short position |
// Full allocation ctx.order.market('ASSET', 1, { signal: 'buy' }); // Half allocation ctx.order.market('ASSET', 0.5, { signal: 'buy' }); // Quarter allocation ctx.order.market('ASSET', 0.25, { signal: 'buy' });
Fixed Fractional Sizing
The simplest approach: risk a fixed percentage of capital on every trade.
function init(ctx) { ctx.addIndicator('atr', 'ATR', { period: 14 }); } function onBar(ctx, i) { const pos = ctx.position('ASSET'); if (pos.qty !== 0) return; const close = ctx.series.close[i]; const atr = ctx.ind.atr[i]; if (q.isNaN(atr)) return; // Risk 2% of capital per trade // Size inversely proportional to volatility const riskPerTrade = 0.02; const stopDistance = atr * 2; const rawSize = (riskPerTrade * close) / stopDistance; const size = q.clamp(rawSize, 0.1, 1); if (q.crossOver(ctx.ind.ema, ctx.series.close, i)) { ctx.order.market('ASSET', size, { signal: 'buy' }); } }
Volatility-Adjusted Sizing
Scale position size inversely with volatility. When the market is calm, take larger positions; when volatile, take smaller ones.
function onBar(ctx, i) { const atr = ctx.ind.atr[i]; const avgAtr = q.sma(ctx.ind.atr, 50, i); if (q.isNaN(avgAtr) || avgAtr === 0) return; // Normalize: if current ATR = average, size = 1 // Higher ATR = smaller size, lower ATR = larger size const volRatio = avgAtr / atr; const size = q.clamp(volRatio, 0.2, 1); if (shouldEnter) { ctx.order.market('ASSET', size, { signal: 'buy' }); } }
Scaling In and Out
Use ctx.order.reduce() to take partial profits, or multiple entries to scale in.
function onBar(ctx, i) { const pos = ctx.position('ASSET'); const close = ctx.series.close[i]; // Scale in: first entry at 50%, add 50% on confirmation if (pos.qty === 0 && initialSignal) { ctx.order.market('ASSET', 0.5, { signal: 'buy_initial' }); } // Add to position on confirmation if (pos.qty > 0 && pos.qty < 0.9 && confirmationSignal) { ctx.order.market('ASSET', 0.5, { signal: 'buy_add' }); } // Scale out: take 50% profit at first target if (pos.qty > 0 && close > pos.avgPrice * 1.05) { ctx.order.reduce('ASSET', 0.5, { signal: 'takeProfit1' }); } // Close remainder at second target if (pos.qty > 0 && close > pos.avgPrice * 1.10) { ctx.order.close('ASSET', { signal: 'takeProfit2' }); } }
Common Mistakes
Over-sizing: Using 1 (100%) for every trade leaves no room for drawdowns. Consider 25-50% as a starting point.
Ignoring volatility: A fixed size of 0.5 means very different risk levels in calm vs choppy markets. Use ATR to normalize.
Stacking entries: Always check pos.qty === 0 before initial entry, or intentionally manage scaling via quantity checks.
Related
- ctx.order.market() — Placing orders
- ctx.order.reduce() — Partial exits
- Risk Management — Stop-loss patterns