@fin.cx/opendata 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +4 -2
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/baserate/baserate.d.ts +9 -0
- package/dist_ts/baserate/baserate.js +49 -0
- package/dist_ts/baserate/classes.baserateerror.d.ts +14 -0
- package/dist_ts/baserate/classes.baserateerror.js +10 -0
- package/dist_ts/baserate/data.baserate.d.ts +18 -0
- package/dist_ts/baserate/data.baserate.js +63 -0
- package/dist_ts/baserate/index.d.ts +4 -0
- package/dist_ts/baserate/index.js +7 -0
- package/dist_ts/baserate/interfaces.d.ts +19 -0
- package/dist_ts/baserate/interfaces.js +2 -0
- package/package.json +11 -5
- package/readme.md +22 -2
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/baserate/baserate.ts +57 -0
- package/ts/baserate/classes.baserateerror.ts +20 -0
- package/ts/baserate/data.baserate.ts +81 -0
- package/ts/baserate/index.ts +6 -0
- package/ts/baserate/interfaces.ts +21 -0
package/.smartconfig.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"githost": "gitlab.com",
|
|
6
6
|
"gitscope": "fin.cx",
|
|
7
7
|
"gitrepo": "opendata",
|
|
8
|
-
"description": "Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, and stock and crypto prices and fundamentals from several providers.",
|
|
8
|
+
"description": "Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, the base rate of § 247 BGB, and stock and crypto prices and fundamentals from several providers.",
|
|
9
9
|
"npmPackagename": "@fin.cx/opendata",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"projectDomain": "fin.cx",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"fundamentals",
|
|
26
26
|
"public holidays",
|
|
27
27
|
"Feiertage",
|
|
28
|
-
"working days"
|
|
28
|
+
"working days",
|
|
29
|
+
"Basiszinssatz",
|
|
30
|
+
"base rate"
|
|
29
31
|
]
|
|
30
32
|
},
|
|
31
33
|
"release": {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/opendata',
|
|
6
|
-
version: '4.
|
|
7
|
-
description: 'Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, and stock and crypto prices and fundamentals from several providers.'
|
|
6
|
+
version: '4.2.0',
|
|
7
|
+
description: 'Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, the base rate of § 247 BGB, and stock and crypto prices and fundamentals from several providers.'
|
|
8
8
|
};
|
|
9
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxrQkFBa0I7SUFDeEIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLG9TQUFvUztDQUNsVCxDQUFBIn0=
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IBaseRatePeriod, IBaseRateSpan, TIsoDate } from './interfaces.js';
|
|
2
|
+
/** The base rate in force on a day: the published period that contains it. */
|
|
3
|
+
export declare const getBaseRate: (dateArg: TIsoDate) => IBaseRatePeriod;
|
|
4
|
+
/**
|
|
5
|
+
* A span of days, both inclusive, split where the base rate changes: one piece per published
|
|
6
|
+
* period it touches, oldest first. Interest under § 288 BGB is computed per piece, since the rate
|
|
7
|
+
* of each half-year applies to the days in it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const splitByBaseRate: (fromArg: TIsoDate, toArg: TIsoDate) => IBaseRateSpan[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { BaseRateError } from './classes.baserateerror.js';
|
|
2
|
+
import { baseRateFirstDay, baseRatePeriods, baseRatesReadOn } from './data.baserate.js';
|
|
3
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
4
|
+
const checkDate = (dateArg) => {
|
|
5
|
+
const match = typeof dateArg === 'string' ? ISO_DATE.exec(dateArg) : null;
|
|
6
|
+
const utc = match ? new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))) : null;
|
|
7
|
+
if (!match || !utc || utc.toISOString().slice(0, 10) !== dateArg) {
|
|
8
|
+
throw new BaseRateError('invalid_date', `${JSON.stringify(dateArg)} is not a calendar date written YYYY-MM-DD.`);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const addDay = (dateArg) => {
|
|
12
|
+
const utc = new Date(`${dateArg}T00:00:00Z`);
|
|
13
|
+
utc.setUTCDate(utc.getUTCDate() + 1);
|
|
14
|
+
return utc.toISOString().slice(0, 10);
|
|
15
|
+
};
|
|
16
|
+
/** The base rate in force on a day: the published period that contains it. */
|
|
17
|
+
export const getBaseRate = (dateArg) => {
|
|
18
|
+
checkDate(dateArg);
|
|
19
|
+
if (dateArg < baseRateFirstDay) {
|
|
20
|
+
throw new BaseRateError('before_first_period', `${dateArg} lies before ${baseRateFirstDay}, the first day of the Bundesbank's table.`);
|
|
21
|
+
}
|
|
22
|
+
const period = baseRatePeriods.find((periodArg) => periodArg.validFrom <= dateArg && dateArg <= periodArg.validTo);
|
|
23
|
+
if (!period) {
|
|
24
|
+
throw new BaseRateError('not_published', `${dateArg} lies after ${baseRatePeriods[baseRatePeriods.length - 1].validTo}; the base rate of its half-year was not published when the table was read on ${baseRatesReadOn}.`);
|
|
25
|
+
}
|
|
26
|
+
return period;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A span of days, both inclusive, split where the base rate changes: one piece per published
|
|
30
|
+
* period it touches, oldest first. Interest under § 288 BGB is computed per piece, since the rate
|
|
31
|
+
* of each half-year applies to the days in it.
|
|
32
|
+
*/
|
|
33
|
+
export const splitByBaseRate = (fromArg, toArg) => {
|
|
34
|
+
checkDate(fromArg);
|
|
35
|
+
checkDate(toArg);
|
|
36
|
+
if (toArg < fromArg) {
|
|
37
|
+
throw new BaseRateError('invalid_range', `${toArg} lies before ${fromArg}.`);
|
|
38
|
+
}
|
|
39
|
+
const spans = [];
|
|
40
|
+
let day = fromArg;
|
|
41
|
+
while (day <= toArg) {
|
|
42
|
+
const period = getBaseRate(day);
|
|
43
|
+
const to = period.validTo < toArg ? period.validTo : toArg;
|
|
44
|
+
spans.push({ from: day, to, ratePercent: period.ratePercent });
|
|
45
|
+
day = addDay(to);
|
|
46
|
+
}
|
|
47
|
+
return spans;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZXJhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9iYXNlcmF0ZS9iYXNlcmF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDM0QsT0FBTyxFQUFFLGdCQUFnQixFQUFFLGVBQWUsRUFBRSxlQUFlLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUd4RixNQUFNLFFBQVEsR0FBRywyQkFBMkIsQ0FBQztBQUU3QyxNQUFNLFNBQVMsR0FBRyxDQUFDLE9BQWlCLEVBQVEsRUFBRTtJQUM1QyxNQUFNLEtBQUssR0FBRyxPQUFPLE9BQU8sS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUMxRSxNQUFNLEdBQUcsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO0lBQ3hHLElBQUksQ0FBQyxLQUFLLElBQUksQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLEtBQUssT0FBTyxFQUFFLENBQUM7UUFDakUsTUFBTSxJQUFJLGFBQWEsQ0FBQyxjQUFjLEVBQUUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyw2Q0FBNkMsQ0FBQyxDQUFDO0lBQ25ILENBQUM7QUFDSCxDQUFDLENBQUM7QUFFRixNQUFNLE1BQU0sR0FBRyxDQUFDLE9BQWlCLEVBQVksRUFBRTtJQUM3QyxNQUFNLEdBQUcsR0FBRyxJQUFJLElBQUksQ0FBQyxHQUFHLE9BQU8sWUFBWSxDQUFDLENBQUM7SUFDN0MsR0FBRyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDckMsT0FBTyxHQUFHLENBQUMsV0FBVyxFQUFFLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUN4QyxDQUFDLENBQUM7QUFFRiw4RUFBOEU7QUFDOUUsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFHLENBQUMsT0FBaUIsRUFBbUIsRUFBRTtJQUNoRSxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbkIsSUFBSSxPQUFPLEdBQUcsZ0JBQWdCLEVBQUUsQ0FBQztRQUMvQixNQUFNLElBQUksYUFBYSxDQUFDLHFCQUFxQixFQUFFLEdBQUcsT0FBTyxnQkFBZ0IsZ0JBQWdCLDRDQUE0QyxDQUFDLENBQUM7SUFDekksQ0FBQztJQUNELE1BQU0sTUFBTSxHQUFHLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxTQUFTLEVBQUUsRUFBRSxDQUFDLFNBQVMsQ0FBQyxTQUFTLElBQUksT0FBTyxJQUFJLE9BQU8sSUFBSSxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbkgsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osTUFBTSxJQUFJLGFBQWEsQ0FDckIsZUFBZSxFQUNmLEdBQUcsT0FBTyxlQUFlLGVBQWUsQ0FBQyxlQUFlLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLE9BQU8saUZBQWlGLGVBQWUsR0FBRyxDQUNoTCxDQUFDO0lBQ0osQ0FBQztJQUNELE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsQ0FBQyxPQUFpQixFQUFFLEtBQWUsRUFBbUIsRUFBRTtJQUNyRixTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbkIsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2pCLElBQUksS0FBSyxHQUFHLE9BQU8sRUFBRSxDQUFDO1FBQ3BCLE1BQU0sSUFBSSxhQUFhLENBQUMsZUFBZSxFQUFFLEdBQUcsS0FBSyxnQkFBZ0IsT0FBTyxHQUFHLENBQUMsQ0FBQztJQUMvRSxDQUFDO0lBQ0QsTUFBTSxLQUFLLEdBQW9CLEVBQUUsQ0FBQztJQUNsQyxJQUFJLEdBQUcsR0FBRyxPQUFPLENBQUM7SUFDbEIsT0FBTyxHQUFHLElBQUksS0FBSyxFQUFFLENBQUM7UUFDcEIsTUFBTSxNQUFNLEdBQUcsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2hDLE1BQU0sRUFBRSxHQUFHLE1BQU0sQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7UUFDM0QsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLFdBQVcsRUFBRSxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQztRQUMvRCxHQUFHLEdBQUcsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ25CLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUMsQ0FBQyJ9
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a question to the base-rate table was refused:
|
|
3
|
+
* - `invalid_date`: the value is not a calendar date written `YYYY-MM-DD`;
|
|
4
|
+
* - `invalid_range`: a span ends before it begins;
|
|
5
|
+
* - `before_first_period`: the day lies before the first period the table holds (1 July 2002);
|
|
6
|
+
* - `not_published`: the day lies after the half-year of the last period the table holds, whose
|
|
7
|
+
* rate the Bundesbank had not published when the table was read (`baseRatesReadOn`).
|
|
8
|
+
*/
|
|
9
|
+
export type TBaseRateErrorCode = 'invalid_date' | 'invalid_range' | 'before_first_period' | 'not_published';
|
|
10
|
+
/** The typed error of the base-rate table. */
|
|
11
|
+
export declare class BaseRateError extends Error {
|
|
12
|
+
readonly code: TBaseRateErrorCode;
|
|
13
|
+
constructor(code: TBaseRateErrorCode, message: string);
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The typed error of the base-rate table. */
|
|
2
|
+
export class BaseRateError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'BaseRateError';
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5iYXNlcmF0ZWVycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvYmFzZXJhdGUvY2xhc3Nlcy5iYXNlcmF0ZWVycm9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQVVBLDhDQUE4QztBQUM5QyxNQUFNLE9BQU8sYUFBYyxTQUFRLEtBQUs7SUFDdEIsSUFBSSxDQUFxQjtJQUV6QyxZQUFZLElBQXdCLEVBQUUsT0FBZTtRQUNuRCxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixJQUFJLENBQUMsSUFBSSxHQUFHLGVBQWUsQ0FBQztRQUM1QixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztJQUNuQixDQUFDO0NBQ0YifQ==
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base rate (Basiszinssatz) of § 247 BGB, as the Deutsche Bundesbank publishes it: "Gemäß
|
|
3
|
+
* § 247 Abs. 2 BGB ist die Deutsche Bundesbank verpflichtet, den aktuellen Stand des
|
|
4
|
+
* Basiszinssatzes im Bundesanzeiger zu veröffentlichen." It changes on 1 January and 1 July
|
|
5
|
+
* (§ 247 Abs. 1 Satz 2 BGB). The table below is the Bundesbank's table "Basiszinssatz nach § 247
|
|
6
|
+
* BGB" (https://www.bundesbank.de/de/bundesbank/organisation/agb-und-regelungen/basiszinssatz-607820),
|
|
7
|
+
* read on 2026-09-27, unaltered: every row from 1 July 2002, the first one it lists, to the
|
|
8
|
+
* current one. A later half-year needs a new release.
|
|
9
|
+
*/
|
|
10
|
+
import type { IBaseRatePeriod } from './interfaces.js';
|
|
11
|
+
/** the first day the table answers for */
|
|
12
|
+
export declare const baseRateFirstDay = "2002-07-01";
|
|
13
|
+
/** the day the Bundesbank's table was read; later half-years need a new release */
|
|
14
|
+
export declare const baseRatesReadOn = "2026-09-27";
|
|
15
|
+
/** the page the table was read from */
|
|
16
|
+
export declare const baseRateSource = "https://www.bundesbank.de/de/bundesbank/organisation/agb-und-regelungen/basiszinssatz-607820";
|
|
17
|
+
/** every published period, oldest first */
|
|
18
|
+
export declare const baseRatePeriods: readonly IBaseRatePeriod[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** the first day the table answers for */
|
|
2
|
+
export const baseRateFirstDay = '2002-07-01';
|
|
3
|
+
/** the day the Bundesbank's table was read; later half-years need a new release */
|
|
4
|
+
export const baseRatesReadOn = '2026-09-27';
|
|
5
|
+
/** the page the table was read from */
|
|
6
|
+
export const baseRateSource = 'https://www.bundesbank.de/de/bundesbank/organisation/agb-und-regelungen/basiszinssatz-607820';
|
|
7
|
+
const rows = [
|
|
8
|
+
// gültig ab, Basiszinssatz in Prozent, as the table of the Deutsche Bundesbank states them
|
|
9
|
+
['2002-07-01', 2.47],
|
|
10
|
+
['2003-01-01', 1.97],
|
|
11
|
+
['2003-07-01', 1.22],
|
|
12
|
+
['2004-01-01', 1.14],
|
|
13
|
+
['2004-07-01', 1.13],
|
|
14
|
+
['2005-01-01', 1.21],
|
|
15
|
+
['2005-07-01', 1.17],
|
|
16
|
+
['2006-01-01', 1.37],
|
|
17
|
+
['2006-07-01', 1.95],
|
|
18
|
+
['2007-01-01', 2.70],
|
|
19
|
+
['2007-07-01', 3.19],
|
|
20
|
+
['2008-01-01', 3.32],
|
|
21
|
+
['2008-07-01', 3.19],
|
|
22
|
+
['2009-01-01', 1.62],
|
|
23
|
+
['2009-07-01', 0.12],
|
|
24
|
+
['2010-01-01', 0.12],
|
|
25
|
+
['2010-07-01', 0.12],
|
|
26
|
+
['2011-01-01', 0.12],
|
|
27
|
+
['2011-07-01', 0.37],
|
|
28
|
+
['2012-01-01', 0.12],
|
|
29
|
+
['2012-07-01', 0.12],
|
|
30
|
+
['2013-01-01', -0.13],
|
|
31
|
+
['2013-07-01', -0.38],
|
|
32
|
+
['2014-01-01', -0.63],
|
|
33
|
+
['2014-07-01', -0.73],
|
|
34
|
+
['2015-01-01', -0.83],
|
|
35
|
+
['2015-07-01', -0.83],
|
|
36
|
+
['2016-01-01', -0.83],
|
|
37
|
+
['2016-07-01', -0.88],
|
|
38
|
+
['2017-01-01', -0.88],
|
|
39
|
+
['2017-07-01', -0.88],
|
|
40
|
+
['2018-01-01', -0.88],
|
|
41
|
+
['2018-07-01', -0.88],
|
|
42
|
+
['2019-01-01', -0.88],
|
|
43
|
+
['2019-07-01', -0.88],
|
|
44
|
+
['2020-01-01', -0.88],
|
|
45
|
+
['2020-07-01', -0.88],
|
|
46
|
+
['2021-01-01', -0.88],
|
|
47
|
+
['2021-07-01', -0.88],
|
|
48
|
+
['2022-01-01', -0.88],
|
|
49
|
+
['2022-07-01', -0.88],
|
|
50
|
+
['2023-01-01', 1.62],
|
|
51
|
+
['2023-07-01', 3.12],
|
|
52
|
+
['2024-01-01', 3.62],
|
|
53
|
+
['2024-07-01', 3.37],
|
|
54
|
+
['2025-01-01', 2.27],
|
|
55
|
+
['2025-07-01', 1.27],
|
|
56
|
+
['2026-01-01', 1.27],
|
|
57
|
+
['2026-07-01', 1.52],
|
|
58
|
+
];
|
|
59
|
+
/** the last day of the half-year a period begins in */
|
|
60
|
+
const halfYearEndOf = (validFromArg) => `${validFromArg.slice(0, 4)}-${validFromArg.slice(5, 7) === '01' ? '06-30' : '12-31'}`;
|
|
61
|
+
/** every published period, oldest first */
|
|
62
|
+
export const baseRatePeriods = Object.freeze(rows.map(([validFrom, ratePercent]) => Object.freeze({ validFrom, validTo: halfYearEndOf(validFrom), ratePercent })));
|
|
63
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS5iYXNlcmF0ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2Jhc2VyYXRlL2RhdGEuYmFzZXJhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBV0EsMENBQTBDO0FBQzFDLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLFlBQVksQ0FBQztBQUU3QyxtRkFBbUY7QUFDbkYsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLFlBQVksQ0FBQztBQUU1Qyx1Q0FBdUM7QUFDdkMsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLDhGQUE4RixDQUFDO0FBRTdILE1BQU0sSUFBSSxHQUE2QztJQUNyRCwyRkFBMkY7SUFDM0YsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDckIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztJQUNwQixDQUFDLFlBQVksRUFBRSxJQUFJLENBQUM7SUFDcEIsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDO0lBQ3BCLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQztDQUNyQixDQUFDO0FBRUYsdURBQXVEO0FBQ3ZELE1BQU0sYUFBYSxHQUFHLENBQUMsWUFBb0IsRUFBVSxFQUFFLENBQ3JELEdBQUcsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLElBQUksWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBRXpGLDJDQUEyQztBQUMzQyxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQStCLE1BQU0sQ0FBQyxNQUFNLENBQ3RFLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLFNBQVMsRUFBRSxXQUFXLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsYUFBYSxDQUFDLFNBQVMsQ0FBQyxFQUFFLFdBQVcsRUFBRSxDQUFDLENBQUMsQ0FDckgsQ0FBQyJ9
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from './interfaces.js';
|
|
2
|
+
export { BaseRateError, type TBaseRateErrorCode } from './classes.baserateerror.js';
|
|
3
|
+
export { baseRateFirstDay, baseRatePeriods, baseRateSource, baseRatesReadOn } from './data.baserate.js';
|
|
4
|
+
export { getBaseRate, splitByBaseRate } from './baserate.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// This entry point stands alone: it imports nothing from the rest of the package, has no
|
|
2
|
+
// dependencies and needs no storage or network.
|
|
3
|
+
export * from './interfaces.js';
|
|
4
|
+
export { BaseRateError } from './classes.baserateerror.js';
|
|
5
|
+
export { baseRateFirstDay, baseRatePeriods, baseRateSource, baseRatesReadOn } from './data.baserate.js';
|
|
6
|
+
export { getBaseRate, splitByBaseRate } from './baserate.js';
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9iYXNlcmF0ZS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSx5RkFBeUY7QUFDekYsZ0RBQWdEO0FBQ2hELGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsT0FBTyxFQUFFLGFBQWEsRUFBMkIsTUFBTSw0QkFBNEIsQ0FBQztBQUNwRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsZUFBZSxFQUFFLGNBQWMsRUFBRSxlQUFlLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUN4RyxPQUFPLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxNQUFNLGVBQWUsQ0FBQyJ9
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A plain calendar date, `YYYY-MM-DD`. */
|
|
2
|
+
export type TIsoDate = string;
|
|
3
|
+
/**
|
|
4
|
+
* One half-year's base rate (Basiszinssatz, § 247 BGB) as the Deutsche
|
|
5
|
+
* Bundesbank publishes it: valid from 1 January or 1 July to the last day of
|
|
6
|
+
* that half-year, both inclusive.
|
|
7
|
+
*/
|
|
8
|
+
export interface IBaseRatePeriod {
|
|
9
|
+
validFrom: TIsoDate;
|
|
10
|
+
validTo: TIsoDate;
|
|
11
|
+
/** percent a year; it can be negative (2013 to 2022) */
|
|
12
|
+
ratePercent: number;
|
|
13
|
+
}
|
|
14
|
+
/** A span of days within one base-rate period, both days inclusive. */
|
|
15
|
+
export interface IBaseRateSpan {
|
|
16
|
+
from: TIsoDate;
|
|
17
|
+
to: TIsoDate;
|
|
18
|
+
ratePercent: number;
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fin.cx/opendata",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, and stock and crypto prices and fundamentals from several providers.",
|
|
5
|
+
"description": "Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, the base rate of § 247 BGB, and stock and crypto prices and fundamentals from several providers.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
7
7
|
"typings": "dist_ts/index.d.ts",
|
|
8
8
|
"exports": {
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"types": "./dist_ts/holidays/index.d.ts",
|
|
19
19
|
"import": "./dist_ts/holidays/index.js"
|
|
20
20
|
},
|
|
21
|
+
"./baserate": {
|
|
22
|
+
"types": "./dist_ts/baserate/index.d.ts",
|
|
23
|
+
"import": "./dist_ts/baserate/index.js"
|
|
24
|
+
},
|
|
21
25
|
"./package.json": "./package.json"
|
|
22
26
|
},
|
|
23
27
|
"type": "module",
|
|
@@ -27,8 +31,8 @@
|
|
|
27
31
|
"@git.zone/tsbuild": "^5.0.0",
|
|
28
32
|
"@git.zone/tsbundle": "^2.15.0",
|
|
29
33
|
"@git.zone/tsrun": "^3.0.0",
|
|
30
|
-
"@git.zone/tstest": "^6.
|
|
31
|
-
"@types/node": "^26.6.
|
|
34
|
+
"@git.zone/tstest": "^6.3.1",
|
|
35
|
+
"@types/node": "^26.6.2"
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
34
38
|
"@push.rocks/qenv": "^6.1.3",
|
|
@@ -77,7 +81,9 @@
|
|
|
77
81
|
"fundamentals",
|
|
78
82
|
"public holidays",
|
|
79
83
|
"Feiertage",
|
|
80
|
-
"working days"
|
|
84
|
+
"working days",
|
|
85
|
+
"Basiszinssatz",
|
|
86
|
+
"base rate"
|
|
81
87
|
],
|
|
82
88
|
"scripts": {
|
|
83
89
|
"test": "(tstest test/ --verbose)",
|
package/readme.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# @fin.cx/opendata
|
|
2
2
|
|
|
3
|
-
`@fin.cx/opendata` is a TypeScript toolbox for open business and market data: stock and crypto prices, SEC fundamentals, lookups in the German registers through the register portal of the federal states,
|
|
3
|
+
`@fin.cx/opendata` is a TypeScript toolbox for open business and market data: stock and crypto prices, SEC fundamentals, lookups in the German registers through the register portal of the federal states, the statutory public holidays of the German federal states, and the base rate of § 247 BGB. It is built for programmers who want scriptable APIs and real workflows instead of a thin wrapper around one remote endpoint.
|
|
4
4
|
|
|
5
|
-
It gives you
|
|
5
|
+
It gives you five practical lanes in one package:
|
|
6
6
|
|
|
7
7
|
- `StockPriceService` for stock and crypto prices with caching, retries, failover, historical ranges, and intraday support
|
|
8
8
|
- `FundamentalsService` and `StockDataService` for SEC fundamentals and combined price + fundamentals payloads
|
|
9
9
|
- `HandelsregisterClient`, from `@fin.cx/opendata/handelsregister`, for single lookups in the commercial, cooperative, partnership, association and civil-law partnership registers over plain HTTP, without any storage
|
|
10
10
|
- the holiday calendar, from `@fin.cx/opendata/holidays`, for the statutory public holidays of each German federal state and the working days of § 193 BGB, computed per year without data files or requests
|
|
11
|
+
- the base rate, from `@fin.cx/opendata/baserate`, for the Basiszinssatz of § 247 BGB per half-year as the Deutsche Bundesbank publishes it, for default interest under § 288 BGB
|
|
11
12
|
|
|
12
13
|
## Issue Reporting and Security
|
|
13
14
|
|
|
@@ -59,6 +60,16 @@ Everything here comes from `@fin.cx/opendata/holidays`. That entry point imports
|
|
|
59
60
|
Holidays and working days in the sense of § 193 BGB: neither a Saturday, nor a Sunday, nor a statutory public holiday.
|
|
60
61
|
- `easterSunday(year)`, `germanStates`, `findGermanState()`
|
|
61
62
|
|
|
63
|
+
### The Base Rate Of § 247 BGB
|
|
64
|
+
|
|
65
|
+
Everything here comes from `@fin.cx/opendata/baserate`. That entry point imports nothing else of the package, has no dependencies, and makes no request.
|
|
66
|
+
|
|
67
|
+
- `getBaseRate(date)`
|
|
68
|
+
The base rate in force on a day: its half-year period with the rate in percent a year.
|
|
69
|
+
- `splitByBaseRate(from, to)`
|
|
70
|
+
A span of days split where the rate changes, as interest under § 288 BGB is computed.
|
|
71
|
+
- `baseRatePeriods`, `baseRateFirstDay`, `baseRatesReadOn`, `baseRateSource`
|
|
72
|
+
|
|
62
73
|
## Before You Start
|
|
63
74
|
|
|
64
75
|
- No stock or fundamentals providers are pre-registered. You must register them before calling the services.
|
|
@@ -450,6 +461,13 @@ Four holidays apply only in part of a state:
|
|
|
450
461
|
- `holidayCalendarFirstYear`, `holidayLawsReadOn`
|
|
451
462
|
- `HolidayCalendarError` with the codes `invalid_date`, `unsupported_year`, `unknown_state` and `invalid_options`
|
|
452
463
|
|
|
464
|
+
### Base rate (`@fin.cx/opendata/baserate`)
|
|
465
|
+
|
|
466
|
+
- `getBaseRate(date)`
|
|
467
|
+
- `splitByBaseRate(from, to)`
|
|
468
|
+
- `baseRatePeriods`, `baseRateFirstDay`, `baseRatesReadOn`, `baseRateSource`
|
|
469
|
+
- `BaseRateError` with the codes `invalid_date`, `invalid_range`, `before_first_period` and `not_published`
|
|
470
|
+
|
|
453
471
|
## Data Sources
|
|
454
472
|
|
|
455
473
|
The package ships no market or register data. Prices and fundamentals come at run time from the providers you register (Marketstack, CoinGecko, SEC EDGAR), and register entries come at run time from the register portal of the federal states (handelsregister.de), one lookup at a time.
|
|
@@ -497,6 +515,8 @@ The holiday calendar is derived from the holiday laws of the federal states, rea
|
|
|
497
515
|
| Thüringen | [Thüringer Feier- und Gedenktagsgesetz (ThürFGtG)](https://landesrecht.thueringen.de/perma?j=FeiertG_TH_!_2) | § 2, § 10 Abs. 1 |
|
|
498
516
|
| all | [Bürgerliches Gesetzbuch](https://www.gesetze-im-internet.de/bgb/__193.html) | § 193 (Sonntag, Feiertag, Sonnabend) |
|
|
499
517
|
|
|
518
|
+
The base rate of `@fin.cx/opendata/baserate` (`ts/baserate/data.baserate.ts`) is the table "Basiszinssatz nach § 247 BGB" of the Deutsche Bundesbank (https://www.bundesbank.de/de/bundesbank/organisation/agb-und-regelungen/basiszinssatz-607820), which the Bundesbank publishes under § 247 Abs. 2 BGB, read on 2026-09-27 and reproduced unaltered: every half-year from 1 July 2002, the first row it lists, to the half-year from 1 July 2026 (1,52 %). The rate changes on 1 January and 1 July (§ 247 Abs. 1 Satz 2 BGB); a day after 31 December 2026 answers `not_published` until a release carries the next row.
|
|
519
|
+
|
|
500
520
|
## Behavior And Caveats
|
|
501
521
|
|
|
502
522
|
- Historical market data is cached aggressively because it is treated as immutable.
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/opendata',
|
|
6
|
-
version: '4.
|
|
7
|
-
description: 'Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, and stock and crypto prices and fundamentals from several providers.'
|
|
6
|
+
version: '4.2.0',
|
|
7
|
+
description: 'Open business and financial data for TypeScript: register lookups on the German register portal (handelsregister.de) without storage, the statutory public holidays of the German federal states, the base rate of § 247 BGB, and stock and crypto prices and fundamentals from several providers.'
|
|
8
8
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { BaseRateError } from './classes.baserateerror.js';
|
|
2
|
+
import { baseRateFirstDay, baseRatePeriods, baseRatesReadOn } from './data.baserate.js';
|
|
3
|
+
import type { IBaseRatePeriod, IBaseRateSpan, TIsoDate } from './interfaces.js';
|
|
4
|
+
|
|
5
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
6
|
+
|
|
7
|
+
const checkDate = (dateArg: TIsoDate): void => {
|
|
8
|
+
const match = typeof dateArg === 'string' ? ISO_DATE.exec(dateArg) : null;
|
|
9
|
+
const utc = match ? new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))) : null;
|
|
10
|
+
if (!match || !utc || utc.toISOString().slice(0, 10) !== dateArg) {
|
|
11
|
+
throw new BaseRateError('invalid_date', `${JSON.stringify(dateArg)} is not a calendar date written YYYY-MM-DD.`);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const addDay = (dateArg: TIsoDate): TIsoDate => {
|
|
16
|
+
const utc = new Date(`${dateArg}T00:00:00Z`);
|
|
17
|
+
utc.setUTCDate(utc.getUTCDate() + 1);
|
|
18
|
+
return utc.toISOString().slice(0, 10);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** The base rate in force on a day: the published period that contains it. */
|
|
22
|
+
export const getBaseRate = (dateArg: TIsoDate): IBaseRatePeriod => {
|
|
23
|
+
checkDate(dateArg);
|
|
24
|
+
if (dateArg < baseRateFirstDay) {
|
|
25
|
+
throw new BaseRateError('before_first_period', `${dateArg} lies before ${baseRateFirstDay}, the first day of the Bundesbank's table.`);
|
|
26
|
+
}
|
|
27
|
+
const period = baseRatePeriods.find((periodArg) => periodArg.validFrom <= dateArg && dateArg <= periodArg.validTo);
|
|
28
|
+
if (!period) {
|
|
29
|
+
throw new BaseRateError(
|
|
30
|
+
'not_published',
|
|
31
|
+
`${dateArg} lies after ${baseRatePeriods[baseRatePeriods.length - 1].validTo}; the base rate of its half-year was not published when the table was read on ${baseRatesReadOn}.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return period;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A span of days, both inclusive, split where the base rate changes: one piece per published
|
|
39
|
+
* period it touches, oldest first. Interest under § 288 BGB is computed per piece, since the rate
|
|
40
|
+
* of each half-year applies to the days in it.
|
|
41
|
+
*/
|
|
42
|
+
export const splitByBaseRate = (fromArg: TIsoDate, toArg: TIsoDate): IBaseRateSpan[] => {
|
|
43
|
+
checkDate(fromArg);
|
|
44
|
+
checkDate(toArg);
|
|
45
|
+
if (toArg < fromArg) {
|
|
46
|
+
throw new BaseRateError('invalid_range', `${toArg} lies before ${fromArg}.`);
|
|
47
|
+
}
|
|
48
|
+
const spans: IBaseRateSpan[] = [];
|
|
49
|
+
let day = fromArg;
|
|
50
|
+
while (day <= toArg) {
|
|
51
|
+
const period = getBaseRate(day);
|
|
52
|
+
const to = period.validTo < toArg ? period.validTo : toArg;
|
|
53
|
+
spans.push({ from: day, to, ratePercent: period.ratePercent });
|
|
54
|
+
day = addDay(to);
|
|
55
|
+
}
|
|
56
|
+
return spans;
|
|
57
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a question to the base-rate table was refused:
|
|
3
|
+
* - `invalid_date`: the value is not a calendar date written `YYYY-MM-DD`;
|
|
4
|
+
* - `invalid_range`: a span ends before it begins;
|
|
5
|
+
* - `before_first_period`: the day lies before the first period the table holds (1 July 2002);
|
|
6
|
+
* - `not_published`: the day lies after the half-year of the last period the table holds, whose
|
|
7
|
+
* rate the Bundesbank had not published when the table was read (`baseRatesReadOn`).
|
|
8
|
+
*/
|
|
9
|
+
export type TBaseRateErrorCode = 'invalid_date' | 'invalid_range' | 'before_first_period' | 'not_published';
|
|
10
|
+
|
|
11
|
+
/** The typed error of the base-rate table. */
|
|
12
|
+
export class BaseRateError extends Error {
|
|
13
|
+
public readonly code: TBaseRateErrorCode;
|
|
14
|
+
|
|
15
|
+
constructor(code: TBaseRateErrorCode, message: string) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'BaseRateError';
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base rate (Basiszinssatz) of § 247 BGB, as the Deutsche Bundesbank publishes it: "Gemäß
|
|
3
|
+
* § 247 Abs. 2 BGB ist die Deutsche Bundesbank verpflichtet, den aktuellen Stand des
|
|
4
|
+
* Basiszinssatzes im Bundesanzeiger zu veröffentlichen." It changes on 1 January and 1 July
|
|
5
|
+
* (§ 247 Abs. 1 Satz 2 BGB). The table below is the Bundesbank's table "Basiszinssatz nach § 247
|
|
6
|
+
* BGB" (https://www.bundesbank.de/de/bundesbank/organisation/agb-und-regelungen/basiszinssatz-607820),
|
|
7
|
+
* read on 2026-09-27, unaltered: every row from 1 July 2002, the first one it lists, to the
|
|
8
|
+
* current one. A later half-year needs a new release.
|
|
9
|
+
*/
|
|
10
|
+
import type { IBaseRatePeriod } from './interfaces.js';
|
|
11
|
+
|
|
12
|
+
/** the first day the table answers for */
|
|
13
|
+
export const baseRateFirstDay = '2002-07-01';
|
|
14
|
+
|
|
15
|
+
/** the day the Bundesbank's table was read; later half-years need a new release */
|
|
16
|
+
export const baseRatesReadOn = '2026-09-27';
|
|
17
|
+
|
|
18
|
+
/** the page the table was read from */
|
|
19
|
+
export const baseRateSource = 'https://www.bundesbank.de/de/bundesbank/organisation/agb-und-regelungen/basiszinssatz-607820';
|
|
20
|
+
|
|
21
|
+
const rows: ReadonlyArray<readonly [string, number]> = [
|
|
22
|
+
// gültig ab, Basiszinssatz in Prozent, as the table of the Deutsche Bundesbank states them
|
|
23
|
+
['2002-07-01', 2.47],
|
|
24
|
+
['2003-01-01', 1.97],
|
|
25
|
+
['2003-07-01', 1.22],
|
|
26
|
+
['2004-01-01', 1.14],
|
|
27
|
+
['2004-07-01', 1.13],
|
|
28
|
+
['2005-01-01', 1.21],
|
|
29
|
+
['2005-07-01', 1.17],
|
|
30
|
+
['2006-01-01', 1.37],
|
|
31
|
+
['2006-07-01', 1.95],
|
|
32
|
+
['2007-01-01', 2.70],
|
|
33
|
+
['2007-07-01', 3.19],
|
|
34
|
+
['2008-01-01', 3.32],
|
|
35
|
+
['2008-07-01', 3.19],
|
|
36
|
+
['2009-01-01', 1.62],
|
|
37
|
+
['2009-07-01', 0.12],
|
|
38
|
+
['2010-01-01', 0.12],
|
|
39
|
+
['2010-07-01', 0.12],
|
|
40
|
+
['2011-01-01', 0.12],
|
|
41
|
+
['2011-07-01', 0.37],
|
|
42
|
+
['2012-01-01', 0.12],
|
|
43
|
+
['2012-07-01', 0.12],
|
|
44
|
+
['2013-01-01', -0.13],
|
|
45
|
+
['2013-07-01', -0.38],
|
|
46
|
+
['2014-01-01', -0.63],
|
|
47
|
+
['2014-07-01', -0.73],
|
|
48
|
+
['2015-01-01', -0.83],
|
|
49
|
+
['2015-07-01', -0.83],
|
|
50
|
+
['2016-01-01', -0.83],
|
|
51
|
+
['2016-07-01', -0.88],
|
|
52
|
+
['2017-01-01', -0.88],
|
|
53
|
+
['2017-07-01', -0.88],
|
|
54
|
+
['2018-01-01', -0.88],
|
|
55
|
+
['2018-07-01', -0.88],
|
|
56
|
+
['2019-01-01', -0.88],
|
|
57
|
+
['2019-07-01', -0.88],
|
|
58
|
+
['2020-01-01', -0.88],
|
|
59
|
+
['2020-07-01', -0.88],
|
|
60
|
+
['2021-01-01', -0.88],
|
|
61
|
+
['2021-07-01', -0.88],
|
|
62
|
+
['2022-01-01', -0.88],
|
|
63
|
+
['2022-07-01', -0.88],
|
|
64
|
+
['2023-01-01', 1.62],
|
|
65
|
+
['2023-07-01', 3.12],
|
|
66
|
+
['2024-01-01', 3.62],
|
|
67
|
+
['2024-07-01', 3.37],
|
|
68
|
+
['2025-01-01', 2.27],
|
|
69
|
+
['2025-07-01', 1.27],
|
|
70
|
+
['2026-01-01', 1.27],
|
|
71
|
+
['2026-07-01', 1.52],
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
/** the last day of the half-year a period begins in */
|
|
75
|
+
const halfYearEndOf = (validFromArg: string): string =>
|
|
76
|
+
`${validFromArg.slice(0, 4)}-${validFromArg.slice(5, 7) === '01' ? '06-30' : '12-31'}`;
|
|
77
|
+
|
|
78
|
+
/** every published period, oldest first */
|
|
79
|
+
export const baseRatePeriods: readonly IBaseRatePeriod[] = Object.freeze(
|
|
80
|
+
rows.map(([validFrom, ratePercent]) => Object.freeze({ validFrom, validTo: halfYearEndOf(validFrom), ratePercent })),
|
|
81
|
+
);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// This entry point stands alone: it imports nothing from the rest of the package, has no
|
|
2
|
+
// dependencies and needs no storage or network.
|
|
3
|
+
export * from './interfaces.js';
|
|
4
|
+
export { BaseRateError, type TBaseRateErrorCode } from './classes.baserateerror.js';
|
|
5
|
+
export { baseRateFirstDay, baseRatePeriods, baseRateSource, baseRatesReadOn } from './data.baserate.js';
|
|
6
|
+
export { getBaseRate, splitByBaseRate } from './baserate.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** A plain calendar date, `YYYY-MM-DD`. */
|
|
2
|
+
export type TIsoDate = string;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One half-year's base rate (Basiszinssatz, § 247 BGB) as the Deutsche
|
|
6
|
+
* Bundesbank publishes it: valid from 1 January or 1 July to the last day of
|
|
7
|
+
* that half-year, both inclusive.
|
|
8
|
+
*/
|
|
9
|
+
export interface IBaseRatePeriod {
|
|
10
|
+
validFrom: TIsoDate;
|
|
11
|
+
validTo: TIsoDate;
|
|
12
|
+
/** percent a year; it can be negative (2013 to 2022) */
|
|
13
|
+
ratePercent: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A span of days within one base-rate period, both days inclusive. */
|
|
17
|
+
export interface IBaseRateSpan {
|
|
18
|
+
from: TIsoDate;
|
|
19
|
+
to: TIsoDate;
|
|
20
|
+
ratePercent: number;
|
|
21
|
+
}
|