@actuarial-ts/core 0.1.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/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.md +133 -0
- package/dist/benktander.d.ts +45 -0
- package/dist/benktander.d.ts.map +1 -0
- package/dist/benktander.js +38 -0
- package/dist/benktander.js.map +1 -0
- package/dist/berquist.d.ts +14 -0
- package/dist/berquist.d.ts.map +1 -0
- package/dist/berquist.js +292 -0
- package/dist/berquist.js.map +1 -0
- package/dist/bf.d.ts +23 -0
- package/dist/bf.d.ts.map +1 -0
- package/dist/bf.js +83 -0
- package/dist/bf.js.map +1 -0
- package/dist/capping.d.ts +96 -0
- package/dist/capping.d.ts.map +1 -0
- package/dist/capping.js +207 -0
- package/dist/capping.js.map +1 -0
- package/dist/caseOutstanding.d.ts +80 -0
- package/dist/caseOutstanding.d.ts.map +1 -0
- package/dist/caseOutstanding.js +147 -0
- package/dist/caseOutstanding.js.map +1 -0
- package/dist/chainladder.d.ts +15 -0
- package/dist/chainladder.d.ts.map +1 -0
- package/dist/chainladder.js +81 -0
- package/dist/chainladder.js.map +1 -0
- package/dist/clark.d.ts +91 -0
- package/dist/clark.d.ts.map +1 -0
- package/dist/clark.js +490 -0
- package/dist/clark.js.map +1 -0
- package/dist/diagnostics.d.ts +80 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +359 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/discounting.d.ts +166 -0
- package/dist/discounting.d.ts.map +1 -0
- package/dist/discounting.js +210 -0
- package/dist/discounting.js.map +1 -0
- package/dist/elrMethods.d.ts +104 -0
- package/dist/elrMethods.d.ts.map +1 -0
- package/dist/elrMethods.js +151 -0
- package/dist/elrMethods.js.map +1 -0
- package/dist/factors.d.ts +19 -0
- package/dist/factors.d.ts.map +1 -0
- package/dist/factors.js +136 -0
- package/dist/factors.js.map +1 -0
- package/dist/fisherLange.d.ts +92 -0
- package/dist/fisherLange.d.ts.map +1 -0
- package/dist/fisherLange.js +224 -0
- package/dist/fisherLange.js.map +1 -0
- package/dist/freqSev.d.ts +57 -0
- package/dist/freqSev.d.ts.map +1 -0
- package/dist/freqSev.js +82 -0
- package/dist/freqSev.js.map +1 -0
- package/dist/ilf.d.ts +111 -0
- package/dist/ilf.d.ts.map +1 -0
- package/dist/ilf.js +449 -0
- package/dist/ilf.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/mack.d.ts +55 -0
- package/dist/mack.d.ts.map +1 -0
- package/dist/mack.js +244 -0
- package/dist/mack.js.map +1 -0
- package/dist/merzWuthrich.d.ts +29 -0
- package/dist/merzWuthrich.d.ts.map +1 -0
- package/dist/merzWuthrich.js +132 -0
- package/dist/merzWuthrich.js.map +1 -0
- package/dist/munichChainLadder.d.ts +101 -0
- package/dist/munichChainLadder.d.ts.map +1 -0
- package/dist/munichChainLadder.js +254 -0
- package/dist/munichChainLadder.js.map +1 -0
- package/dist/odpBootstrap.d.ts +65 -0
- package/dist/odpBootstrap.d.ts.map +1 -0
- package/dist/odpBootstrap.js +243 -0
- package/dist/odpBootstrap.js.map +1 -0
- package/dist/onlevel.d.ts +39 -0
- package/dist/onlevel.d.ts.map +1 -0
- package/dist/onlevel.js +104 -0
- package/dist/onlevel.js.map +1 -0
- package/dist/salvageSubro.d.ts +82 -0
- package/dist/salvageSubro.d.ts.map +1 -0
- package/dist/salvageSubro.js +100 -0
- package/dist/salvageSubro.js.map +1 -0
- package/dist/stochastic.d.ts +56 -0
- package/dist/stochastic.d.ts.map +1 -0
- package/dist/stochastic.js +99 -0
- package/dist/stochastic.js.map +1 -0
- package/dist/tail.d.ts +18 -0
- package/dist/tail.d.ts.map +1 -0
- package/dist/tail.js +121 -0
- package/dist/tail.js.map +1 -0
- package/dist/trend.d.ts +46 -0
- package/dist/trend.d.ts.map +1 -0
- package/dist/trend.js +99 -0
- package/dist/trend.js.map +1 -0
- package/dist/triangle.d.ts +19 -0
- package/dist/triangle.d.ts.map +1 -0
- package/dist/triangle.js +163 -0
- package/dist/triangle.js.map +1 -0
- package/dist/triangleAlgebra.d.ts +10 -0
- package/dist/triangleAlgebra.d.ts.map +1 -0
- package/dist/triangleAlgebra.js +97 -0
- package/dist/triangleAlgebra.js.map +1 -0
- package/dist/types.d.ts +280 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +79 -0
- package/dist/types.js.map +1 -0
- package/dist/ulae.d.ts +172 -0
- package/dist/ulae.d.ts.map +1 -0
- package/dist/ulae.js +164 -0
- package/dist/ulae.js.map +1 -0
- package/dist/util.d.ts +24 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +66 -0
- package/dist/util.js.map +1 -0
- package/package.json +66 -0
package/dist/onlevel.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ReservingError } from "./types.js";
|
|
2
|
+
import { isNum } from "./util.js";
|
|
3
|
+
/** ISO date -> fractional year (2021-07-01 -> ~2021.5). */
|
|
4
|
+
function dateToYearFraction(iso) {
|
|
5
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
|
|
6
|
+
if (!m) {
|
|
7
|
+
throw new ReservingError("BAD_DATE", `Not an ISO date: ${iso}`);
|
|
8
|
+
}
|
|
9
|
+
const year = Number(m[1]);
|
|
10
|
+
const start = Date.UTC(year, 0, 1);
|
|
11
|
+
const next = Date.UTC(year + 1, 0, 1);
|
|
12
|
+
const t = Date.UTC(year, Number(m[2]) - 1, Number(m[3]));
|
|
13
|
+
return year + (t - start) / (next - start);
|
|
14
|
+
}
|
|
15
|
+
/** Origin label -> [start, end) in fractional years ("2021" or "2021Q3"). */
|
|
16
|
+
function originInterval(origin) {
|
|
17
|
+
const year = Number(origin.slice(0, 4));
|
|
18
|
+
if (!Number.isInteger(year)) {
|
|
19
|
+
throw new ReservingError("BAD_ORIGIN", `Cannot parse an origin period from "${origin}"`);
|
|
20
|
+
}
|
|
21
|
+
const qMatch = /Q([1-4])$/.exec(origin);
|
|
22
|
+
if (qMatch) {
|
|
23
|
+
const q = Number(qMatch[1]);
|
|
24
|
+
return [year + (q - 1) / 4, year + q / 4];
|
|
25
|
+
}
|
|
26
|
+
return [year, year + 1];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Earned-premium density of the period [p0, p1) by policy WRITTEN time w:
|
|
30
|
+
* overlap of the earning interval [w, w+1] with [p0, p1). Integrated exactly
|
|
31
|
+
* over [a, b] (the density is piecewise linear with breakpoints at p0-1,
|
|
32
|
+
* p1-1, p0, p1).
|
|
33
|
+
*/
|
|
34
|
+
function earnedArea(p0, p1, a, b) {
|
|
35
|
+
const lo = Math.max(a, p0 - 1);
|
|
36
|
+
const hi = Math.min(b, p1);
|
|
37
|
+
if (hi <= lo)
|
|
38
|
+
return 0;
|
|
39
|
+
const density = (w) => Math.max(0, Math.min(p1, w + 1) - Math.max(p0, w));
|
|
40
|
+
// Integrate exactly across the linear segments.
|
|
41
|
+
const breaks = [p0 - 1, p1 - 1, p0, p1]
|
|
42
|
+
.filter((x) => x > lo && x < hi)
|
|
43
|
+
.sort((x, y) => x - y);
|
|
44
|
+
const knots = [lo, ...breaks, hi];
|
|
45
|
+
let area = 0;
|
|
46
|
+
for (let i = 0; i + 1 < knots.length; i++) {
|
|
47
|
+
const x0 = knots[i];
|
|
48
|
+
const x1 = knots[i + 1];
|
|
49
|
+
area += ((density(x0) + density(x1)) / 2) * (x1 - x0); // exact: linear segment
|
|
50
|
+
}
|
|
51
|
+
return area;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* On-level factors per origin period from a rate-change history. Changes
|
|
55
|
+
* effective before all data simply set the base level; an empty history
|
|
56
|
+
* yields factors of exactly 1.
|
|
57
|
+
*/
|
|
58
|
+
export function parallelogramOnLevel(origins, history) {
|
|
59
|
+
const warnings = [];
|
|
60
|
+
for (const rc of history) {
|
|
61
|
+
if (!isNum(rc.change) || rc.change <= -1) {
|
|
62
|
+
throw new ReservingError("BAD_RATE_CHANGE", "A rate change must be a number greater than -100%");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const changes = [...history]
|
|
66
|
+
.map((rc) => ({ at: dateToYearFraction(rc.effectiveDate), change: rc.change }))
|
|
67
|
+
.sort((a, b) => a.at - b.at);
|
|
68
|
+
// Rate-level eras: [eraStart_i, eraStart_{i+1}) at cumulative level_i.
|
|
69
|
+
const eras = [{ from: -Infinity, level: 1 }];
|
|
70
|
+
let level = 1;
|
|
71
|
+
for (const c of changes) {
|
|
72
|
+
level *= 1 + c.change;
|
|
73
|
+
eras.push({ from: c.at, level });
|
|
74
|
+
}
|
|
75
|
+
const currentLevel = level;
|
|
76
|
+
const rows = origins.map((origin) => {
|
|
77
|
+
const [p0, p1] = originInterval(origin);
|
|
78
|
+
const total = earnedArea(p0, p1, -Infinity, p1);
|
|
79
|
+
if (!(total > 0)) {
|
|
80
|
+
throw new ReservingError("BAD_ORIGIN", `Origin "${origin}" has no earnable area`);
|
|
81
|
+
}
|
|
82
|
+
let weighted = 0;
|
|
83
|
+
for (let i = 0; i < eras.length; i++) {
|
|
84
|
+
const from = eras[i].from;
|
|
85
|
+
const to = i + 1 < eras.length ? eras[i + 1].from : Infinity;
|
|
86
|
+
const share = earnedArea(p0, p1, from, to) / total;
|
|
87
|
+
weighted += share * eras[i].level;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
origin,
|
|
91
|
+
averageRateLevel: weighted,
|
|
92
|
+
onLevelFactor: currentLevel / weighted,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
if (changes.length > 0) {
|
|
96
|
+
const lastOriginEnd = Math.max(...origins.map((o) => originInterval(o)[1]));
|
|
97
|
+
const beyond = changes.filter((c) => c.at >= lastOriginEnd);
|
|
98
|
+
if (beyond.length > 0) {
|
|
99
|
+
warnings.push(`${beyond.length} rate change(s) effective after the last origin period only move the current level (nothing historical to restate)`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { rows, currentLevel, warnings };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=onlevel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onlevel.js","sourceRoot":"","sources":["../src/onlevel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ChainLadderResult, LdfSelections, Triangle, TriangleKind } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Salvage & subrogation: recovery triangles develop like loss triangles, so
|
|
4
|
+
* ultimate recoveries come from a chain ladder on cumulative RECEIVED
|
|
5
|
+
* recoveries with the caller's selected factors, and net results come from
|
|
6
|
+
* subtracting the recovery projection from a gross one, aligned by origin.
|
|
7
|
+
*
|
|
8
|
+
* Ground truth:
|
|
9
|
+
* - Recovery development is commonly SLOWER and LUMPIER than the loss
|
|
10
|
+
* development it offsets (recoveries lag settlement); every run says so in
|
|
11
|
+
* warnings, because borrowing loss LDFs for recoveries is the classic
|
|
12
|
+
* mistake this module exists to prevent.
|
|
13
|
+
* - `netOfRecoveries` never fabricates: an origin present on only one side
|
|
14
|
+
* is EXCLUDED with a warning, and both sides' totals cover only the
|
|
15
|
+
* aligned origins so net = gross - recoveries holds exactly within the
|
|
16
|
+
* result.
|
|
17
|
+
* - Algebra identity (tested): with the same selections on both triangles,
|
|
18
|
+
* netting the two projections equals a chain ladder on the cell-wise
|
|
19
|
+
* difference triangle (subtractTriangles), because CL is linear in the
|
|
20
|
+
* latest diagonal.
|
|
21
|
+
*/
|
|
22
|
+
export interface SalvageSubroOptions {
|
|
23
|
+
/** Selected recovery LDFs, one per development interval (null -> 1.000 with a warning). */
|
|
24
|
+
selected: LdfSelections["selected"];
|
|
25
|
+
tailFactor?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SalvageSubroRow {
|
|
28
|
+
origin: string;
|
|
29
|
+
latestAge: number;
|
|
30
|
+
/** Cumulative recoveries received as of the latest diagonal. */
|
|
31
|
+
receivedToDate: number;
|
|
32
|
+
/** Cumulative development factor from latestAge to ultimate. */
|
|
33
|
+
cdf: number;
|
|
34
|
+
ultimateRecoveries: number;
|
|
35
|
+
/** ultimate - received: recoveries still expected. */
|
|
36
|
+
futureRecoveries: number;
|
|
37
|
+
}
|
|
38
|
+
export interface SalvageSubroResult {
|
|
39
|
+
method: "salvageSubro";
|
|
40
|
+
basis: TriangleKind;
|
|
41
|
+
cdfs: number[];
|
|
42
|
+
percentDeveloped: number[];
|
|
43
|
+
rows: SalvageSubroRow[];
|
|
44
|
+
totals: {
|
|
45
|
+
receivedToDate: number;
|
|
46
|
+
ultimateRecoveries: number;
|
|
47
|
+
futureRecoveries: number;
|
|
48
|
+
};
|
|
49
|
+
warnings: string[];
|
|
50
|
+
}
|
|
51
|
+
/** Chain ladder on a cumulative received-recoveries triangle. */
|
|
52
|
+
export declare function runSalvageSubro(recoveries: Triangle, options: SalvageSubroOptions): SalvageSubroResult;
|
|
53
|
+
export interface NetOfRecoveriesRow {
|
|
54
|
+
origin: string;
|
|
55
|
+
grossUltimate: number;
|
|
56
|
+
ultimateRecoveries: number;
|
|
57
|
+
netUltimate: number;
|
|
58
|
+
grossUnpaid: number;
|
|
59
|
+
futureRecoveries: number;
|
|
60
|
+
netUnpaid: number;
|
|
61
|
+
}
|
|
62
|
+
export interface NetOfRecoveriesResult {
|
|
63
|
+
method: "netOfRecoveries";
|
|
64
|
+
basis: TriangleKind;
|
|
65
|
+
rows: NetOfRecoveriesRow[];
|
|
66
|
+
totals: {
|
|
67
|
+
grossUltimate: number;
|
|
68
|
+
ultimateRecoveries: number;
|
|
69
|
+
netUltimate: number;
|
|
70
|
+
grossUnpaid: number;
|
|
71
|
+
futureRecoveries: number;
|
|
72
|
+
netUnpaid: number;
|
|
73
|
+
};
|
|
74
|
+
warnings: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Nets a gross chain ladder against a recovery projection, aligned by origin
|
|
78
|
+
* label. Origins missing on either side are warned and EXCLUDED - never
|
|
79
|
+
* zero-filled - so the totals net exactly over the aligned origins.
|
|
80
|
+
*/
|
|
81
|
+
export declare function netOfRecoveries(gross: ChainLadderResult, recoveries: SalvageSubroResult): NetOfRecoveriesResult;
|
|
82
|
+
//# sourceMappingURL=salvageSubro.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"salvageSubro.d.ts","sourceRoot":"","sources":["../src/salvageSubro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI3F;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,MAAM,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC;IACzF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,iEAAiE;AACjE,wBAAgB,eAAe,CAC7B,UAAU,EAAE,QAAQ,EACpB,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAwCpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,MAAM,EAAE;QACN,aAAa,EAAE,MAAM,CAAC;QACtB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,iBAAiB,EACxB,UAAU,EAAE,kBAAkB,GAC7B,qBAAqB,CAuEvB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { runChainLadder } from "./chainladder.js";
|
|
2
|
+
import { ReservingError } from "./types.js";
|
|
3
|
+
import { isNum } from "./util.js";
|
|
4
|
+
/** Chain ladder on a cumulative received-recoveries triangle. */
|
|
5
|
+
export function runSalvageSubro(recoveries, options) {
|
|
6
|
+
const warnings = [
|
|
7
|
+
"Salvage and subrogation typically develop more slowly and less smoothly than the losses they offset; select recovery factors from recovery experience rather than borrowing loss LDFs",
|
|
8
|
+
];
|
|
9
|
+
const negativeOrigins = recoveries.origins.filter((_, i) => recoveries.values[i].some((v) => isNum(v ?? null) && v < 0));
|
|
10
|
+
if (negativeOrigins.length > 0) {
|
|
11
|
+
warnings.push(`Negative cumulative recoveries for origin(s) ${negativeOrigins.join(", ")}; received salvage/subrogation should be non-negative - verify the data`);
|
|
12
|
+
}
|
|
13
|
+
const cl = runChainLadder(recoveries, {
|
|
14
|
+
selected: options.selected,
|
|
15
|
+
tailFactor: options.tailFactor ?? 1,
|
|
16
|
+
});
|
|
17
|
+
warnings.push(...cl.warnings);
|
|
18
|
+
return {
|
|
19
|
+
method: "salvageSubro",
|
|
20
|
+
basis: cl.basis,
|
|
21
|
+
cdfs: cl.cdfs,
|
|
22
|
+
percentDeveloped: cl.percentDeveloped,
|
|
23
|
+
rows: cl.rows.map((r) => ({
|
|
24
|
+
origin: r.origin,
|
|
25
|
+
latestAge: r.latestAge,
|
|
26
|
+
receivedToDate: r.latestValue,
|
|
27
|
+
cdf: r.cdf,
|
|
28
|
+
ultimateRecoveries: r.ultimate,
|
|
29
|
+
futureRecoveries: r.unpaid,
|
|
30
|
+
})),
|
|
31
|
+
totals: {
|
|
32
|
+
receivedToDate: cl.totals.latest,
|
|
33
|
+
ultimateRecoveries: cl.totals.ultimate,
|
|
34
|
+
futureRecoveries: cl.totals.unpaid,
|
|
35
|
+
},
|
|
36
|
+
warnings,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Nets a gross chain ladder against a recovery projection, aligned by origin
|
|
41
|
+
* label. Origins missing on either side are warned and EXCLUDED - never
|
|
42
|
+
* zero-filled - so the totals net exactly over the aligned origins.
|
|
43
|
+
*/
|
|
44
|
+
export function netOfRecoveries(gross, recoveries) {
|
|
45
|
+
const warnings = [];
|
|
46
|
+
const recoveryByOrigin = new Map(recoveries.rows.map((r) => [r.origin, r]));
|
|
47
|
+
const grossOrigins = new Set(gross.rows.map((r) => r.origin));
|
|
48
|
+
const rows = [];
|
|
49
|
+
const negativeNetOrigins = [];
|
|
50
|
+
for (const g of gross.rows) {
|
|
51
|
+
const rec = recoveryByOrigin.get(g.origin);
|
|
52
|
+
if (!rec) {
|
|
53
|
+
warnings.push(`Origin ${g.origin} has no recovery projection; excluded from the net results (never zero-filled)`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (g.latestAge !== rec.latestAge) {
|
|
57
|
+
warnings.push(`Origin ${g.origin}: gross projected from age ${g.latestAge} months but recoveries from age ${rec.latestAge}; the net mixes valuation dates`);
|
|
58
|
+
}
|
|
59
|
+
const row = {
|
|
60
|
+
origin: g.origin,
|
|
61
|
+
grossUltimate: g.ultimate,
|
|
62
|
+
ultimateRecoveries: rec.ultimateRecoveries,
|
|
63
|
+
netUltimate: g.ultimate - rec.ultimateRecoveries,
|
|
64
|
+
grossUnpaid: g.unpaid,
|
|
65
|
+
futureRecoveries: rec.futureRecoveries,
|
|
66
|
+
netUnpaid: g.unpaid - rec.futureRecoveries,
|
|
67
|
+
};
|
|
68
|
+
if (row.netUltimate < 0 || row.netUnpaid < 0)
|
|
69
|
+
negativeNetOrigins.push(g.origin);
|
|
70
|
+
rows.push(row);
|
|
71
|
+
}
|
|
72
|
+
for (const rec of recoveries.rows) {
|
|
73
|
+
if (!grossOrigins.has(rec.origin)) {
|
|
74
|
+
warnings.push(`Origin ${rec.origin} has a recovery projection but no gross result; excluded from the net results (never zero-filled)`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (rows.length === 0) {
|
|
78
|
+
throw new ReservingError("NO_DATA", "No origin appears in both the gross and recovery projections");
|
|
79
|
+
}
|
|
80
|
+
if (negativeNetOrigins.length > 0) {
|
|
81
|
+
warnings.push(`Projected recoveries exceed gross losses for origin(s) ${negativeNetOrigins.join(", ")}; the net result is negative - verify the recovery development`);
|
|
82
|
+
}
|
|
83
|
+
const totals = rows.reduce((acc, r) => ({
|
|
84
|
+
grossUltimate: acc.grossUltimate + r.grossUltimate,
|
|
85
|
+
ultimateRecoveries: acc.ultimateRecoveries + r.ultimateRecoveries,
|
|
86
|
+
netUltimate: acc.netUltimate + r.netUltimate,
|
|
87
|
+
grossUnpaid: acc.grossUnpaid + r.grossUnpaid,
|
|
88
|
+
futureRecoveries: acc.futureRecoveries + r.futureRecoveries,
|
|
89
|
+
netUnpaid: acc.netUnpaid + r.netUnpaid,
|
|
90
|
+
}), {
|
|
91
|
+
grossUltimate: 0,
|
|
92
|
+
ultimateRecoveries: 0,
|
|
93
|
+
netUltimate: 0,
|
|
94
|
+
grossUnpaid: 0,
|
|
95
|
+
futureRecoveries: 0,
|
|
96
|
+
netUnpaid: 0,
|
|
97
|
+
});
|
|
98
|
+
return { method: "netOfRecoveries", basis: gross.basis, rows, totals, warnings };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=salvageSubro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"salvageSubro.js","sourceRoot":"","sources":["../src/salvageSubro.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stochastic infrastructure: a seeded, reproducible RNG and the shared
|
|
3
|
+
* result shape every simulation-based method returns.
|
|
4
|
+
*
|
|
5
|
+
* Ground truth:
|
|
6
|
+
* - NO ambient randomness anywhere in the engine: every stochastic method
|
|
7
|
+
* takes an explicit integer seed, and the same seed + same inputs must
|
|
8
|
+
* reproduce the same output bit for bit (the reproducibility-bundle
|
|
9
|
+
* contract depends on it).
|
|
10
|
+
* - The generator is mulberry32: tiny, fast, well-distributed for
|
|
11
|
+
* simulation purposes. It is NOT cryptographic and does not need to be.
|
|
12
|
+
*/
|
|
13
|
+
export interface Rng {
|
|
14
|
+
/** Uniform on [0, 1). */
|
|
15
|
+
next(): number;
|
|
16
|
+
/** Standard normal (Box-Muller with cached spare). */
|
|
17
|
+
normal(): number;
|
|
18
|
+
/**
|
|
19
|
+
* Gamma(shape, scale = 1) via Marsaglia-Tsang squeeze (shape >= 1) with
|
|
20
|
+
* the Ahrens-Dieter boost for shape < 1.
|
|
21
|
+
*/
|
|
22
|
+
gamma(shape: number): number;
|
|
23
|
+
}
|
|
24
|
+
/** Deterministic seeded RNG. Same seed = same stream, forever. */
|
|
25
|
+
export declare function createRng(seed: number): Rng;
|
|
26
|
+
/** The percentiles every stochastic summary reports, as fractions. */
|
|
27
|
+
export declare const STANDARD_PERCENTILES: readonly [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99];
|
|
28
|
+
export interface StochasticSummary {
|
|
29
|
+
mean: number;
|
|
30
|
+
/** Sample standard deviation (n - 1). */
|
|
31
|
+
sd: number;
|
|
32
|
+
/** sd / |mean|; null when the mean is 0. */
|
|
33
|
+
cv: number | null;
|
|
34
|
+
/** Keyed "p50", "p75", ... per STANDARD_PERCENTILES; linear interpolation. */
|
|
35
|
+
percentiles: Record<string, number>;
|
|
36
|
+
}
|
|
37
|
+
export interface StochasticOriginResult {
|
|
38
|
+
origin: string;
|
|
39
|
+
summary: StochasticSummary;
|
|
40
|
+
}
|
|
41
|
+
export interface StochasticResult {
|
|
42
|
+
/** What the simulated quantity IS (e.g. "unpaid", "ultimate", "cdr"). */
|
|
43
|
+
quantity: string;
|
|
44
|
+
seed: number;
|
|
45
|
+
nSims: number;
|
|
46
|
+
total: StochasticSummary;
|
|
47
|
+
byOrigin: StochasticOriginResult[];
|
|
48
|
+
/** Total-level simulated values, ascending, for caller-side percentiles/plots. */
|
|
49
|
+
totalSamples: number[];
|
|
50
|
+
warnings: string[];
|
|
51
|
+
}
|
|
52
|
+
/** Linear-interpolated percentile of a SORTED ascending sample. */
|
|
53
|
+
export declare function percentileOfSorted(sorted: number[], p: number): number;
|
|
54
|
+
/** Summarizes a sample (sorts a copy; the input is not mutated). */
|
|
55
|
+
export declare function summarizeSample(values: number[]): StochasticSummary;
|
|
56
|
+
//# sourceMappingURL=stochastic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stochastic.d.ts","sourceRoot":"","sources":["../src/stochastic.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,GAAG;IAClB,yBAAyB;IACzB,IAAI,IAAI,MAAM,CAAC;IACf,sDAAsD;IACtD,MAAM,IAAI,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAwD3C;AAED,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,8DAA+D,CAAC;AAEjG,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,kFAAkF;IAClF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAYtE;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAiBnE"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ReservingError } from "./types.js";
|
|
2
|
+
import { isNum } from "./util.js";
|
|
3
|
+
/** Deterministic seeded RNG. Same seed = same stream, forever. */
|
|
4
|
+
export function createRng(seed) {
|
|
5
|
+
if (!Number.isInteger(seed)) {
|
|
6
|
+
throw new ReservingError("BAD_SEED", "The RNG seed must be an integer");
|
|
7
|
+
}
|
|
8
|
+
let a = seed >>> 0;
|
|
9
|
+
const next = () => {
|
|
10
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
11
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
12
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
13
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
14
|
+
};
|
|
15
|
+
let spare = null;
|
|
16
|
+
const normal = () => {
|
|
17
|
+
if (spare !== null) {
|
|
18
|
+
const v = spare;
|
|
19
|
+
spare = null;
|
|
20
|
+
return v;
|
|
21
|
+
}
|
|
22
|
+
// Box-Muller; u clamped away from 0 so log stays finite.
|
|
23
|
+
let u = next();
|
|
24
|
+
if (u < 1e-12)
|
|
25
|
+
u = 1e-12;
|
|
26
|
+
const v = next();
|
|
27
|
+
const r = Math.sqrt(-2 * Math.log(u));
|
|
28
|
+
const theta = 2 * Math.PI * v;
|
|
29
|
+
spare = r * Math.sin(theta);
|
|
30
|
+
return r * Math.cos(theta);
|
|
31
|
+
};
|
|
32
|
+
const gamma = (shape) => {
|
|
33
|
+
if (!isNum(shape) || shape <= 0) {
|
|
34
|
+
throw new ReservingError("BAD_SHAPE", "Gamma shape must be a positive number");
|
|
35
|
+
}
|
|
36
|
+
if (shape < 1) {
|
|
37
|
+
// Ahrens-Dieter boost: G(a) = G(a+1) * U^(1/a).
|
|
38
|
+
const u = Math.max(next(), 1e-12);
|
|
39
|
+
return gamma(shape + 1) * Math.pow(u, 1 / shape);
|
|
40
|
+
}
|
|
41
|
+
// Marsaglia & Tsang (2000).
|
|
42
|
+
const d = shape - 1 / 3;
|
|
43
|
+
const c = 1 / Math.sqrt(9 * d);
|
|
44
|
+
for (;;) {
|
|
45
|
+
let x;
|
|
46
|
+
let v;
|
|
47
|
+
do {
|
|
48
|
+
x = normal();
|
|
49
|
+
v = 1 + c * x;
|
|
50
|
+
} while (v <= 0);
|
|
51
|
+
v = v * v * v;
|
|
52
|
+
const u = next();
|
|
53
|
+
if (u < 1 - 0.0331 * x * x * x * x)
|
|
54
|
+
return d * v;
|
|
55
|
+
if (Math.log(Math.max(u, 1e-300)) < 0.5 * x * x + d * (1 - v + Math.log(v)))
|
|
56
|
+
return d * v;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return { next, normal, gamma };
|
|
60
|
+
}
|
|
61
|
+
/** The percentiles every stochastic summary reports, as fractions. */
|
|
62
|
+
export const STANDARD_PERCENTILES = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99];
|
|
63
|
+
/** Linear-interpolated percentile of a SORTED ascending sample. */
|
|
64
|
+
export function percentileOfSorted(sorted, p) {
|
|
65
|
+
if (sorted.length === 0) {
|
|
66
|
+
throw new ReservingError("NO_DATA", "Cannot take a percentile of an empty sample");
|
|
67
|
+
}
|
|
68
|
+
if (!isNum(p) || p < 0 || p > 1) {
|
|
69
|
+
throw new ReservingError("BAD_PERCENTILE", `Percentile must be in [0, 1] (got ${p})`);
|
|
70
|
+
}
|
|
71
|
+
const idx = p * (sorted.length - 1);
|
|
72
|
+
const lo = Math.floor(idx);
|
|
73
|
+
const hi = Math.ceil(idx);
|
|
74
|
+
if (lo === hi)
|
|
75
|
+
return sorted[lo];
|
|
76
|
+
return sorted[lo] + (idx - lo) * (sorted[hi] - sorted[lo]);
|
|
77
|
+
}
|
|
78
|
+
/** Summarizes a sample (sorts a copy; the input is not mutated). */
|
|
79
|
+
export function summarizeSample(values) {
|
|
80
|
+
if (values.length < 2) {
|
|
81
|
+
throw new ReservingError("TOO_SMALL", "A stochastic summary needs at least two simulations");
|
|
82
|
+
}
|
|
83
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
84
|
+
const n = sorted.length;
|
|
85
|
+
let sum = 0;
|
|
86
|
+
for (const v of sorted)
|
|
87
|
+
sum += v;
|
|
88
|
+
const mean = sum / n;
|
|
89
|
+
let ss = 0;
|
|
90
|
+
for (const v of sorted)
|
|
91
|
+
ss += (v - mean) ** 2;
|
|
92
|
+
const sd = Math.sqrt(ss / (n - 1));
|
|
93
|
+
const percentiles = {};
|
|
94
|
+
for (const p of STANDARD_PERCENTILES) {
|
|
95
|
+
percentiles[`p${Math.round(p * 100)}`] = percentileOfSorted(sorted, p);
|
|
96
|
+
}
|
|
97
|
+
return { mean, sd, cv: mean === 0 ? null : sd / Math.abs(mean), percentiles };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=stochastic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stochastic.js","sourceRoot":"","sources":["../src/stochastic.ts"],"names":[],"mappings":"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"}
|
package/dist/tail.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TailFit, TailMethod } from "./types.js";
|
|
2
|
+
export interface FitTailOptions {
|
|
3
|
+
method: TailMethod;
|
|
4
|
+
/**
|
|
5
|
+
* Selected LDFs by development column (from ages[j] to ages[j+1]);
|
|
6
|
+
* null entries are skipped.
|
|
7
|
+
*/
|
|
8
|
+
selectedLdfs: (number | null)[];
|
|
9
|
+
/** Cap on extrapolated development periods (default 200). */
|
|
10
|
+
maxHorizon?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function fitTail(options: FitTailOptions): TailFit;
|
|
13
|
+
/** Fits both supported curves so the user can compare them side by side. */
|
|
14
|
+
export declare function fitAllTails(selectedLdfs: (number | null)[]): {
|
|
15
|
+
exponentialDecay: TailFit;
|
|
16
|
+
inversePower: TailFit;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=tail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tail.d.ts","sourceRoot":"","sources":["../src/tail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwBtD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,YAAY,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAChC,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CA8GxD;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CACzB,YAAY,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAC9B;IAAE,gBAAgB,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAKtD"}
|
package/dist/tail.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { isNum, ols } from "./util.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tail factor curve fitting per Sherman (1984) and Boor (2006).
|
|
4
|
+
*
|
|
5
|
+
* - Exponential decay: ln(LDF_j - 1) is linear in the development period index.
|
|
6
|
+
* - Inverse power: ln(LDF_j - 1) is linear in ln(period index).
|
|
7
|
+
*
|
|
8
|
+
* The fit runs on the SELECTED age-to-age factors. Guards:
|
|
9
|
+
* - fewer than MIN_POINTS usable points (factor > 1) -> invalid;
|
|
10
|
+
* - non-negative slope (growth instead of decay) -> invalid;
|
|
11
|
+
* - extrapolation is capped in horizon and in total tail size, with a loud
|
|
12
|
+
* warning when the cap binds (inverse power with slope > -1 diverges).
|
|
13
|
+
*/
|
|
14
|
+
const MIN_POINTS = 3;
|
|
15
|
+
/** Extrapolate at most this many development periods beyond the last age. */
|
|
16
|
+
const MAX_HORIZON = 200;
|
|
17
|
+
/** Stop extrapolating once the incremental factor is this close to 1. */
|
|
18
|
+
const CONVERGENCE_EPS = 1e-7;
|
|
19
|
+
/** A fitted tail beyond this bound is treated as divergent. */
|
|
20
|
+
const MAX_TAIL = 5;
|
|
21
|
+
export function fitTail(options) {
|
|
22
|
+
const { method, selectedLdfs } = options;
|
|
23
|
+
const maxHorizon = options.maxHorizon ?? MAX_HORIZON;
|
|
24
|
+
const warnings = [];
|
|
25
|
+
// x = 1-based development period index of each factor; y = ln(f - 1).
|
|
26
|
+
const xs = [];
|
|
27
|
+
const ys = [];
|
|
28
|
+
let skippedAtOrBelowOne = 0;
|
|
29
|
+
for (let j = 0; j < selectedLdfs.length; j++) {
|
|
30
|
+
const f = selectedLdfs[j] ?? null;
|
|
31
|
+
if (!isNum(f))
|
|
32
|
+
continue;
|
|
33
|
+
if (f <= 1) {
|
|
34
|
+
skippedAtOrBelowOne++;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const idx = j + 1;
|
|
38
|
+
xs.push(method === "exponentialDecay" ? idx : Math.log(idx));
|
|
39
|
+
ys.push(Math.log(f - 1));
|
|
40
|
+
}
|
|
41
|
+
if (skippedAtOrBelowOne > 0) {
|
|
42
|
+
warnings.push(`${skippedAtOrBelowOne} selected factor(s) at or below 1.000 were excluded from the fit (ln(f-1) undefined)`);
|
|
43
|
+
}
|
|
44
|
+
const invalid = (message) => ({
|
|
45
|
+
method,
|
|
46
|
+
intercept: NaN,
|
|
47
|
+
slope: NaN,
|
|
48
|
+
rSquared: NaN,
|
|
49
|
+
nPoints: xs.length,
|
|
50
|
+
extrapolatedFactors: [],
|
|
51
|
+
tailFactor: 1,
|
|
52
|
+
valid: false,
|
|
53
|
+
warnings: [...warnings, message],
|
|
54
|
+
});
|
|
55
|
+
if (xs.length < MIN_POINTS) {
|
|
56
|
+
return invalid(`Only ${xs.length} usable point(s); at least ${MIN_POINTS} factors above 1.000 are required to fit a tail curve`);
|
|
57
|
+
}
|
|
58
|
+
const fit = ols(xs, ys);
|
|
59
|
+
if (!fit)
|
|
60
|
+
return invalid("Degenerate regression (no variation in development ages)");
|
|
61
|
+
if (fit.slope >= 0) {
|
|
62
|
+
return invalid("Fitted curve grows with age instead of decaying; the selected factors do not support a tail extrapolation with this model");
|
|
63
|
+
}
|
|
64
|
+
// Extrapolate incremental factors beyond the last SELECTED column. Trailing
|
|
65
|
+
// null selections carry no development of their own (chain ladder treats
|
|
66
|
+
// them as 1.000), so the extrapolation must start right after the last
|
|
67
|
+
// non-null selection or the curve's predicted factors for those columns
|
|
68
|
+
// would silently vanish from the tail.
|
|
69
|
+
let lastIdx = 0; // 1-based index of the last non-null selection
|
|
70
|
+
for (let j = selectedLdfs.length - 1; j >= 0; j--) {
|
|
71
|
+
if (isNum(selectedLdfs[j] ?? null)) {
|
|
72
|
+
lastIdx = j + 1;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (lastIdx < selectedLdfs.length) {
|
|
77
|
+
warnings.push(`The last ${selectedLdfs.length - lastIdx} development column(s) have no selected factor; the fitted tail covers them via the curve (chain ladder would otherwise treat them as 1.000)`);
|
|
78
|
+
}
|
|
79
|
+
const extrapolatedFactors = [];
|
|
80
|
+
let tail = 1;
|
|
81
|
+
let converged = false;
|
|
82
|
+
for (let step = 1; step <= maxHorizon; step++) {
|
|
83
|
+
const idx = lastIdx + step;
|
|
84
|
+
const x = method === "exponentialDecay" ? idx : Math.log(idx);
|
|
85
|
+
const f = 1 + Math.exp(fit.intercept + fit.slope * x);
|
|
86
|
+
extrapolatedFactors.push(f);
|
|
87
|
+
tail *= f;
|
|
88
|
+
if (tail > MAX_TAIL) {
|
|
89
|
+
return invalid(`Extrapolated tail exceeded ${MAX_TAIL.toFixed(1)} after ${step} periods; the fit is divergent (inverse power with slope > -1 has an unbounded product). Enter a tail judgmentally instead.`);
|
|
90
|
+
}
|
|
91
|
+
if (f - 1 < CONVERGENCE_EPS) {
|
|
92
|
+
converged = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!converged) {
|
|
97
|
+
warnings.push(`Extrapolation stopped at the ${maxHorizon}-period horizon before the factors converged to 1.000; the tail is truncated and understated`);
|
|
98
|
+
}
|
|
99
|
+
if (fit.rSquared < 0.8) {
|
|
100
|
+
warnings.push(`Fit quality is weak (R-squared ${fit.rSquared.toFixed(3)}); treat this tail as indicative only`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
method,
|
|
104
|
+
intercept: fit.intercept,
|
|
105
|
+
slope: fit.slope,
|
|
106
|
+
rSquared: fit.rSquared,
|
|
107
|
+
nPoints: fit.n,
|
|
108
|
+
extrapolatedFactors,
|
|
109
|
+
tailFactor: tail,
|
|
110
|
+
valid: true,
|
|
111
|
+
warnings,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Fits both supported curves so the user can compare them side by side. */
|
|
115
|
+
export function fitAllTails(selectedLdfs) {
|
|
116
|
+
return {
|
|
117
|
+
exponentialDecay: fitTail({ method: "exponentialDecay", selectedLdfs }),
|
|
118
|
+
inversePower: fitTail({ method: "inversePower", selectedLdfs }),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=tail.js.map
|
package/dist/tail.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tail.js","sourceRoot":"","sources":["../src/tail.ts"],"names":[],"mappings":"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"}
|