forex-position-size
v0.1.0
Published
Forex position size and pip value for any pair and account currency: yen pairs, crosses, gold and silver, indices, lot steps, commission, and prop-firm daily loss headroom. No dependencies.
Maintainers
Readme
Position size calculator: npm library, embeddable widget, MT5 script
How many lots risk the amount you chose, for the stop you chose. The same sum in three forms:
| Form | For | Where |
| --- | --- | --- |
| forex-position-size | Developers: pip value and lot size for any pair and account currency | npm |
| The widget | Websites and blogs: a position size calculator in one line of HTML | widget/ |
| PositionSizeFromLine.mq5 | MetaTrader 5: size a trade from a stop line you drag on the chart | mt5/ |
None of them needs an account, a key or a network call. You type the prices; nothing you type leaves the page or the terminal.
The sum:
pip value per lot = pip size x contract size, turned into the account's currency
lots = money at risk / (stop in pips x pip value per lot + commission per lot)rounded down to the lot step, so the trade never risks more than you set. A position size is arithmetic, not advice: it does not say where a stop belongs or how much to risk, and a stop can fill past its price.
For the working in full, with yen pairs, crosses and gold, read Forexia's guide to the forex lot size calculator. More tools: Forexia trading tools.
The library
npm install forex-position-sizeES module with TypeScript types, no dependencies, Node 18+ and every browser.
import { positionSize } from 'forex-position-size';
positionSize({ balance: 10000, riskPct: 1, stopPips: 20, pair: 'EURUSD' });
// { lots: 0.5, units: 50000, risk: 100, riskPct: 1, pipValuePerLot: 10, stopPips: 20, warnings: [], ... }Worked examples
Each of these is a test in test/index.test.js, worked by hand first.
| Trade | Working | Lots | | --- | --- | --- | | EURUSD, USD account, $10,000, 1%, 20 pips | $100 / (20 x $10) | 0.50 | | USDJPY at 150, USD account, $10,000, 1%, 30 pips | pip = 1,000 JPY / 150 = $6.67; $100 / (30 x $6.67) | 0.50 | | EURGBP, USD account, GBPUSD 1.27, $5,000, 2%, 25 pips | pip = 10 GBP x 1.27 = $12.70; $100 / $317.50 = 0.315 | 0.31 (risks $98.43) | | GBPJPY, EUR account, EURJPY 160, EUR 20,000, 0.5%, 40 pips | pip = 1,000 JPY / 160 = EUR 6.25; EUR 100 / EUR 250 | 0.40 | | EURUSD, EUR account, entry 1.0800, stop 1.0750 | 50 pips; pip = $10 / 1.08 = EUR 9.26; EUR 100 / EUR 462.96 = 0.216 | 0.21 | | XAUUSD, entry 2400, stop 2390, $10,000, 1% | $10 x 100 oz = $1,000 a lot; $100 / $1,000 | 0.10 (10 oz) | | XAGUSD, entry 30.00, stop 29.50, $10,000, 2% | $0.50 x 5,000 oz = $2,500 a lot; $200 / $2,500 | 0.08 (400 oz) | | EURUSD, 20 pips, $7 commission a lot, $100 risk | $100 / ($200 + $7) = 0.483 | 0.48 | | US30 (index), $1 a point a lot, 100 points, $1,000 risk | $1,000 / $100 | 10 |
Which prices it needs
A pip is worth a fixed amount in the pair's quote currency (the second one). To turn it into the account's currency the sum needs one price:
| Pair and account | Price needed | Where it comes from |
| --- | --- | --- |
| Quoted in the account's currency (EURUSD on USD) | none | |
| The account's currency first (USDJPY on USD) | the pair's own price | entry, or price |
| A cross (EURGBP on USD) | the quote currency against the account's (GBPUSD) | rates |
rates takes prices keyed by pair, in either direction ({ GBPUSD: 1.27 } or { 'USD/GBP': 0.787 }), and goes through USD or EUR when there is no direct price. rateNeeded(pair, accountCurrency) tells you which price to ask your user for.
API
| Function | Returns |
| --- | --- |
| positionSize(input) | { lots, units, rawLots, risk, riskPct, targetRisk, stopPips, stopDistance, direction, pipValuePerLot, riskPerLot, pipSize, contractSize, lotStep, warnings } |
| pipValue({ pair, accountCurrency, rates, price, lots }) | { perLot, perLotInQuote, value, pipSize, contractSize, quoteToAccount } |
| rateNeeded(pair, accountCurrency) | { pair: 'GBPUSD', reason: 'conversion' \| 'own-price' } or null |
| conversionRate(from, to, rates) | units of to per unit of from, or null |
| roundLots(raw, { lotStep, minLot, maxLot }) | { lots, below, capped } |
| dailyLossHeadroom(input) | { headroom, dailyFloor, overallFloor, floor, binding, breached, headroomPct } |
| positionSizeWithinLimits(sizeInput, headroomInput) | positionSize, with the risk cut to the headroom |
| parsePair, marketPair, pipSizeOf, contractSizeOf | helpers |
positionSize input: balance; riskPct or riskAmount (wins); stopPips, or entry and stop; pair; accountCurrency (default USD); rates; price; and, for your broker, lotStep (0.01), minLot, maxLot, commissionPerLot (round turn, in the account's currency), pipSize, contractSize. For an index or any symbol that is not a currency pair, pass quoteCurrency, pipSize (the move you count, such as 1 point) and contractSize (money per point on one lot).
Bad input throws a PositionSizeError with a code ('balance', 'risk', 'stop', 'rate', ...). Soft problems come back in warnings: a size below the smallest lot (then lots is 0 and the warning says what the smallest lot would risk), a size capped at maxLot, a stop under one pip, a risk above 10% of the balance, a metal sized from pips.
Conventions
- A pip is 0.01 on JPY and HUF pairs, 0.0001 on other currency pairs.
- Gold counts $1.00 as one pip, as Forexia's charts and lot size calculator do. Brokers differ: some call $0.10 or $0.01 a pip. The lots come out the same if the stop is the same distance in price, so for metals give
entryandstop, or setpipSize. - Silver counts $0.01 as one pip. A lot is 100,000 units of a currency, 100 oz of gold, 5,000 oz of silver. Check your broker's contract size and set
contractSizeif it differs.
Prop firm daily loss limits
dailyLossHeadroom says how much more an account can lose today before it breaks its daily loss limit, or its overall limit if that is closer:
import { dailyLossHeadroom, positionSizeWithinLimits } from 'forex-position-size';
// $100,000 account, 5% daily and 10% overall limits, the day began at $101,000, equity now $99,500
dailyLossHeadroom({ initialBalance: 100000, dayStart: 101000, equity: 99500, dailyLossPct: 5, maxLossPct: 10 });
// { dailyFloor: 96000, overallFloor: 90000, floor: 96000, binding: 'daily', headroom: 3500, breached: false, ... }
// Size a 1% trade, cut to what the account can still lose
positionSizeWithinLimits(
{ balance: 100000, riskPct: 1, stopPips: 20, pair: 'EURUSD' },
{ initialBalance: 100000, dayStart: 101000, equity: 96600, dailyLossPct: 5 }
); // lots: 3 (risk cut from $1,000 to $600)Firms count the daily limit differently: of the starting balance or the day's, from the day-start balance or the higher of balance and equity, at a server time of their own. Pass the figures your firm's rules name (dayStart, dailyLossBase, dailyLossAmount, overallFloor for a trailing drawdown you track, openRisk for trades already open). To evaluate a whole challenge (daily and overall loss, trailing drawdown, trading days, consistency, the news rule) against an account's history, use PropSim's prop-firm-rules, which documents each firm's basis.
The widget
A position size calculator for any website: one HTML file with its styles and the library inline, no cookie, no storage, no request. The reader types every price, so it works offline and needs no data licence.
<div class="forexia-position-size" data-pair="EURUSD" data-currency="USD"><a href="https://forexiapro.com/tradingtools?via=position-size">Forexia</a></div>
<script async src="https://forexiapro.github.io/position-size/embed.js"></script>embed.js turns each .forexia-position-size into a frame of index.html and keeps its height right as the reader opens its settings. Every data-* attribute is passed on:
| Option | Values |
| --- | --- |
| pair | EURUSD, XAUUSD, ... |
| currency | The account's currency: USD, EUR, GBP, ... |
| balance, risk | Starting figures (risk in percent) |
| stop | prices (entry and stop, the default) or pips |
| theme | light, dark, auto |
| accent | A hex colour without #, such as f5a524 |
| flat | 1 for a transparent background |
| border | 0 to drop the frame's border |
Or link the frame yourself: <iframe src="https://forexiapro.github.io/position-size/?pair=GBPJPY¤cy=EUR&theme=dark" ...>.
The calculator's credit link says "Forexia", and only that, on purpose: a keyword-stuffed link inside a widget is the kind search engines treat as spam. It goes to Forexia's trading tools.
Build. widget/index.html is built from widget/template.html and src/index.js:
npm run build:widget # writes widget/index.html
node scripts/build-widget.mjs --check # fails if it is out of date (CI runs this)The MT5 script
mt5/PositionSizeFromLine.mq5 sizes a trade from a horizontal line you drag.
- Copy it to
MQL5/Scripts/(File → Open Data Folder) and compile it in MetaEditor, or download it from the MQL5 Code Base. - Drag it onto a chart. The first time, it draws a red dashed line,
PS_Stop. Drag it to your stop. - Run it again. It writes the lots, the money at risk and the working on the chart and in the Experts log.
- Entry: the current price (Ask for a buy, Bid for a sell; a stop below the price is a buy), or a horizontal line named
PS_Entryfor a pending order (limit or stop, chosen from where the line sits). A line namedPS_Targetadds the take profit and shows the reward in R. - The sum: stop distance /
SYMBOL_TRADE_TICK_SIZExSYMBOL_TRADE_TICK_VALUE_LOSS(falling back toSYMBOL_TRADE_TICK_VALUE), plus your commission per lot; rounded down toSYMBOL_VOLUME_STEPand kept withinSYMBOL_VOLUME_MIN,SYMBOL_VOLUME_MAXandSYMBOL_VOLUME_LIMIT. It works on any symbol the broker gives a tick value for: currencies, metals, indices, crypto. It cross-checks againstOrderCalcProfitand logs a gap over 1%, and warns when the margin is more than the free margin or the stop is inside the broker's stops level. - Inputs: risk percent of balance or equity, or a fixed amount; commission per lot; the three line names;
PlaceOrder(off by default) andConfirm(asks before sending); magic number, slippage, comment.
With PlaceOrder on, it asks, then sends the order with the stop (and target) attached. Try it on a demo account first.
Develop
npm test # node:test, no dependencies
npm run build:widgetLicence
MIT. See LICENSE.
Part of the Forexia network: InterestRateDecision.com · Forexia Academy · TradingSignalBot · PropSim · OpenCharts
