A canonical, well-specified, cross-language (Python + TypeScript) reference implementation of the price-weighted index. Add up the prices, divide by the divisor — and discover that the weights belong to whoever cut their equity into the fewest pieces. This module computes the index in exact decimal arithmetic, then shows what the three published numbers hide: the weights nobody chose, the re-weighting a share split performs for free, and who actually moved the level.
📖 Full article (canonical): Price-Weighted Index — The Fintech Builder
This repository is the runnable, production-oriented companion to that article. The article teaches the concept; this repo is the code you install and build on.
🧭 Browse all algorithms: Awesome FinTech Algorithms — the full index of the library.
🗂️ This algorithm's domain: Index and Benchmark Engineering › Weighting and Capping
📥 Just want to call it? It also ships in the fintech-algorithms npm package — see Two ways to use this.
| Catalog topic | D03-F02-A01 |
| Domain | D03 — Index and Benchmark Engineering |
| Family | D03-F02 — Weighting and Capping |
| Difficulty | 2 / 5 |
| Languages | Python, TypeScript |
| Opens | the D03-F02 Weighting and Capping family |
- The weights nobody chose
- What a split does to the index
- What the reference got wrong
- Two ways to use this
- Install
- Quickstart
- Views: the analysis surface
- Input shape
- API reference
- Edge cases & limitations
- Testing
- Related algorithms
- License
price sum = sum(prices)
level = price sum / divisor
weight_i = price_i / price sum
ALFA price 50 weight 0.2 points 200
BETA price 75 weight 0.3 points 300
GAMMA price 25 weight 0.1 points 100
DELTA price 100 weight 0.4 points 400
most expensive share: DELTA at 0.400000 priciest over cheapest: 4.000000000
Nothing here knows what any of these companies are worth. DELTA is 40% of the index because its shares are priced at 100, and a company worth ten times as much with a 10 share price would be 4%.
A 2-for-1 split creates no value and destroys none, so the level must not move. The divisor is rescaled by the new price sum over the old one — and the weights quietly change anyway:
ALFA 50 -> 50 weight 0.2 -> 0.25 change 0.05
BETA 75 -> 75 weight 0.3 -> 0.375 change 0.075
GAMMA 25 -> 25 weight 0.1 -> 0.125 change 0.025
DELTA 100 -> 50 weight 0.4 -> 0.25 change -0.15
divisor 0.25 -> 0.2, level 1000 -> 1000 (unchanged: true)
DELTA keeps 0.625000000000 of its weight; 0.15 moved to the others
levelUnchanged is checked exactly, not within a tolerance. Fifteen percent of the index changed hands
because a company decided to print more certificates.
All confirmed by running it:
- NaN passed.
float("nan") <= 0is false, so a NaN price came back as a NaN price sum and NaN weights — not even valid JSON. Only finite JSON numbers are accepted here. - Coercion. Strings and booleans were converted silently.
- Float arithmetic. The reference summed binary prices and divided; here every number is read as the decimal it spells, arithmetic runs in exact fractions, and rounding happens once, at publication, to six decimals, half away from zero.
This repo is the production home: the full implementation, the analysis surface below, and 140 tests across two languages.
The fintech-algorithms npm package
ships the same topic as one import among several hundred.
fintech-algorithms/index-and-benchmark-engineering/weighting-and-capping/price-weighted-index
Python
cd python
pip install -e ".[dev]"TypeScript
cd typescript
npm install
npm run buildfrom fintech_price_weighted import calculate, split_impact
calculate({"prices": [50, 75, 25, 100], "divisor": 0.25})
# {'priceSum': 250, 'divisor': 0.25, 'indexLevel': 1000, 'weights': [0.2, 0.3, 0.1, 0.4]}
split_impact({"prices": [50, 75, 25, 100], "divisor": 0.25, "ids": ["ALFA", "BETA", "GAMMA", "DELTA"]},
"DELTA", 2)["divisorAfter"]
# 0.2TypeScript is the same call:
import { calculate, splitImpact } from 'fintech-price-weighted';
calculate({ prices: [50, 75, 25, 100], divisor: 0.25 }).indexLevel; // 1000Run the tour in either language:
cd python && python examples/quickstart.py
cd typescript && npm run exampleBoth print byte-identical output.
Each member's price, weight, exact weight and points of the level. The exact weights sum to 1 by construction; the published ones need not:
publishedWeightSum 0.999999
publishedWeightResidual -0.000001
weightsSumToOneExactly true
Three equal members publish 0.333333 three times. That is not an error to be forced away — it is what
six decimals can say — so it is reported.
Shown above: the new divisor, every member's weight before and after, the ratio the split member keeps, and the weight that moved to everyone else. Works on a reverse split too, and a ratio of 1 is a no-op.
BIG 100 -> 101 points 1 move 0.010000000
SMALL 10 -> 10.6 points 0.6 move 0.060000000
biggest contributor BIG, biggest mover SMALL, agree: false
Every member contributes (price after − price before) / divisor, so the index cannot tell the
difference between a $1 move on a $100 share and a $1 move on a $10 one. pointsAndPercentAgree says
when the headline mover is not the one that actually moved the index.
ok thePriceSumIsTheSumOfPrices
FAIL theDivisorIsEchoed
ok theLevelIsPriceSumOverDivisor
ok everyWeightIsItsPriceShare
Pass a second argument to audit somebody else's numbers. The publication residual is reported in the body, never as a failure.
ids is an addition to the positional contract: it names members in the views and changes no published
number. split_impact accepts an id or a position.
Python — from fintech_price_weighted import ...
| function | returns |
|---|---|
calculate(data) / price_weighted_index(data) |
priceSum, divisor, indexLevel, weights |
weight_table(data) |
per-member weight, exact weight, points, and the publication residual |
split_impact(data, target, ratio) |
the rescaled divisor and every weight before and after |
price_move_attribution(data, new_prices) |
points and percent per member, exact sum check |
verify_price_weighted(data, result=None) |
four checks; pass result to audit a supplied answer |
validate_request · validate_ids |
the validation steps, exposed |
to_fraction · render · number · trim · scaled |
exact-arithmetic helpers |
TypeScript — import { ... } from 'fintech-price-weighted'
The same functions in camelCase (priceWeightedIndex, weightTable, splitImpact, ...).
- The divisor is yours to carry. This module never invents one: a split changes the divisor, and
split_impacttells you what to change it to. - Weights are prices, not value. That is the design, not a defect; if you want value weighting, the sibling repos below do exactly that.
price_move_attributionassumes no membership change between the two price vectors — it aligns positionally, and a changed basket needs a divisor adjustment first.- Published weights are six decimals and need not sum to 1; the residual is reported.
- Large published numbers are doubles. Above ~9e9 a six-decimal value is finer than a double can hold; the number is the nearest double to the correctly rounded decimal.
cd python && pytest -q # 71 tests
cd typescript && npm test # 69 testsBoth suites reproduce the canonical fixture byte for byte.
The two implementations were compared directly across 1,500 scenarios and 7,500 calls — the index and all four surfaces, valid and malformed — and their canonical JSON output is byte-identical (3.1 MB). The examples are byte-identical too.
The Python port was differentially tested against the reference engine on 12,000 generated baskets
with zero unexplained divergences. An independent Decimal oracle confirmed all 9,374 results the
port returned. Every divergence is classified by name:
| divergence | cases | what happened |
|---|---|---|
| coercion | 543 | reference accepted strings and booleans |
| rounding rule | 329 | reference summed binary floats; port rounds the exact decimal half away from zero |
| non-finite | 303 | reference accepted NaN or infinity and published NaN weights |
| ids validation | 294 | the optional ids array was malformed (the reference ignores it) |
| double resolution | 104 | both correct to the decimal; neighbouring doubles above ~9e9 |
Same family — D03-F02 Weighting and Capping
- Total-Market-Cap Index — weight by what each whole company is worth, and measure the concentration that brings.
- Free-Float Market-Cap Index — weight by the part of each company that actually trades.
- Capped Free-Float Market-Cap Index — hold every name under a cap, and redistribute what is over it.
- Modified Market-Cap Index — bend cap weighting toward equal weighting with one exponent.
Related — D03-F01 Index Initialization and Continuity
- Divisor Continuity Adjustment — the general rule this topic's split case is one instance of.
- Base-Date/Base-Value Initialization — where a divisor comes from in the first place.
MIT — see LICENSE.
{ "prices": [50, 75, 25, 100], // > 0, finite JSON numbers "divisor": 0.25, // > 0 "ids": ["ALFA", "BETA", "GAMMA", "DELTA"] // optional, aligned, non-blank, unique }