@robotixai/calculator-engine 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 +21 -0
- package/README.md +127 -0
- package/dist/advanced.d.ts +24 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +747 -0
- package/dist/backtest.d.ts +28 -0
- package/dist/backtest.d.ts.map +1 -0
- package/dist/backtest.js +235 -0
- package/dist/defaults.d.ts +4 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +84 -0
- package/dist/heatmap.d.ts +38 -0
- package/dist/heatmap.d.ts.map +1 -0
- package/dist/heatmap.js +63 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/monte-carlo.d.ts +43 -0
- package/dist/monte-carlo.d.ts.map +1 -0
- package/dist/monte-carlo.js +178 -0
- package/dist/optimizer.d.ts +40 -0
- package/dist/optimizer.d.ts.map +1 -0
- package/dist/optimizer.js +134 -0
- package/dist/portfolio.d.ts +43 -0
- package/dist/portfolio.d.ts.map +1 -0
- package/dist/portfolio.js +86 -0
- package/dist/projection.d.ts +16 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +382 -0
- package/dist/sensitivity.d.ts +30 -0
- package/dist/sensitivity.d.ts.map +1 -0
- package/dist/sensitivity.js +92 -0
- package/dist/tax.d.ts +49 -0
- package/dist/tax.d.ts.map +1 -0
- package/dist/tax.js +210 -0
- package/dist/types.d.ts +250 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/withdrawal.d.ts +136 -0
- package/dist/withdrawal.d.ts.map +1 -0
- package/dist/withdrawal.js +241 -0
- package/package.json +33 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Scenario, Metrics } from './types';
|
|
2
|
+
export interface BacktestPeriod {
|
|
3
|
+
startYear: number;
|
|
4
|
+
endYear: number;
|
|
5
|
+
terminalReal: number;
|
|
6
|
+
survived: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface BacktestResult {
|
|
9
|
+
periods: BacktestPeriod[];
|
|
10
|
+
successRate: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Runs the scenario against historical Shiller data using rolling N-year windows.
|
|
14
|
+
*
|
|
15
|
+
* N = end_age - current_age (the full projection span).
|
|
16
|
+
* For each starting year where N years of data are available, the projection
|
|
17
|
+
* is run using historical real stock returns, and the terminal balance and
|
|
18
|
+
* survival status are recorded.
|
|
19
|
+
*
|
|
20
|
+
* @param scenario The base scenario (retirement_age, current_age, etc.)
|
|
21
|
+
* @param projectionFn A projection function that accepts a Scenario plus an
|
|
22
|
+
* array of annual real returns and produces Metrics.
|
|
23
|
+
* @returns Array of BacktestPeriod results and the overall success rate (0-100).
|
|
24
|
+
*/
|
|
25
|
+
export declare function runHistoricalBacktest(scenario: Scenario, projectionFn: (s: Scenario, returns: number[]) => {
|
|
26
|
+
metrics: Metrics;
|
|
27
|
+
}): BacktestResult;
|
|
28
|
+
//# sourceMappingURL=backtest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backtest.d.ts","sourceRoot":"","sources":["../src/backtest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA6KjD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GACrE,cAAc,CAoEhB"}
|
package/dist/backtest.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Historical Backtest — Shiller Data (real total stock returns, 1871-2024)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/**
|
|
5
|
+
* Embedded Shiller real total stock return data (inflation-adjusted).
|
|
6
|
+
* Source: Robert Shiller's dataset — real total return on S&P composite index.
|
|
7
|
+
* Values represent annual real (after-inflation) total returns including
|
|
8
|
+
* dividends, expressed as decimals (e.g., 0.1478 = 14.78%).
|
|
9
|
+
*/
|
|
10
|
+
const SHILLER_DATA = [
|
|
11
|
+
{ year: 1871, realStockReturn: 0.1478 },
|
|
12
|
+
{ year: 1872, realStockReturn: 0.1076 },
|
|
13
|
+
{ year: 1873, realStockReturn: -0.0415 },
|
|
14
|
+
{ year: 1874, realStockReturn: 0.1249 },
|
|
15
|
+
{ year: 1875, realStockReturn: 0.0555 },
|
|
16
|
+
{ year: 1876, realStockReturn: -0.0290 },
|
|
17
|
+
{ year: 1877, realStockReturn: 0.0187 },
|
|
18
|
+
{ year: 1878, realStockReturn: 0.1797 },
|
|
19
|
+
{ year: 1879, realStockReturn: 0.2102 },
|
|
20
|
+
{ year: 1880, realStockReturn: 0.2361 },
|
|
21
|
+
{ year: 1881, realStockReturn: 0.0118 },
|
|
22
|
+
{ year: 1882, realStockReturn: 0.0043 },
|
|
23
|
+
{ year: 1883, realStockReturn: -0.0041 },
|
|
24
|
+
{ year: 1884, realStockReturn: -0.0785 },
|
|
25
|
+
{ year: 1885, realStockReturn: 0.3034 },
|
|
26
|
+
{ year: 1886, realStockReturn: 0.1350 },
|
|
27
|
+
{ year: 1887, realStockReturn: -0.0122 },
|
|
28
|
+
{ year: 1888, realStockReturn: 0.0447 },
|
|
29
|
+
{ year: 1889, realStockReturn: 0.0511 },
|
|
30
|
+
{ year: 1890, realStockReturn: -0.0678 },
|
|
31
|
+
{ year: 1891, realStockReturn: 0.1920 },
|
|
32
|
+
{ year: 1892, realStockReturn: 0.0486 },
|
|
33
|
+
{ year: 1893, realStockReturn: -0.1716 },
|
|
34
|
+
{ year: 1894, realStockReturn: 0.0311 },
|
|
35
|
+
{ year: 1895, realStockReturn: 0.0520 },
|
|
36
|
+
{ year: 1896, realStockReturn: -0.0146 },
|
|
37
|
+
{ year: 1897, realStockReturn: 0.1768 },
|
|
38
|
+
{ year: 1898, realStockReturn: 0.2315 },
|
|
39
|
+
{ year: 1899, realStockReturn: 0.0178 },
|
|
40
|
+
{ year: 1900, realStockReturn: 0.1422 },
|
|
41
|
+
{ year: 1901, realStockReturn: 0.2007 },
|
|
42
|
+
{ year: 1902, realStockReturn: 0.0694 },
|
|
43
|
+
{ year: 1903, realStockReturn: -0.1724 },
|
|
44
|
+
{ year: 1904, realStockReturn: 0.3269 },
|
|
45
|
+
{ year: 1905, realStockReturn: 0.2206 },
|
|
46
|
+
{ year: 1906, realStockReturn: -0.0123 },
|
|
47
|
+
{ year: 1907, realStockReturn: -0.3274 },
|
|
48
|
+
{ year: 1908, realStockReturn: 0.4576 },
|
|
49
|
+
{ year: 1909, realStockReturn: 0.1805 },
|
|
50
|
+
{ year: 1910, realStockReturn: -0.0254 },
|
|
51
|
+
{ year: 1911, realStockReturn: 0.0399 },
|
|
52
|
+
{ year: 1912, realStockReturn: 0.0196 },
|
|
53
|
+
{ year: 1913, realStockReturn: -0.1087 },
|
|
54
|
+
{ year: 1914, realStockReturn: -0.0641 },
|
|
55
|
+
{ year: 1915, realStockReturn: 0.3197 },
|
|
56
|
+
{ year: 1916, realStockReturn: -0.0159 },
|
|
57
|
+
{ year: 1917, realStockReturn: -0.3180 },
|
|
58
|
+
{ year: 1918, realStockReturn: 0.1073 },
|
|
59
|
+
{ year: 1919, realStockReturn: 0.0759 },
|
|
60
|
+
{ year: 1920, realStockReturn: -0.1702 },
|
|
61
|
+
{ year: 1921, realStockReturn: 0.2315 },
|
|
62
|
+
{ year: 1922, realStockReturn: 0.2934 },
|
|
63
|
+
{ year: 1923, realStockReturn: 0.0540 },
|
|
64
|
+
{ year: 1924, realStockReturn: 0.2772 },
|
|
65
|
+
{ year: 1925, realStockReturn: 0.2808 },
|
|
66
|
+
{ year: 1926, realStockReturn: 0.1124 },
|
|
67
|
+
{ year: 1927, realStockReturn: 0.3727 },
|
|
68
|
+
{ year: 1928, realStockReturn: 0.4362 },
|
|
69
|
+
{ year: 1929, realStockReturn: -0.0885 },
|
|
70
|
+
{ year: 1930, realStockReturn: -0.2512 },
|
|
71
|
+
{ year: 1931, realStockReturn: -0.3887 },
|
|
72
|
+
{ year: 1932, realStockReturn: -0.0157 },
|
|
73
|
+
{ year: 1933, realStockReturn: 0.5701 },
|
|
74
|
+
{ year: 1934, realStockReturn: 0.0225 },
|
|
75
|
+
{ year: 1935, realStockReturn: 0.4577 },
|
|
76
|
+
{ year: 1936, realStockReturn: 0.3274 },
|
|
77
|
+
{ year: 1937, realStockReturn: -0.3788 },
|
|
78
|
+
{ year: 1938, realStockReturn: 0.2862 },
|
|
79
|
+
{ year: 1939, realStockReturn: 0.0230 },
|
|
80
|
+
{ year: 1940, realStockReturn: -0.0935 },
|
|
81
|
+
{ year: 1941, realStockReturn: -0.1792 },
|
|
82
|
+
{ year: 1942, realStockReturn: 0.1218 },
|
|
83
|
+
{ year: 1943, realStockReturn: 0.2275 },
|
|
84
|
+
{ year: 1944, realStockReturn: 0.1741 },
|
|
85
|
+
{ year: 1945, realStockReturn: 0.3413 },
|
|
86
|
+
{ year: 1946, realStockReturn: -0.2449 },
|
|
87
|
+
{ year: 1947, realStockReturn: -0.0434 },
|
|
88
|
+
{ year: 1948, realStockReturn: 0.0233 },
|
|
89
|
+
{ year: 1949, realStockReturn: 0.2115 },
|
|
90
|
+
{ year: 1950, realStockReturn: 0.2493 },
|
|
91
|
+
{ year: 1951, realStockReturn: 0.1458 },
|
|
92
|
+
{ year: 1952, realStockReturn: 0.1470 },
|
|
93
|
+
{ year: 1953, realStockReturn: -0.0124 },
|
|
94
|
+
{ year: 1954, realStockReturn: 0.5266 },
|
|
95
|
+
{ year: 1955, realStockReturn: 0.3139 },
|
|
96
|
+
{ year: 1956, realStockReturn: 0.0389 },
|
|
97
|
+
{ year: 1957, realStockReturn: -0.1401 },
|
|
98
|
+
{ year: 1958, realStockReturn: 0.4292 },
|
|
99
|
+
{ year: 1959, realStockReturn: 0.1055 },
|
|
100
|
+
{ year: 1960, realStockReturn: -0.0012 },
|
|
101
|
+
{ year: 1961, realStockReturn: 0.2638 },
|
|
102
|
+
{ year: 1962, realStockReturn: -0.1001 },
|
|
103
|
+
{ year: 1963, realStockReturn: 0.2055 },
|
|
104
|
+
{ year: 1964, realStockReturn: 0.1555 },
|
|
105
|
+
{ year: 1965, realStockReturn: 0.1040 },
|
|
106
|
+
{ year: 1966, realStockReturn: -0.1369 },
|
|
107
|
+
{ year: 1967, realStockReturn: 0.2084 },
|
|
108
|
+
{ year: 1968, realStockReturn: 0.0658 },
|
|
109
|
+
{ year: 1969, realStockReturn: -0.1464 },
|
|
110
|
+
{ year: 1970, realStockReturn: -0.0203 },
|
|
111
|
+
{ year: 1971, realStockReturn: 0.1068 },
|
|
112
|
+
{ year: 1972, realStockReturn: 0.1520 },
|
|
113
|
+
{ year: 1973, realStockReturn: -0.2343 },
|
|
114
|
+
{ year: 1974, realStockReturn: -0.3728 },
|
|
115
|
+
{ year: 1975, realStockReturn: 0.2896 },
|
|
116
|
+
{ year: 1976, realStockReturn: 0.1924 },
|
|
117
|
+
{ year: 1977, realStockReturn: -0.1248 },
|
|
118
|
+
{ year: 1978, realStockReturn: -0.0186 },
|
|
119
|
+
{ year: 1979, realStockReturn: 0.0574 },
|
|
120
|
+
{ year: 1980, realStockReturn: 0.1934 },
|
|
121
|
+
{ year: 1981, realStockReturn: -0.1269 },
|
|
122
|
+
{ year: 1982, realStockReturn: 0.1703 },
|
|
123
|
+
{ year: 1983, realStockReturn: 0.1871 },
|
|
124
|
+
{ year: 1984, realStockReturn: 0.0122 },
|
|
125
|
+
{ year: 1985, realStockReturn: 0.2810 },
|
|
126
|
+
{ year: 1986, realStockReturn: 0.1683 },
|
|
127
|
+
{ year: 1987, realStockReturn: 0.0192 },
|
|
128
|
+
{ year: 1988, realStockReturn: 0.1201 },
|
|
129
|
+
{ year: 1989, realStockReturn: 0.2662 },
|
|
130
|
+
{ year: 1990, realStockReturn: -0.0917 },
|
|
131
|
+
{ year: 1991, realStockReturn: 0.2654 },
|
|
132
|
+
{ year: 1992, realStockReturn: 0.0441 },
|
|
133
|
+
{ year: 1993, realStockReturn: 0.0720 },
|
|
134
|
+
{ year: 1994, realStockReturn: -0.0138 },
|
|
135
|
+
{ year: 1995, realStockReturn: 0.3452 },
|
|
136
|
+
{ year: 1996, realStockReturn: 0.1920 },
|
|
137
|
+
{ year: 1997, realStockReturn: 0.3128 },
|
|
138
|
+
{ year: 1998, realStockReturn: 0.2709 },
|
|
139
|
+
{ year: 1999, realStockReturn: 0.1826 },
|
|
140
|
+
{ year: 2000, realStockReturn: -0.1249 },
|
|
141
|
+
{ year: 2001, realStockReturn: -0.1347 },
|
|
142
|
+
{ year: 2002, realStockReturn: -0.2388 },
|
|
143
|
+
{ year: 2003, realStockReturn: 0.2637 },
|
|
144
|
+
{ year: 2004, realStockReturn: 0.0769 },
|
|
145
|
+
{ year: 2005, realStockReturn: 0.0146 },
|
|
146
|
+
{ year: 2006, realStockReturn: 0.1338 },
|
|
147
|
+
{ year: 2007, realStockReturn: 0.0111 },
|
|
148
|
+
{ year: 2008, realStockReturn: -0.3685 },
|
|
149
|
+
{ year: 2009, realStockReturn: 0.2646 },
|
|
150
|
+
{ year: 2010, realStockReturn: 0.1306 },
|
|
151
|
+
{ year: 2011, realStockReturn: -0.0183 },
|
|
152
|
+
{ year: 2012, realStockReturn: 0.1396 },
|
|
153
|
+
{ year: 2013, realStockReturn: 0.3069 },
|
|
154
|
+
{ year: 2014, realStockReturn: 0.1156 },
|
|
155
|
+
{ year: 2015, realStockReturn: 0.0065 },
|
|
156
|
+
{ year: 2016, realStockReturn: 0.0977 },
|
|
157
|
+
{ year: 2017, realStockReturn: 0.1930 },
|
|
158
|
+
{ year: 2018, realStockReturn: -0.0624 },
|
|
159
|
+
{ year: 2019, realStockReturn: 0.2880 },
|
|
160
|
+
{ year: 2020, realStockReturn: 0.1640 },
|
|
161
|
+
{ year: 2021, realStockReturn: 0.2168 },
|
|
162
|
+
{ year: 2022, realStockReturn: -0.2455 },
|
|
163
|
+
{ year: 2023, realStockReturn: 0.2214 },
|
|
164
|
+
{ year: 2024, realStockReturn: 0.2315 },
|
|
165
|
+
];
|
|
166
|
+
/**
|
|
167
|
+
* Runs the scenario against historical Shiller data using rolling N-year windows.
|
|
168
|
+
*
|
|
169
|
+
* N = end_age - current_age (the full projection span).
|
|
170
|
+
* For each starting year where N years of data are available, the projection
|
|
171
|
+
* is run using historical real stock returns, and the terminal balance and
|
|
172
|
+
* survival status are recorded.
|
|
173
|
+
*
|
|
174
|
+
* @param scenario The base scenario (retirement_age, current_age, etc.)
|
|
175
|
+
* @param projectionFn A projection function that accepts a Scenario plus an
|
|
176
|
+
* array of annual real returns and produces Metrics.
|
|
177
|
+
* @returns Array of BacktestPeriod results and the overall success rate (0-100).
|
|
178
|
+
*/
|
|
179
|
+
export function runHistoricalBacktest(scenario, projectionFn) {
|
|
180
|
+
const span = scenario.end_age - scenario.current_age;
|
|
181
|
+
// Guard: span must be at least 1
|
|
182
|
+
if (span < 1) {
|
|
183
|
+
return { periods: [], successRate: 0 };
|
|
184
|
+
}
|
|
185
|
+
const periods = [];
|
|
186
|
+
const firstYear = SHILLER_DATA[0].year;
|
|
187
|
+
const lastYear = SHILLER_DATA[SHILLER_DATA.length - 1].year;
|
|
188
|
+
const dataLength = lastYear - firstYear + 1;
|
|
189
|
+
// Number of rolling windows we can create
|
|
190
|
+
const windowCount = dataLength - span + 1;
|
|
191
|
+
if (windowCount < 1) {
|
|
192
|
+
// Projection span exceeds available data — return empty with a note
|
|
193
|
+
// (CONTRACT-005: "fewer periods returned; minimum 1 period required" —
|
|
194
|
+
// but if we truly can't make even 1 window, return empty.)
|
|
195
|
+
return { periods: [], successRate: 0 };
|
|
196
|
+
}
|
|
197
|
+
// Build a lookup map for O(1) access by year
|
|
198
|
+
const returnsByYear = new Map();
|
|
199
|
+
for (const entry of SHILLER_DATA) {
|
|
200
|
+
returnsByYear.set(entry.year, entry.realStockReturn);
|
|
201
|
+
}
|
|
202
|
+
let survivedCount = 0;
|
|
203
|
+
for (let i = 0; i < windowCount; i++) {
|
|
204
|
+
const startYear = firstYear + i;
|
|
205
|
+
const endYear = startYear + span - 1;
|
|
206
|
+
// Extract returns for this window
|
|
207
|
+
const returns = [];
|
|
208
|
+
for (let y = startYear; y <= endYear; y++) {
|
|
209
|
+
const r = returnsByYear.get(y);
|
|
210
|
+
if (r === undefined)
|
|
211
|
+
break;
|
|
212
|
+
returns.push(r);
|
|
213
|
+
}
|
|
214
|
+
// If we didn't get enough years (shouldn't happen given windowCount calc), skip
|
|
215
|
+
if (returns.length < span)
|
|
216
|
+
continue;
|
|
217
|
+
// Run projection with historical returns
|
|
218
|
+
const result = projectionFn(scenario, returns);
|
|
219
|
+
// Balance floor at 0 (CONTRACT-005 invariant)
|
|
220
|
+
const terminalReal = Math.max(0, result.metrics.terminal_real);
|
|
221
|
+
const survived = result.metrics.first_shortfall_age === null;
|
|
222
|
+
periods.push({
|
|
223
|
+
startYear,
|
|
224
|
+
endYear,
|
|
225
|
+
terminalReal,
|
|
226
|
+
survived,
|
|
227
|
+
});
|
|
228
|
+
if (survived)
|
|
229
|
+
survivedCount++;
|
|
230
|
+
}
|
|
231
|
+
const successRate = periods.length > 0
|
|
232
|
+
? (survivedCount / periods.length) * 100
|
|
233
|
+
: 0;
|
|
234
|
+
return { periods, successRate };
|
|
235
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMjD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAKrD,CAAC;AAMF,eAAO,MAAM,gBAAgB,EAAE,QAqF9B,CAAC"}
|
package/dist/defaults.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Engine Defaults — standalone default values (no Zod dependency)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Cadence Multiplier
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export const CadenceMultiplier = {
|
|
8
|
+
Annual: 1,
|
|
9
|
+
Monthly: 12,
|
|
10
|
+
'Bi-Weekly': 26,
|
|
11
|
+
Weekly: 52,
|
|
12
|
+
};
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Default Scenario
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
export const DEFAULT_SCENARIO = {
|
|
17
|
+
name: 'Scenario A',
|
|
18
|
+
// Timeline
|
|
19
|
+
current_age: 30,
|
|
20
|
+
retirement_age: 65,
|
|
21
|
+
end_age: 100,
|
|
22
|
+
// Portfolio
|
|
23
|
+
current_balance: 100000,
|
|
24
|
+
contrib_amount: 500,
|
|
25
|
+
contrib_cadence: 'Monthly',
|
|
26
|
+
contrib_increase_pct: 0,
|
|
27
|
+
// Returns
|
|
28
|
+
nominal_return_pct: 8,
|
|
29
|
+
return_stdev_pct: 15,
|
|
30
|
+
return_distribution: 'log-normal',
|
|
31
|
+
inflation_pct: 3,
|
|
32
|
+
inflation_enabled: true,
|
|
33
|
+
fee_pct: 0.5,
|
|
34
|
+
perf_fee_pct: 0,
|
|
35
|
+
// Withdrawal
|
|
36
|
+
withdrawal_method: 'Fixed % of prior-year end balance',
|
|
37
|
+
withdrawal_pct: 4,
|
|
38
|
+
withdrawal_real_amount: 50000,
|
|
39
|
+
withdrawal_frequency: 'Annual',
|
|
40
|
+
withdrawal_strategy: 'Standard',
|
|
41
|
+
// Guyton-Klinger
|
|
42
|
+
gk_ceiling_pct: 20,
|
|
43
|
+
gk_floor_pct: 20,
|
|
44
|
+
gk_prosperity_threshold: 20,
|
|
45
|
+
gk_capital_preservation_threshold: 20,
|
|
46
|
+
// Spending phases
|
|
47
|
+
spending_phases: [],
|
|
48
|
+
// Monte Carlo
|
|
49
|
+
enable_mc: true,
|
|
50
|
+
mc_runs: 1000,
|
|
51
|
+
// Tax
|
|
52
|
+
enable_taxes: false,
|
|
53
|
+
effective_tax_rate_pct: 0,
|
|
54
|
+
tax_jurisdiction: 'Custom',
|
|
55
|
+
tax_config: null,
|
|
56
|
+
tax_deferred_pct: 85,
|
|
57
|
+
// Planning
|
|
58
|
+
planning_mode: 'Individual',
|
|
59
|
+
partner_name: '',
|
|
60
|
+
partner_current_age: null,
|
|
61
|
+
partner_retirement_age: null,
|
|
62
|
+
partner_income_sources: [],
|
|
63
|
+
// Assets
|
|
64
|
+
assets: [],
|
|
65
|
+
liquid_pct: 100,
|
|
66
|
+
// Advanced
|
|
67
|
+
detail_mode: 'basic',
|
|
68
|
+
financial_items: [],
|
|
69
|
+
// Income
|
|
70
|
+
income_sources: [],
|
|
71
|
+
// Liquidity events
|
|
72
|
+
liquidity_events: [],
|
|
73
|
+
// Estate
|
|
74
|
+
desired_estate: 0,
|
|
75
|
+
// Black swan
|
|
76
|
+
black_swan_enabled: false,
|
|
77
|
+
black_swan_age: 70,
|
|
78
|
+
black_swan_loss_pct: 50,
|
|
79
|
+
// Currency
|
|
80
|
+
currency_code: 'USD',
|
|
81
|
+
currency_symbol: '$',
|
|
82
|
+
// Withdrawal order
|
|
83
|
+
withdrawal_order: 'Tax-Efficient',
|
|
84
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Scenario, Metrics } from './types';
|
|
2
|
+
export interface HeatmapCell {
|
|
3
|
+
retirementAge: number;
|
|
4
|
+
annualSpending: number;
|
|
5
|
+
viable: boolean;
|
|
6
|
+
terminalReal: number;
|
|
7
|
+
}
|
|
8
|
+
export interface HeatmapOptions {
|
|
9
|
+
/** [min, max] retirement age range. Defaults to [current_age+1, end_age-1]. */
|
|
10
|
+
retirementAgeRange?: [number, number];
|
|
11
|
+
/** [min, max] annual spending range. Defaults to [10_000, 120_000]. */
|
|
12
|
+
spendingRange?: [number, number];
|
|
13
|
+
/** Number of steps on each axis. Defaults to 10. */
|
|
14
|
+
steps?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generates a 2D grid of retirement_age x annual_spending cells.
|
|
18
|
+
*
|
|
19
|
+
* For each combination, the scenario is adjusted and a deterministic projection
|
|
20
|
+
* is run. Each cell records whether the scenario is viable (no shortfall and
|
|
21
|
+
* terminal_real >= desired_estate) along with the terminal_real value.
|
|
22
|
+
*
|
|
23
|
+
* The spending adjustment modifies:
|
|
24
|
+
* - withdrawal_pct (when using "Fixed % of prior-year end balance")
|
|
25
|
+
* - withdrawal_real_amount (when using "Fixed real-dollar amount")
|
|
26
|
+
*
|
|
27
|
+
* For percentage-based withdrawal, the spending value is treated as a dollar
|
|
28
|
+
* amount and the withdrawal_real_amount is set (method switched to fixed-dollar)
|
|
29
|
+
* so the heatmap consistently represents dollar-denominated spending levels.
|
|
30
|
+
*
|
|
31
|
+
* @param scenario Base scenario
|
|
32
|
+
* @param projectionFn Deterministic projection function
|
|
33
|
+
* @param options Optional axis ranges and step count
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateHeatmap(scenario: Scenario, projectionFn: (s: Scenario) => {
|
|
36
|
+
metrics: Metrics;
|
|
37
|
+
}, options?: HeatmapOptions): HeatmapCell[];
|
|
38
|
+
//# sourceMappingURL=heatmap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heatmap.d.ts","sourceRoot":"","sources":["../src/heatmap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAMjD,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,EACnD,OAAO,CAAC,EAAE,cAAc,GACvB,WAAW,EAAE,CAoDf"}
|
package/dist/heatmap.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-clone a scenario.
|
|
3
|
+
*/
|
|
4
|
+
function cloneScenario(s) {
|
|
5
|
+
return JSON.parse(JSON.stringify(s));
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generates a 2D grid of retirement_age x annual_spending cells.
|
|
9
|
+
*
|
|
10
|
+
* For each combination, the scenario is adjusted and a deterministic projection
|
|
11
|
+
* is run. Each cell records whether the scenario is viable (no shortfall and
|
|
12
|
+
* terminal_real >= desired_estate) along with the terminal_real value.
|
|
13
|
+
*
|
|
14
|
+
* The spending adjustment modifies:
|
|
15
|
+
* - withdrawal_pct (when using "Fixed % of prior-year end balance")
|
|
16
|
+
* - withdrawal_real_amount (when using "Fixed real-dollar amount")
|
|
17
|
+
*
|
|
18
|
+
* For percentage-based withdrawal, the spending value is treated as a dollar
|
|
19
|
+
* amount and the withdrawal_real_amount is set (method switched to fixed-dollar)
|
|
20
|
+
* so the heatmap consistently represents dollar-denominated spending levels.
|
|
21
|
+
*
|
|
22
|
+
* @param scenario Base scenario
|
|
23
|
+
* @param projectionFn Deterministic projection function
|
|
24
|
+
* @param options Optional axis ranges and step count
|
|
25
|
+
*/
|
|
26
|
+
export function generateHeatmap(scenario, projectionFn, options) {
|
|
27
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
28
|
+
const steps = (_a = options === null || options === void 0 ? void 0 : options.steps) !== null && _a !== void 0 ? _a : 10;
|
|
29
|
+
const ageMin = (_c = (_b = options === null || options === void 0 ? void 0 : options.retirementAgeRange) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : scenario.current_age + 1;
|
|
30
|
+
const ageMax = (_e = (_d = options === null || options === void 0 ? void 0 : options.retirementAgeRange) === null || _d === void 0 ? void 0 : _d[1]) !== null && _e !== void 0 ? _e : scenario.end_age - 1;
|
|
31
|
+
const spendMin = (_g = (_f = options === null || options === void 0 ? void 0 : options.spendingRange) === null || _f === void 0 ? void 0 : _f[0]) !== null && _g !== void 0 ? _g : 10000;
|
|
32
|
+
const spendMax = (_j = (_h = options === null || options === void 0 ? void 0 : options.spendingRange) === null || _h === void 0 ? void 0 : _h[1]) !== null && _j !== void 0 ? _j : 120000;
|
|
33
|
+
// Ensure at least 2 steps to avoid division by zero
|
|
34
|
+
const effectiveSteps = Math.max(steps, 2);
|
|
35
|
+
const ageStep = (ageMax - ageMin) / (effectiveSteps - 1);
|
|
36
|
+
const spendStep = (spendMax - spendMin) / (effectiveSteps - 1);
|
|
37
|
+
const cells = [];
|
|
38
|
+
const desiredEstate = (_k = scenario.desired_estate) !== null && _k !== void 0 ? _k : 0;
|
|
39
|
+
for (let ai = 0; ai < effectiveSteps; ai++) {
|
|
40
|
+
const retirementAge = Math.round(ageMin + ai * ageStep);
|
|
41
|
+
// Clamp retirement age to valid range
|
|
42
|
+
const clampedAge = Math.max(scenario.current_age + 1, Math.min(scenario.end_age - 1, retirementAge));
|
|
43
|
+
for (let si = 0; si < effectiveSteps; si++) {
|
|
44
|
+
const annualSpending = Math.round(spendMin + si * spendStep);
|
|
45
|
+
const s = cloneScenario(scenario);
|
|
46
|
+
s.retirement_age = clampedAge;
|
|
47
|
+
// Set spending as a fixed real-dollar amount for consistent comparison
|
|
48
|
+
s.withdrawal_method = 'Fixed real-dollar amount';
|
|
49
|
+
s.withdrawal_real_amount = Math.max(0, annualSpending);
|
|
50
|
+
const result = projectionFn(s);
|
|
51
|
+
const terminalReal = result.metrics.terminal_real;
|
|
52
|
+
const survived = result.metrics.first_shortfall_age === null;
|
|
53
|
+
const viable = survived && terminalReal >= desiredEstate;
|
|
54
|
+
cells.push({
|
|
55
|
+
retirementAge: clampedAge,
|
|
56
|
+
annualSpending,
|
|
57
|
+
viable,
|
|
58
|
+
terminalReal,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return cells;
|
|
63
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { Cadence, CurrencyCode, ContribStep, ProfitStep, RaiseStep, YieldStep, IncomeStep, LoanDraw, LumpRepayment, SpendingPhase, TaxConfig, IncomeSource, Asset, LiquidityEvent, FinancialItemCategory, FinancialItem, Scenario, TimelineRow, FanChartRow, Metrics, } from './types';
|
|
2
|
+
export { CadenceMultiplier, DEFAULT_SCENARIO } from './defaults';
|
|
3
|
+
export { runProjection } from './projection';
|
|
4
|
+
export { runAdvancedProjection } from './advanced';
|
|
5
|
+
export { runMonteCarloSimulation, type MCOptions, type MCResult, type ProjectionFn, } from './monte-carlo';
|
|
6
|
+
export { runSensitivityAnalysis, type SensitivityFactor, } from './sensitivity';
|
|
7
|
+
export { runHistoricalBacktest, type BacktestPeriod, type BacktestResult, } from './backtest';
|
|
8
|
+
export { findEarliestRetirementAge, type OptimizerResult, type OptimizerOutput, type OptimizerOptions, } from './optimizer';
|
|
9
|
+
export { generateHeatmap, type HeatmapCell, type HeatmapOptions, } from './heatmap';
|
|
10
|
+
export { blendPortfolio, calculateEstateValue, type BlendedPortfolio, } from './portfolio';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EACV,OAAO,EACP,YAAY,EACZ,WAAW,EACX,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,QAAQ,EACR,aAAa,EACb,aAAa,EACb,SAAS,EACT,YAAY,EACZ,KAAK,EACL,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,QAAQ,EACR,WAAW,EACX,WAAW,EACX,OAAO,GACR,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGjE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGnD,OAAO,EACL,uBAAuB,EACvB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,sBAAsB,EACtB,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,yBAAyB,EACzB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Engine — barrel export
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
export { CadenceMultiplier, DEFAULT_SCENARIO } from './defaults';
|
|
5
|
+
// Deterministic projection (basic & advanced)
|
|
6
|
+
export { runProjection } from './projection';
|
|
7
|
+
export { runAdvancedProjection } from './advanced';
|
|
8
|
+
// Monte Carlo simulation
|
|
9
|
+
export { runMonteCarloSimulation, } from './monte-carlo';
|
|
10
|
+
// Sensitivity (Tornado chart)
|
|
11
|
+
export { runSensitivityAnalysis, } from './sensitivity';
|
|
12
|
+
// Historical backtest (Shiller data)
|
|
13
|
+
export { runHistoricalBacktest, } from './backtest';
|
|
14
|
+
// Retirement age optimizer
|
|
15
|
+
export { findEarliestRetirementAge, } from './optimizer';
|
|
16
|
+
// Retirement age x spending heatmap
|
|
17
|
+
export { generateHeatmap, } from './heatmap';
|
|
18
|
+
// Portfolio blending & estate value
|
|
19
|
+
export { blendPortfolio, calculateEstateValue, } from './portfolio';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monte Carlo Simulation Engine
|
|
3
|
+
*
|
|
4
|
+
* Deterministic, seeded PRNG-based Monte Carlo runner for retirement projections.
|
|
5
|
+
* Generates randomized annual returns and delegates year-by-year calculation to
|
|
6
|
+
* a caller-provided projection function, keeping MC fully decoupled from the
|
|
7
|
+
* projection engine.
|
|
8
|
+
*/
|
|
9
|
+
import type { Scenario, TimelineRow, Metrics, FanChartRow } from './types';
|
|
10
|
+
export type ProjectionFn = (scenario: Scenario, overrideReturns?: number[]) => {
|
|
11
|
+
timeline: TimelineRow[];
|
|
12
|
+
metrics: Metrics;
|
|
13
|
+
};
|
|
14
|
+
export interface MCOptions {
|
|
15
|
+
/** Number of simulation runs. Default 1000, validated 100-10000. */
|
|
16
|
+
runs?: number;
|
|
17
|
+
/** PRNG seed for reproducibility. Default 42. */
|
|
18
|
+
seed?: number;
|
|
19
|
+
/** Wall-clock budget in ms before aborting. Default 50000. */
|
|
20
|
+
budgetMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface MCResult {
|
|
23
|
+
probability_no_shortfall: number;
|
|
24
|
+
median_terminal: number;
|
|
25
|
+
p10_terminal: number;
|
|
26
|
+
p90_terminal: number;
|
|
27
|
+
fan_chart: FanChartRow[];
|
|
28
|
+
terminal_distribution: number[];
|
|
29
|
+
runs_completed: number;
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare class SeededRNG {
|
|
33
|
+
private state;
|
|
34
|
+
constructor(seed?: number);
|
|
35
|
+
/** Returns a uniform random number in [0, 1). Mulberry32 algorithm. */
|
|
36
|
+
next(): number;
|
|
37
|
+
/** Returns a standard normal random variate via Box-Muller transform. */
|
|
38
|
+
gaussian(): number;
|
|
39
|
+
}
|
|
40
|
+
export declare function generateReturn(rng: SeededRNG, mean: number, stdev: number, distribution: 'log-normal' | 'normal'): number;
|
|
41
|
+
export declare function extractPercentile(sortedArray: number[], p: number): number;
|
|
42
|
+
export declare function runMonteCarloSimulation(scenario: Scenario, projectionFn: ProjectionFn, options?: MCOptions): MCResult;
|
|
43
|
+
//# sourceMappingURL=monte-carlo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monte-carlo.d.ts","sourceRoot":"","sources":["../src/monte-carlo.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAM3E,MAAM,MAAM,YAAY,GAAG,CACzB,QAAQ,EAAE,QAAQ,EAClB,eAAe,CAAC,EAAE,MAAM,EAAE,KACvB;IAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB;AAMD,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAS;gBAEV,IAAI,GAAE,MAAW;IAI7B,uEAAuE;IACvE,IAAI,IAAI,MAAM;IAQd,yEAAyE;IACzE,QAAQ,IAAI,MAAM;CAUnB;AAMD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,YAAY,GAAG,QAAQ,GACpC,MAAM,CAuBR;AAMD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1E;AAMD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,YAAY,EAC1B,OAAO,GAAE,SAAc,GACtB,QAAQ,CAoIV"}
|