Money forecast: calm optimistic start, rising plateau, thin solid projected lines
All checks were successful
Deploy production / deploy (push) Successful in 1m13s
All checks were successful
Deploy production / deploy (push) Successful in 1m13s
- Optimistic: no crash or correction through 2031 (5 years from today), earning the 9.5% average; cycle resumes after (correction 2035, crash 2040). Salary plateau grows 2%/yr real so pay never stalls. - Realistic keeps its single -30% crash in 2029; pessimistic unchanged. - Projected lines are now thin, slightly lighter solid lines in every view; legend updated. - Explanation text lists the crash/correction years and recovery time.
This commit is contained in:
@@ -189,6 +189,49 @@ describe('runScenario', () => {
|
||||
expect(markets.yearReturn(2034).kind).toBe('correction');
|
||||
expect(markets.yearReturn(2028).kind).toBe('normal');
|
||||
});
|
||||
it('keeps the optimistic start calm and its cycle after that', () => {
|
||||
const markets = marketReturns(defaultScenarios.optimistic, 2026);
|
||||
for (let year = 2026; year <= 2031; year++)
|
||||
expect(markets.yearReturn(year)).toEqual({ kind: 'calm', rate: 0.095 });
|
||||
expect(markets.yearReturn(2032).kind).toBe('normal');
|
||||
expect(markets.yearReturn(2035)).toMatchObject({ kind: 'correction', rate: -0.15 });
|
||||
expect(markets.yearReturn(2040)).toMatchObject({ kind: 'crash', rate: -0.38 });
|
||||
// No down year at all in the first five years of the run.
|
||||
const run = runScenario(inputs, defaultScenarios.optimistic);
|
||||
const growth = run.years.filter((row) => row.year <= 2031).map((row) => row.invested);
|
||||
growth.slice(1).forEach((value, index) => expect(value).toBeGreaterThan(growth[index]));
|
||||
});
|
||||
it('gives realistic one crash in its first ten years and keeps pessimistic as it was', () => {
|
||||
const shocks = (id: 'realistic' | 'pessimistic') => {
|
||||
const markets = marketReturns(defaultScenarios[id], 2026);
|
||||
return Array.from({ length: 10 }, (_, offset) => 2026 + offset)
|
||||
.map((year) => ({ year, ...markets.yearReturn(year) }))
|
||||
.filter((row) => row.kind === 'crash');
|
||||
};
|
||||
expect(shocks('realistic')).toEqual([{ year: 2029, kind: 'crash', rate: -0.3 }]);
|
||||
expect(shocks('pessimistic').map((row) => row.year)).toEqual([2027, 2035]);
|
||||
expect(defaultScenarios.realistic.calmYears).toBe(0);
|
||||
expect(defaultScenarios.pessimistic.calmYears).toBe(0);
|
||||
// Without a start year the calm start never applies.
|
||||
expect(marketReturns(defaultScenarios.optimistic).yearReturn(2030).kind).toBe('crash');
|
||||
});
|
||||
it('keeps the optimistic salary rising every working year', () => {
|
||||
const long = { ...inputs, endDate: '2076-10-24' };
|
||||
const years = runScenario(long, defaultScenarios.optimistic).years.filter(
|
||||
(row) => row.year >= 2027 && row.role !== 'Retired'
|
||||
);
|
||||
expect(years.length).toBeGreaterThan(40);
|
||||
years
|
||||
.slice(1)
|
||||
.forEach((row, index) => expect(row.baseReal).toBeGreaterThan(years[index].baseReal));
|
||||
// A flat plateau stalls once it is reached.
|
||||
const flatCap = runScenario(long, {
|
||||
...defaultScenarios.optimistic,
|
||||
salaryCapGrowthPercent: 0
|
||||
});
|
||||
const late = flatCap.years.filter((row) => row.year >= 2050 && row.role !== 'Retired');
|
||||
expect(late.at(-1)!.baseReal).toBeCloseTo(late[0].baseReal, -2);
|
||||
});
|
||||
it('hops jobs, caps the salary and retires at the chosen age', () => {
|
||||
const long = { ...inputs, endDate: '2126-10-24' };
|
||||
const result = runScenario(long, {
|
||||
|
||||
Reference in New Issue
Block a user