// Investor Flow — yfinance-adjustments // Pure parser for yahoo-finance2 v3 chart `events` payloads into PriceAdjustment rows. // Zero runtime dependencies: this module only reads JSON-shaped input and emits objects. /** * A single corporate-action adjustment row produced by parseAdjustments(). * * - `symbol` is the ticker the chart was fetched for. * - `exDate` is the event date in YYYY-MM-DD (UTC, derived from ISO input). * - `type` is either "split" or "dividend". * - `ratio`: * split → numerator / denominator (e.g. 4.0 for a 4:1 split). * dividend → amount per share in USD (e.g. 0.01). */ export interface PriceAdjustment { symbol: string; exDate: string; type: "split" | "dividend"; ratio: number; } type SplitEvent = { date: string; numerator: number; denominator: number; splitRatio?: string }; type DividendEvent = { date: string; amount: number }; /** * Parse a yahoo-finance2 v3 chart response's `events` object into a flat list of * PriceAdjustment rows, sorted by exDate ascending. * * The function is defensive: * - `chartJson.events` may be absent or null/undefined → return []. * - `splits` / `dividends` may be absent, null, an array, or a map of keyed objects. * Both shapes are handled via `Object.values()`. * - Entries missing a `date` are skipped. */ export function parseAdjustments( symbol: string, chartJson: Record, ): PriceAdjustment[] { const result: PriceAdjustment[] = []; const events = (chartJson.events as Record) ?? {}; emitFrom(events.splits, symbol, "split", result); emitFrom(events.dividends, symbol, "dividend", result); result.sort((a, b) => (a.exDate < b.exDate ? -1 : a.exDate > b.exDate ? 1 : 0)); return result; } function emitFrom( source: unknown, symbol: string, type: "split" | "dividend", out: PriceAdjustment[], ): void { if (!source) return; const entries: Record[] = Array.isArray(source) ? (source as Record[]) : (Object.values(source as Record) as Record[]); for (const entry of entries) { if (!entry || typeof entry !== "object") continue; if (type === "split") { const e = entry as SplitEvent; if (!e.date) continue; const numerator = typeof e.numerator === "number" ? e.numerator : 1; const denominator = typeof e.denominator === "number" && e.denominator !== 0 ? e.denominator : 1; out.push({ symbol, exDate: e.date.slice(0, 10), type: "split", ratio: numerator / denominator, }); } else { const e = entry as DividendEvent; if (!e.date) continue; const amount = typeof e.amount === "number" ? e.amount : 0; out.push({ symbol, exDate: e.date.slice(0, 10), type: "dividend", ratio: amount, }); } } }