Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fintech Total-Market-Cap Index — Index Engineering Algorithm

A canonical, well-specified, cross-language (Python + TypeScript) reference implementation of the total-market-cap index. Own each company in proportion to its size and the weights look after themselves — at the cost of being exactly as concentrated as the market is. This module computes the index in exact decimal arithmetic with every input factor checked on its own, then measures the concentration the level never mentions.

Python TypeScript License Tests

📖 Full article (canonical): Total-Market-Cap 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-A02
Domain D03 — Index and Benchmark Engineering
Family D03-F02 — Weighting and Capping
Difficulty 2 / 5
Languages Python, TypeScript

Table of contents


Weight by what a company is worth

market value_i = price_i x shares_i x fx_i
weight_i       = market value_i / sum(market values)
level          = sum(market values) / divisor

Prices move and the weights move with them, so a fund tracking this index never has to trade to stay aligned. That property is why cap weighting is the default design for a market benchmark.


The number the level hides

1  MEGA   value 3000000000   weight 0.695491    cumulative 0.695491
2  LARGE  value  960000000   weight 0.222557    cumulative 0.918048
3  MID    value  297000000   weight 0.068854    cumulative 0.986902
4  SMALL  value   48000000   weight 0.011128    cumulative 0.99803
5  TINY   value    8500000   weight 0.001971    cumulative 1

herfindahl 0.538108, effective constituents 1.858364 of 5
names needed for half the index: 1

Five members, and the index is really 1.86 of them. effectiveConstituents is 1 / HHI — the number of equally weighted names that would be this concentrated — and it is the honest description of a cap-weighted benchmark that a level cannot give you.


What the reference got wrong

All confirmed by running it:

  • Sign errors cancel. Only the product price × shares × fx was checked, so a negative price times negative shares — two data errors — passed as a valid constituent. Each factor is checked on its own here.
  • Coercion, NaN and infinity were all accepted.
  • Ids were never read. The fixture carries them; the output does not. Here they are validated when supplied and default to positional labels, so the analysis surface can name members.

Arithmetic is exact: every JSON number is read as the decimal it spells, all arithmetic runs in exact fractions, and rounding happens once, at publication, to six decimals, half away from zero.


Two ways to use this

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/total-market-cap-index

Install

Python

cd python
pip install -e ".[dev]"

TypeScript

cd typescript
npm install
npm run build

Quickstart

from fintech_total_cap import calculate, concentration_report

basket = {"constituents": [
    {"id": "A", "price": 20, "shares": 1000000, "fx": 1},
    {"id": "B", "price": 40, "shares": 500000, "fx": 1},
    {"id": "C", "price": 30, "shares": 800000, "fx": 1.1},
], "divisor": 50000}

calculate(basket)["indexLevel"]                        # 1328
concentration_report(basket)["effectiveConstituents"]  # 2.945276

TypeScript is the same call:

import { calculate, concentrationReport } from 'fintech-total-cap';

calculate(basket).indexLevel;   // 1328

Run the tour in either language:

cd python && python examples/quickstart.py
cd typescript && npm run example

Both print byte-identical output.


Views: the analysis surface

weight_table — ranked, with the running total

Members largest first (ties by id in code-point order), each with its exact weight and the cumulative weight above it. Where the cumulative column crosses 0.5 is how much of the index the top handful is.

concentration_report — three measures that disagree usefully

Top-N share by name, the Herfindahl index, effectiveConstituents, namesForHalf, and equalWeightHerfindahl for comparison. Cutoffs default to 1/3/5/10, skipping any larger than the basket.

share_change_impact — a buyback moves the value, not the market

market value 4313500000 -> 4013500000 (non-market change -300000000)
divisor 50000 -> 46522.545497, level 86270 -> 86270
without the adjustment the level would read 80270: a -0.069549090 return nobody earned

No price moved, so the divisor absorbs the change. levelWithoutAdjustment is the phantom return the index would have printed had it not been adjusted, and every other member's weight rises.

verify_total_cap — four checks, and an audit mode

The last one, theReportedValuesAndLevelAgree, re-derives the level from the reported market values, so it catches a level that was computed on a different basket than the one published beside it. The publication residual is reported as a fact, never as a failure.


Input shape

{
  "constituents": [{
    "id": "A",           // optional; non-blank and unique when supplied, else positional
    "price": 20,         // > 0
    "shares": 1000000,   // > 0, total shares outstanding
    "fx": 1              // > 0, into the index currency
  }],
  "divisor": 50000       // > 0
}

API reference

Python — from fintech_total_cap import ...

function returns
calculate(data) / total_market_cap_index(data) marketValues, weights, indexLevel
weight_table(data) members ranked by size with cumulative weight and the residual
concentration_report(data, cutoffs=None) top-N shares, HHI, effective constituents, names for half
share_change_impact(data, changes) the rescaled divisor, weight shifts, and the phantom return
verify_total_cap(data, result=None) four checks; pass result to audit a supplied answer
validate_request · validate_constituent the validation steps, exposed
to_fraction · render · number · trim · scaled exact-arithmetic helpers

TypeScript — import { ... } from 'fintech-total-cap'

The same functions in camelCase (totalMarketCapIndex, concentrationReport, ...).


Edge cases & limitations

  • Total shares, not free float. This design weights the whole company, including stock that will never trade; the free-float sibling below closes that gap.
  • share_change_impact holds prices fixed — it prices the non-market part of a change. If prices moved too, split them out first.
  • The output is positional, as the contract is; weight_table and the other views are ranked and carry ids.
  • 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.

Testing

cd python && pytest -q          # 70 tests
cd typescript && npm test       # 70 tests

Both 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 (2.6 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,404 results the port returned. Every divergence is classified by name:

divergence cases what happened
double resolution 3,302 both correct to the decimal; neighbouring doubles above ~9e9
rounding rule 861 reference summed binary floats; port rounds the exact decimal half away from zero
coercion 728 reference accepted strings and booleans
ids validation 472 a constituent id was not a non-blank string, or ids repeated
non-finite 319 reference accepted NaN or infinity
sign cancellation 286 reference accepted negative price × negative shares

Related algorithms

Same family — D03-F02 Weighting and Capping

Related — D03-F01 Index Initialization and Continuity

🧭 Browse all algorithms →


License

MIT — see LICENSE.