Files
investor-flow/app/server/src/analysis/__tests__/seasonality.test.ts
T

70 lines
2.3 KiB
TypeScript
Raw Normal View History

import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import {
monthlyReturnsFromCandles,
aggregateMonthSeasonality,
buildSeasonalitySnapshot,
electionCycleYear,
upcomingSimpleEvents,
} from '../seasonality.ts';
function mkYear(year: number, monthlyCloses: number[]) {
// One candle per month end-ish
return monthlyCloses.map((c, i) => ({
ts: new Date(Date.UTC(year, i, 28)).toISOString(),
c,
}));
}
test('monthlyReturnsFromCandles skips incomplete current month', () => {
// Use fixed "now" via candles only in past years
const candles = [
...mkYear(2020, [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111]),
...mkYear(2021, [111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122]),
];
const monthly = monthlyReturnsFromCandles(candles, new Date(Date.UTC(2026, 0, 15)));
assert.ok(monthly.length >= 20);
assert.ok(monthly.every((m) => Number.isFinite(m.returnPct)));
});
test('aggregateMonthSeasonality has 12 months', () => {
const monthly = [
{ year: 2020, month: 1, returnPct: 2 },
{ year: 2021, month: 1, returnPct: -1 },
{ year: 2020, month: 5, returnPct: -3 },
];
const agg = aggregateMonthSeasonality(monthly);
assert.equal(agg.length, 12);
assert.equal(agg[0].sampleYears, 2);
assert.ok(Math.abs(agg[0].avgReturnPct - 0.5) < 0.01);
assert.equal(agg[0].winRate, 0.5);
});
test('electionCycleYear labels 2026 as midterm year', () => {
const c = electionCycleYear(2026);
assert.equal(c.yearInCycle, 2);
assert.ok(/midterm/i.test(c.label));
});
test('buildSeasonalitySnapshot marks current month', () => {
const candles = [
...mkYear(2019, Array.from({ length: 12 }, (_, i) => 100 + i)),
...mkYear(2020, Array.from({ length: 12 }, (_, i) => 110 + i)),
...mkYear(2021, Array.from({ length: 12 }, (_, i) => 120 + i)),
];
const snap = buildSeasonalitySnapshot('SPY', candles, new Date(Date.UTC(2026, 6, 15)));
assert.equal(snap.currentMonth, 7);
assert.equal(snap.symbol, 'SPY');
assert.ok(snap.months.length === 12);
assert.ok(snap.electionCycle.label.length > 0);
});
test('upcomingSimpleEvents uses plain language', () => {
const events = upcomingSimpleEvents();
assert.ok(events.length >= 3);
for (const e of events) {
assert.ok(e.plainWhy.length > 20);
assert.ok(!/you should buy|go long/i.test(e.plainWhy));
}
});