Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fintech Base-Date/Base-Value Initialization — Index Engineering Algorithm

A canonical, well-specified, cross-language (Python + TypeScript) reference implementation of index initialization. An index starts with one decision: on this date, this basket is worth this many points. This module turns a basket, a base date and a base level into the market value, the divisor and the opening level — in exact decimal arithmetic, with every input factor checked on its own — and then shows what those three numbers leave out.

Python TypeScript License Tests

📖 Full article (canonical): Base-Date/Base-Value Initialization — 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 EngineeringIndex Initialization and Continuity 📥 Just want to call it? It also ships in the fintech-algorithms npm package — see Two ways to use this.

Catalog topic D03-F01-A01
Domain D03 — Index and Benchmark Engineering
Family D03-F01 — Index Initialization and Continuity
Difficulty 2 / 5
Languages Python, TypeScript
Opens the D03 domain and the D03-F01 family

Table of contents


One decision, one relation

Every level an index will ever publish is one relation:

level = market value / divisor

Initialization fixes it. On the base date, add up each member's float-adjusted market value in the index currency, choose a base level, and the divisor follows:

market value = sum(price x shares x floatFactor x fx)
divisor      = market value / base level

On the base date the level is the base level. From then on, prices move the market value and the level moves with it; a corporate action or a membership change moves the market value too, and the divisor is adjusted so the level does not. That is the rest of this family.

Initialization on 2026-01-02
  market value  119200
  divisor       119.2
  index level   1000

Every factor is checked on its own

The reference engine checked only the total market value. Two consequences, both confirmed by running it:

  • Sign errors cancel. A negative price times negative shares is a positive contribution, so two data errors produced a valid-looking index.
  • Zero factors vanish. A zero fx rate or float factor silently removed a member's value from the base, while the member stayed in the basket.

Here price, shares and fx must each be positive, floatFactor must be in (0, 1], ids must be unique non-blank strings, and baseDate — never read by the reference — must be a calendar-valid YYYY-MM-DD.

One more refusal: a divisor that publishes as zero. A true divisor below 5e-7 rounds to 0 at six decimals, which both reference twins returned. Nothing can be divided by it.


Exact arithmetic

Every JSON number is read as the decimal its shortest round-trip spelling writes — 0.8 is exactly 4/5 — and all arithmetic runs in exact fractions (Fraction in Python, a bigint rational in TypeScript). Rounding happens once, at publication: six decimals, half away from zero.

calculate({"constituents": [{"id": "X", "price": 1.0000005, "shares": 1, "floatFactor": 1, "fx": 1}],
           "baseLevel": 1, "baseDate": "2026-01-02"})["divisor"]
# 1.000001  -- the written decimal is exactly on the half; its binary double is just below it

The published number is built from the rounded decimal string, so Python and TypeScript return the same double. Summing binary products instead, the reference's market values drifted by up to 1e-4 above ~1e11.


Two ways to use this

This repo is the production home: the full implementation, the analysis surface below, and 194 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/index-initialization-and-continuity/base-date-base-value-initialization

Install

Python

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

TypeScript

cd typescript
npm install
npm run build

Quickstart

from fintech_index_base import calculate, constituent_breakdown

request = {
    "constituents": [
        {"id": "ALFA", "price": 50, "shares": 1000, "floatFactor": 0.8, "fx": 1},
        {"id": "BETA", "price": 80, "shares": 600, "floatFactor": 0.75, "fx": 1},
        {"id": "GAMMA", "price": 40, "shares": 1200, "floatFactor": 0.9, "fx": 1},
    ],
    "baseLevel": 1000,
    "baseDate": "2026-01-02",
}

calculate(request)
# {'marketValue': 119200, 'divisor': 119.2, 'indexLevel': 1000}

TypeScript is the same call:

import { calculate, constituentBreakdown } from 'fintech-index-base';

calculate(request);   // { marketValue: 119200, divisor: 119.2, indexLevel: 1000 }

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

constituent_breakdown — where the base value comes from

ALFA   float value  40000   weight 0.335570   points 335.57047
BETA   float value  36000   weight 0.302013   points 302.013423
GAMMA  float value  43200   weight 0.362416   points 362.416107
weights sum to one: true   points sum to the base level: true

levelPoints is each member's share of the base level. Both sums are checked exactly, not within a tolerance.

base_level_sensitivity — the base level only sets the scale, until it doesn't

