fix: backfill symbol_demand for sidebar-added symbols + analyst ratings schema fix
- Add await ctx.cache.subscribe() to addSymbol mutation so symbols added via the sidebar get registered in symbol_demand and yfinance jobs are queued immediately - Backfill PEP, WYNN, STZ, CELH into symbol_demand + adapter_queue - Upgrade yahoo-finance2 3.15.3 -> 3.15.4 and pass validateResult:false to quoteSummary() to handle Yahoo schema drift - Add error detail logging for analyst ratings schema failures - Update .gitignore with common ignores
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
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));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user