Finance Toolkit v1.8 to v2.1: What Chang ...

Finance Toolkit v1.8 to v2.1: What Changed and Why

Jun 25, 2026

imageAt v1.8.1, the Finance Toolkit was a Python library for computing financial metrics from normalized statements. At v2.1.2 it is that plus a hosted MCP server, a brand-new Fixed Income module, a Portfolio module, a complete Economics rewrite backed by two data sources, a fiscal year normalization layer, a column filtering system, a currencies module, a SQLite result cache, a setup wizard for five MCP clients, and a utilities package that replaced tqdm throughout. This post is the full accounting of what changed, why each decision was made, and what the code actually looks like before and after.

The diff between v1.8.1 and v2.1.2 spans 684 files, 158,028 insertions, and 105,187 deletions. Here is what all of that means in practice.

What Got Added

Five new top-level additions: fixedincome/, mcp_server/, portfolio/, utilities/, and currencies_model.py. Two modules extracted or refactored: fmp_model.py split out from fundamentals_model.py, and yfinance_model.py added as an alternative data path. The project also added a Dockerfile, a docker-compose.yml, and migrated from pip to uv (tracked via uv.lock).

Fixed Income Module
The biggest new Python module. v1.8.1 had ECB and Fed rate data living inside the Economics module as a rough approximation of fixed income coverage. v2.1.2 has a dedicated FixedIncome controller with bond math, spread data from FRED, and rate data from ECB, Fed, and EURIBOR.

Bond Pricing and Analytics
The core bond math lives in financetoolkit/fixedincome/bond_model.py and covers:

  • get_bond_price — present value of coupons plus face value, supporting multiple compounding frequencies

  • get_current_yield — annual coupon / market price

  • get_effective_yield — adjusts for compounding frequency

  • get_yield_to_maturity — solved numerically from price

  • get_macaulays_duration — weighted average time to cash flows

  • get_modified_duration — Macaulay duration / (1 + yield per period)

  • get_effective_duration — finite difference approximation using shifted yield curves

  • get_dollar_duration — modified duration × price × 0.01

  • get_dv01 — dollar value of a basis point

  • get_convexity — second-order price sensitivity

All of these feed into the FixedIncome controller's get_present_value and get_duration methods. You can call them directly on any bond given par value, coupon rate, maturity, and yield.

ICE BofA Bond Index Data
The FRED integration pulls ICE BofA bond index data. These are the most commonly cited spread/yield benchmarks in fixed income analysis. The controller exposes:

  • get_ice_bofa_option_adjusted_spread — OAS by maturity bucket or credit rating

  • get_ice_bofa_effective_yield — effective yield by maturity or rating

  • get_ice_bofa_total_return — total return index

  • get_ice_bofa_yield_to_worst — YTW by maturity or rating

Each of these takes a maturity or rating parameter to switch between the two series families. This requires a FRED API key (free to get), stored as FRED_API_KEY in the environment.

Government Bond Yields and Central Bank Rates

  • get_government_bond_yield — pulls OECD government bond yields by country and maturity

  • get_euribor_rates — EURIBOR term rates from 1 week to 12 months

  • get_european_central_bank_rates — ECB deposit, main refinancing, and marginal lending rates

  • get_federal_reserve_rates — EFFR, IORB, overnight repo, and related rates from FRED

The ECB and Fed rate methods moved here from the Economics module, where they had been placed as a stopgap.

Derivative Pricing
get_derivative_price uses the Black model to price European options on futures, covering calls, puts, caps, floors, and swaptions. The implementation is in derivative_model.py and exposed through FixedIncome.get_derivative_price.

Economics Module: Full Rewrite

The Economics module was rebuilt twice between v1.8.1 and v2.1.2. The change count is large: the controller went from ~500 lines to over 4,300 lines, and the OECD model was rewritten from scratch.

OECD API v1 → v2
This was a silent breakage. OECD deprecated their v1 API stats.oecd.org/sdmx-json/data/DP_LIVE/) and replaced it with a new endpoint structure at sdmx.oecd.org/public/rest/data/. The old API returned CSV with column names like LOCATION, TIME, Value, INDICATOR. The new API returns CSV with REF_AREA, TIME_PERIOD, OBS_VALUE, and a different dimension layout.

The v2 endpoint also added 429 rate limit detection with a clear warning rather than a silent failure.

If your Economics queries broke around mid-2024 and started returning empty DataFrames or wrong data, this is exactly why. Updating to v1.9+ fixes it.

Global Macro Database (GMDB)
v1.9 added integration with the Global Macro Database from KMueller-Lab. This is an academic dataset that covers historical macro time series going much further back than OECD does for many indicators.

The gmdb_model.py module contains a collect_global_macro_database_dataset function that downloads and caches the raw dataset, then exposes individual series as typed DataFrames. Functions include:

  • get_nominal_gross_domestic_product

  • get_real_gross_domestic_product

  • get_gross_domestic_product_deflator

  • get_population

  • get_total_consumption / get_total_consumption_to_gdp_ratio

  • get_investment / get_fixed_investment (with GDP ratios)

  • get_exports / get_imports (with GDP ratios)

  • get_current_account_balance

  • get_real_effective_exchange_rate

  • get_usd_exchange_rate

  • and more

These feed into the Economics controller's corresponding methods, which automatically select between OECD and GMDB depending on the indicator and availability.