The base level cannot move money between members, so the weights never change. What changes is how many decimals the divisor needs — and a divisor published to six decimals only reproduces the base level approximately when it does not terminate:

base     1   divisor       119200   exact in 0 dp      level error from 6dp divisor 0
base     7   divisor 17028.571429   never terminates   level error from 6dp divisor -0.000000000176174
base  1000   divisor        119.2   exact in 1 dp      level error from 6dp divisor 0
base 10000   divisor        11.92   exact in 2 dp      level error from 6dp divisor 0

rebase_series — the same history on another base

Restates a level history on a new base date and level. Every level is multiplied by one exact factor, so every period return is preserved, and returnsPreserved checks it exactly:

2026-01-02      1000 -> 98.765432
2026-01-05    1012.5 -> 100
2026-01-06    998.25 -> 98.592593
factor 0.098765432099   returns preserved: true

verify_initialization — four checks, and an audit mode

ok   theMarketValueIsTheSumOfFloatValues
FAIL theDivisorIsMarketValueOverBaseLevel
ok   theLevelOnTheBaseDateIsTheBaseLevel
FAIL thePublishedDivisorReproducesTheBaseLevel

That is a vendor file that published the divisor as 119. Pass a second argument to audit somebody else's numbers; the last check divides the market value by the divisor as published and asks whether the base level comes back.


Input shape

{
  "constituents": [{
    "id": "ALFA",          // non-blank string, unique in the basket
    "price": 50,           // > 0, in the security's currency
    "shares": 1000,        // > 0, index shares
    "floatFactor": 0.8,    // in (0, 1]
    "fx": 1                // > 0, security currency -> index currency, on the base date
  }],
  "baseLevel": 1000,       // > 0
  "baseDate": "2026-01-02" // calendar-valid YYYY-MM-DD
}

Numbers must be JSON numbers: strings, booleans, NaN and infinities are refused.


API reference

Pythonfrom fintech_index_base import ...

function returns
calculate(data) / initialize_index(data) marketValue, divisor, indexLevel
constituent_breakdown(data) per-member float value, weight and index points; exact sum checks
base_level_sensitivity(data, base_levels=None) divisor, decimals needed and reproduction error per base level
rebase_series(series, rebase_date, new_base_level) a restated level history and its exact factor
verify_initialization(data, result=None) four checks; pass result to audit a supplied answer
validate_request · validate_constituent · parse_date the validation steps, exposed
to_fraction · render · number · trim · scaled the exact-arithmetic helpers

TypeScriptimport { ... } from 'fintech-index-base'

The same functions in camelCase (initializeIndex, constituentBreakdown, ...). Exact state is a Rational with bigint numerator and denominator.


Edge cases & limitations

  • Large published numbers are doubles. Above ~9e9 a six-decimal value is finer than a double can hold, so the published number is the nearest double to the correctly rounded decimal. Weights and reproduction errors are exact decimal strings for this reason. Python returns integral values as int, which is exact beyond 2^53; JavaScript cannot.
  • The fx rate is yours to get right. It must convert the security's currency into the index currency at the base date; the module does not know currencies.
  • baseDate is validated, not used. No prices are fetched; the date labels the result.
  • A zero-weight member is refused, not ignored. Remove it from the basket instead.
  • rebase_series needs the rebase date to be in the series — no interpolation between dates.
  • JSON integers beyond 2^53 reach TypeScript already rounded by JSON.parse.

Testing

cd python && pytest -q          # 97 tests
cd typescript && npm test       # 97 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 initialization and all four surfaces, valid and malformed — and their canonical JSON output is byte-identical. The exact-arithmetic modules were probed on 20,000 values, and 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,974 results the port returned. Every divergence is classified by name:

divergence cases what happened
double resolution 2,324 both correct to the decimal; neighbouring doubles above ~9e9
rounding rule 1,246 reference rounded a binary float; port rounds the exact decimal half away from zero
coercion 449 reference accepted strings and booleans
base date 429 reference never read baseDate
zero factor 261 reference let a zero fx or float factor erase a member
duplicate ids 222 reference did not check ids
sign cancellation 206 reference accepted negative price × negative shares
float bound 156 reference accepted a float factor above 1
zero published divisor 27 reference returned a divisor of 0
reference float accumulation 12 reference's binary sum drifted; port matches the oracle

Related algorithms

Same family — D03-F01 Index Initialization and Continuity

🧭 Browse all algorithms →


License

MIT — see LICENSE.