finance.uk-bank-rate
Bank of England Bank Rate (base rate) in force on a date, from the Bank's official history since 2001.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 14 tests, run in TypeScript, Python and Rust.
What it does
The Bank of England's Bank Rate (the "base rate", and the "official dealing rate" in older legislation) in force on a date. Statutory interest, tracker mortgages and loan agreements are all written as a margin over it, and all of them need the rate on a particular past date, not today's.
The rates are dated rows in `data/bank-rate.json`, one per change, each in force from the day of the change to the day before the next. They run from the cut to 4% on 8 November 2001 (so the rate on any date from then on is known) to the cut to 3.75% on 18 December 2025, which was still in force when this version was published. A date before 8 November 2001 is refused rather than guessed.
For example
uk_bank_rate(2026-09-23)→ rate 3.75%, valid from 2025-12-18, valid to — in force todayuk_bank_rate(2025-12-18)→ rate 3.75%, valid from 2025-12-18, valid to — the day a cut takes effectuk_bank_rate(2025-12-17)→ rate 4%, valid from 2025-08-07, valid to 2025-12-17 the day before it
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
pub fn uk_bank_rate(on_date: &str) -> BankRate
| on_date | date | the date to look up; a change takes effect on the day it is announced as effective |
| returns | BankRate |
The type it declares, generated into your project
/// The rate in force, and the dates it was in force.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BankRate {
/// 375 = 3.75%
pub basis_points: i64,
/// the day it took effect
pub valid_from: String,
/// the day before the next change; null while still in force
pub valid_to: Option<String>,
}
Your code names it in one line, in the file that uses it
fune!(finance.uk-bank-rate@^1); // then call uk_bank_rate(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::add_days; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::finance_uk_bank_rate_data::{BANK_RATE, BANK_RATE_HISTORY}; ← this capability’s own data, compiled from data/bank-rate.json into the same file by fune build
/// Bank Rate in force on a date: the latest change on or before it.
///
/// # Panics
/// Panics on a malformed date or one before the data begins.
pub fn uk_bank_rate(on_date: &str) -> BankRate {
add_days(on_date, 0); // validates the date; ISO dates then compare as strings
for row in BANK_RATE {
if on_date >= row.valid_from && row.valid_to.map_or(true, |to| on_date <= to) {
return BankRate {
basis_points: row.basis_points,
valid_from: row.valid_from.to_string(),
valid_to: row.valid_to.map(str::to_string),
};
}
}
// A history=current build keeps only the rate in force; answering an older
// date with it would be exactly the wrong answer this data exists to prevent.
if BANK_RATE_HISTORY != "full" {
if let Some(earliest) = BANK_RATE.iter().map(|r| r.valid_from).min() {
if on_date < earliest {
panic!(
"no Bank Rate on {}: this build was installed with history={}, so it only carries rates from {}. Reinstall with history=full for older dates.",
on_date, BANK_RATE_HISTORY, earliest
);
}
}
}
panic!("no Bank Rate on {}", on_date);
}
pub fn bank_rate_to_value(rate: &BankRate) -> Value {
Value::obj(vec![
("basisPoints", Value::Int(rate.basis_points)),
("validFrom", Value::str(&rate.valid_from)),
("validTo", rate.valid_to.as_deref().map_or(Value::Null, Value::str)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
bank_rate_to_value(&uk_bank_rate(args[0].as_str()))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add finance.uk-bank-rate
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./finance.uk-bank-rate-1.0.1-rust.fune, or fetch it from a terminal with fune pull finance.uk-bank-rate@1.0.1:rust.
The whole function, every language, is one file too: finance.uk-bank-rate-1.0.1.fune, 19,031 bytes, sha256 4b45acdee1dd9262e18c14d1cc635cc6cd8ae0e61583ea93b4e5adba0f9fdeac. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before finance.uk-bank-rate
after — your function gets the result and the arguments, and returns the final result.
// fune: after finance.uk-bank-rate
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace dates.add-days in finance.uk-bank-rate
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show finance.uk-bank-rate --steps.
// fune: step finance.uk-bank-rate after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| in force today | 2026-09-23 | → | rate 3.75%, valid from 2025-12-18, valid to — |
| the day a cut takes effect | 2025-12-18 | → | rate 3.75%, valid from 2025-12-18, valid to — |
| the day before it | 2025-12-17 | → | rate 4%, valid from 2025-08-07, valid to 2025-12-17 |
| the 0.1% emergency low of March 2020 | 2021-06-30 | → | rate 0.1%, valid from 2020-03-19, valid to 2021-12-15 |
| the short-lived 0.25% between the two March 2020 cuts | 2020-03-15 | → | rate 0.25%, valid from 2020-03-11, valid to 2020-03-18 |
| the 5.25% peak | 2024-06-30 | → | rate 5.25%, valid from 2023-08-03, valid to 2024-07-31 |
| seven years at 0.5% after March 2009 | 2012-12-31 | → | rate 0.5%, valid from 2009-03-05, valid to 2016-08-03 |
| a leap day | 2024-02-29 | → | rate 5.25%, valid from 2023-08-03, valid to 2024-07-31 |
| the first date the data covers | 2001-11-08 | → | rate 4%, valid from 2001-11-08, valid to 2003-02-05 |
| 30 June 2002, the first reference date of the Late Payment rate order | 2002-06-30 | → | rate 4%, valid from 2001-11-08, valid to 2003-02-05 |
Show the other 4 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| before the credit crunch cuts | 2007-12-31 | → | rate 5.5%, valid from 2007-12-06, valid to 2008-02-06 |
| a date before the data begins is refused | 2001-11-07 | → | error: no Bank Rate on 2001-11-07 |
| an impossible date is refused | 2025-02-29 | → | error: not a real calendar date |
| a malformed date is refused | 31/12/2025 | → | error: not an ISO date |
More from the author
Source: Bank of England, "Official Bank Rate history", https://www.bankofengland.co.uk/boeapps/database/Bank-Rate.asp, checked on 23 September 2026 (no change in 2026 up to that date). Rates are held in basis points, which every published rate fits exactly (0.1% is 10).
When the rate changes, publish a new version with the new row and the previous row's `validTo` filled in. The data declares its effective dates, so a `history=current` build ships only the rate still in force and refuses an earlier date, explaining why, rather than answering it with today's rate.
## Notices
Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
Contains Bank of England data. Reproduction of data in the Database is subject to the terms of the UK Open Government Licence.
1.0.1 adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 311 |
| README.md | 1,663 |
| data/bank-rate.json | 7,023 |
| impl/python.py | 1,210 |
| impl/rust.rs | 1,788 |
| impl/typescript.ts | 1,250 |
| vectors.json | 2,020 |