SoftTraderFrontend/lib/stockApi.ts
2026-03-20 11:10:13 -04:00

70 lines
2.3 KiB
TypeScript

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<MappedQuote> {
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;