70 lines
2.3 KiB
TypeScript
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;
|