const API_BASE = "https://api.dongfeng-systems.org"; // ── Raw shape returned by the API ──────────────────────────────────────────── export interface LiveQuote { symbol: string; lastPrice: number; volume: number | null; previousClose: number; openPrice: number | null; marketOpen?: boolean; } // ── Derived/mapped shape used by every UI component ────────────────────────── export interface MappedQuote { symbol: string; lastPrice: number; volume: number | null; previousClose: number; openPrice: number | null; marketOpen: boolean; /** lastPrice - previousClose */ priceChange: number; /** ((lastPrice - previousClose) / previousClose) * 100 */ percentChange: number; } /** * Format a value that may be null when markets are closed. * Returns "Markets closed" if marketOpen is false or value is null/undefined. */ export function formatMarketValue( value: number | null | undefined, marketOpen: boolean, formatter: (v: number) => string ): string { if (!marketOpen || value == null) { return "Markets closed"; } return formatter(value); } /** Single transformation applied once; all components share this output type. */ export function mapQuote(raw: LiveQuote): MappedQuote { const priceChange = raw.lastPrice - raw.previousClose; const percentChange = raw.previousClose !== 0 ? (priceChange / raw.previousClose) * 100 : 0; return { ...raw, marketOpen: raw.marketOpen ?? true, priceChange, percentChange, }; } // ── Fetch helpers ───────────────────────────────────────────────────────────── export async function getLiveQuote(symbol: string): Promise { const res = await fetch(`${API_BASE}/api/market/live/${symbol}`, { cache: "no-store", }); if (!res.ok) throw new Error("Failed to fetch live quote"); const raw: LiveQuote = await res.json(); return mapQuote(raw); } // Alias kept so InterestsPage import is unchanged export const getWatchlistQuote = getLiveQuote; export type WatchlistQuote = MappedQuote;