export interface PositionSize { shares: number; riskPerShare: number; dollarRisk: number; positionValue: number; /** Position value exceeds the account → needs margin / not affordable in cash. */ exceedsAccount: boolean; } /** * Risk-based position sizing. Risk a fixed % of the account per trade; the stop * distance sets how many shares that budget buys: * shares = floor((account × risk%) / |entry − stop|) * Returns null when inputs are unusable (no account, no risk, zero stop width). */ export function positionSize( accountSize: number, riskPct: number, entry: number, stop: number, ): PositionSize | null { const riskPerShare = Math.abs(entry - stop); if (!(accountSize > 0) || !(riskPct > 0) || !(riskPerShare > 0) || !(entry > 0)) { return null; } const budget = accountSize * (riskPct / 100); const shares = Math.floor(budget / riskPerShare); const dollarRisk = shares * riskPerShare; const positionValue = shares * entry; return { shares, riskPerShare, dollarRisk, positionValue, exceedsAccount: positionValue > accountSize, }; }