Position sizing answers one question: how big can this trade be if I accept to lose a fixed share of my account when my stop is hit?
It is a small formula, and a good exercise in input validation and floating point. Here is a version in TypeScript.
Disclosure: I am the founder of copy.top. We publish a free position size calculator, and this post explains the math behind that kind of tool. The code below is a minimal version written for this article.
The formula
Four inputs:
-
account: the size of your account -
riskPct: the share of the account you accept to lose on one trade, in percent -
entry: your entry price -
stop: the price where you exit if you are wrong
Then:
riskAmount = account * riskPct / 100 lossPerCoin = |entry - stop| quantity = riskAmount / lossPerCoin notional = quantity * entry The idea is that the stop distance decides the size. A tight stop allows a bigger position, a wide stop forces a smaller one, and the loss at the stop stays the same.
The code
interface SizeInput { account: number; riskPct: number; entry: number; stop: number; } interface SizeResult { side: "long" | "short"; riskAmount: number; // what you lose if the stop is hit quantity: number; // position size in coins notional: number; // position size in quote currency leverageNeeded: number; // notional / account } export function positionSize(i: SizeInput): SizeResult { if (i.account <= 0 || i.entry <= 0 || i.stop <= 0) { throw new RangeError("account, entry and stop must be positive"); } if (i.riskPct <= 0 || i.riskPct > 100) { throw new RangeError("riskPct must be above 0 and at most 100"); } if (i.entry === i.stop) { throw new RangeError("entry and stop must differ"); } const riskAmount = i.account * (i.riskPct / 100); const lossPerCoin = Math.abs(i.entry - i.stop); const quantity = riskAmount / lossPerCoin; const notional = quantity * i.entry; return { side: i.stop < i.entry ? "long" : "short", riskAmount, quantity, notional, leverageNeeded: notional / i.account, }; } The side is inferred from the stop: a stop below the entry means a long, a stop above means a short. One less field for the user to get wrong.
A worked example
positionSize({ account: 1000, riskPct: 1, entry: 0.5, stop: 0.45 }); // side: "long" // riskAmount: 10 // quantity: 200.00000000000006 // notional: 100.00000000000003 // leverageNeeded: 0.10000000000000003 With a tighter stop at 0.49, the same risk gives about 1000 coins and a notional of about 500. Same loss at the stop, five times the size.
The floating point detail
0.5 - 0.45 is not exactly 0.05 in binary floating point, which is why the quantity is 200.00000000000006.
Do the math in full precision and round only for display:
const fmt = (n: number, digits = 2) => n.toLocaleString("en-US", { maximumFractionDigits: digits }); fmt(200.00000000000006); // "200" Do not round the intermediate values. Rounding lossPerCoin before dividing can change the result by a visible amount when the stop is very close to the entry.
What leverageNeeded tells you
If leverageNeeded is above 1, the position is larger than the account, so it can only be opened with leverage. That is worth showing in a UI: a tight stop can quietly ask for a lot of leverage.
What the formula cannot do
The loss is riskAmount only if you exit exactly at the stop. On thin markets the price can jump past it, and the real loss is larger. Fees and slippage add to it. Treat the result as the planned loss, not the maximum loss.
If you only need the numbers, the hosted version is here, free and with no sign up. Nothing here is financial advice.