New Economics Indicators
The set of indicators in the controller grew substantially. v1.8.1 had functions like get_gross_domestic_product_growth, get_narrow_and_broad_money, get_purchasing_power_parity, and several commodity/population stats. v2.1.2 restructured this into a cleaner taxonomy.

Portfolio Module
A Portfolio module was added with support for portfolio-weighted calculations across the existing analysis modules (Ratios, Technicals, Performance, Risk). The toolkit controller now accepts portfolio_weights which gets passed into each sub-module. The portfolio section in toolkit_controller.py propagates weights when sub-modules are instantiated.

Fundamentals Model Split
At v1.8.1, all FMP-specific API calls lived inside fundamentals_model.py, which was 1,302 lines long. At v2.1.2, fundamentals_model.py is 312 lines. The FMP-specific fetching logic was extracted into financetoolkit/fmp_model.py (dedicated FMP data layer) and the Yahoo Finance equivalent went into yfinance_model.py.

This separation makes it possible to swap data providers more cleanly, and it is what enables the Yahoo Finance fallback.

Yahoo Finance as a Fallback
yfinance_model.py was added as an alternative historical price data source. When FMP returns an error (rate limit, plan restriction, unsupported ticker), the Toolkit can automatically retry via Yahoo Finance for historical price data. This keeps historical data accessible for free-tier users without interrupting analysis.

The fallback does not cover financial statements — Yahoo Finance does not expose full statements in a normalized form that matches the FMP structure — so it is scoped to price and statistics data only.

Currencies Model
financetoolkit/currencies_model.py was added with two functions:

- determine_currencies — detect which currencies a set of tickers trades in

- convert_currencies — convert values between currencies using historical FX rates

This feeds into the statement normalization pipeline. When tickers are denominated in different currencies, the Toolkit now has a dedicated layer for handling FX conversion rather than treating it inline.

Fiscal Year to Calendar Year Mapping
This change matters most for anyone comparing multiple tickers in a single DataFrame.

Companies have non-December fiscal year ends. NVIDIA ends January. Apple ends September. Before v2.1, the Toolkit labeled statement data by fiscal year. That meant "2024" for NVDA and "2024" for AAPL were actually different 12-month windows, making direct comparisons misleading.

v2.1 introduced automatic fiscal year to calendar year remapping. The Toolkit detects each company's fiscal year end from statement dates, shifts the labels to the calendar year the majority of the period falls in, and records the remapping in a _notes field so it is always traceable.

When remapping occurs, the adjustment is logged into this dict and surfaced in the output via the _notes metadata, so you can always audit what was shifted.

This is particularly important when using the MCP server, where an LLM will interpret column headers literally. Inconsistent period labels across tickers would cause silent errors in any period-based comparison.

Column Filtering
The show_columns parameter was added across the entire public API — toolkit methods, MCP tools, and sub-module controllers.

Before v2.1, calling get_income_statement() on a multi-ticker Toolkit instance returned the full statement. For deeply analyzed statements with 20+ line items across 10+ years and 5+ tickers, that is a large DataFrame. In a Python session that is fine. In an MCP context where the LLM has a finite context window, returning 200 rows when you need 2 burns context and makes chained analysis unstable.

tqdm Removed
Progress bars (via tqdm) were replaced with logger.info statements throughout the codebase. The commit message is direct: "Replace the TQDM with logger statements."

This was the right call for several reasons:

1. tqdm output is unstructured and does not play well with log aggregation

2. In non-interactive environments — CI pipelines, Jupyter with output capture, the MCP server process — tqdm bars either produce garbage output or silently fail

3. tqdm was a runtime dependency that added no real value beyond what a log statement provides

4. Log statements respect log level control DEBUG, INFO, WARNING), so output can be suppressed in production without code changes

The new financetoolkit/utilities/logger_model.py centralizes logger initialization across the entire package. All modules now call get_logger() from this model rather than configuring logging ad hoc.

The MCP Server (v2.0)
v2.0.0 was the MCP server release. The financetoolkit/mcp_server/ package is approximately 4,700 lines across ten modules. Here is what each one does.

Rather than hand-writing each MCP tool, the server uses inspection_controller.py to introspect the controller classes at startup. It reads method signatures, docstrings, and parameter annotations, then generates MCP tool definitions automatically. The config.yaml controls which modules are included and which methods are skipped.

Hosted (financetoolkit.jeroenbouma.com/mcp): Per-request FMP key resolution from HTTP headers or query parameters. Clients pass their own FMP key as x-fmp-api-key or x-financial-modeling-prep-api-key header, or as a fmp_api_key query parameter. The Claude.ai hosted environment uses the query parameter path.

For hosted deployments that need OAuth, the auth model implements full OAuth 2.1 with PKCE — generating server-signed code challenges, validating PKCE S256/plain verifiers, and issuing Bearer tokens. This is what Claude.ai's MCP authorization flow uses.

A one-click installation bundle is available on the latest release page. The MCPB format is a ZIP containing a manifest and pyproject.toml that MCP-compatible launchers (like Claude Desktop's bundle importer) can use to install and configure the server without running the wizard manually.

Thanks for the support. It genuinely helps keep this going.

- Jeroen

Enjoy this post?

Buy Jeroen Bouma a coffee

More from Jeroen Bouma