/** * Authenticated fetch helpers for the new market-data proxy endpoints. * Follows the same pattern as profileApi.ts. */ import { getPlatformAccessToken } from './authSession'; import { tradingRuntime } from './runtime'; import { createRequestId } from '../../../shared/request-id.js'; async function apiGet(path: string): Promise { const token = await getPlatformAccessToken(); const res = await fetch(`${tradingRuntime.tradingApiUrl}${path}`, { headers: { Authorization: `Bearer ${token}`, 'x-request-id': createRequestId('web-market'), }, }); if (!res.ok) { const body = await res.json().catch(() => ({})) as any; throw new Error(body?.error ?? `Request failed (${res.status})`); } return res.json() as Promise; } // ── Chart bars ──────────────────────────────────────────────────────────────── export interface OHLCVBar { ts: number; open: number; high: number; low: number; close: number; volume: number; } export async function fetchChartBars( symbol: string, period: string, ): Promise { const data = await apiGet<{ bars: OHLCVBar[] }>( `/api/chart/bars?symbol=${encodeURIComponent(symbol)}&period=${encodeURIComponent(period)}`, ); return data.bars ?? []; } // ── Market indices ──────────────────────────────────────────────────────────── export interface IndexSnapshot { label: string; // 'S&P 500' | 'Dow Jones' | 'Nasdaq' symbol: string; // SPY | DIA | QQQ price: number; change: number; changePct:number; positive: boolean; } const INDEX_META: Record = { SPY: { label: 'S&P 500', symbol: 'SPY' }, DIA: { label: 'Dow Jones', symbol: 'DIA' }, QQQ: { label: 'Nasdaq', symbol: 'QQQ' }, }; export async function fetchMarketIndices(): Promise { // Backend returns Alpaca snapshot shape: { SPY: { latestTrade, dailyBar, prevDailyBar, ... }, ... } const data = await apiGet>('/api/market/indices'); return Object.entries(INDEX_META).map(([ticker, meta]) => { const snap = data[ticker]; const price = snap?.latestTrade?.p ?? snap?.latestQuote?.ap ?? 0; const prevClose = snap?.prevDailyBar?.c ?? 0; const change = prevClose > 0 ? price - prevClose : 0; const changePct = prevClose > 0 ? (change / prevClose) * 100 : 0; return { ...meta, price, change, changePct, positive: changePct >= 0, }; }); } // ── News ────────────────────────────────────────────────────────────────────── export interface NewsArticle { id?: string; url: string; headline: string; source: string; created_at: string; images?: Array<{ url: string; size?: string }>; } export async function fetchNews(symbol: string, limit = 8): Promise { const data = await apiGet<{ news: NewsArticle[] }>( `/api/news?symbols=${encodeURIComponent(symbol)}&limit=${limit}`, ); return data.news ?? []; } // ── Research ────────────────────────────────────────────────────────────────── export async function fetchResearchProfile(symbol: string): Promise { return apiGet(`/api/research/profile?symbol=${encodeURIComponent(symbol)}`); } export async function fetchResearchMetrics(symbol: string): Promise { return apiGet(`/api/research/metrics?symbol=${encodeURIComponent(symbol)}`); } export async function fetchResearchEarnings(symbol: string): Promise { const data = await apiGet<{ earnings: any[] }>( `/api/research/earnings?symbol=${encodeURIComponent(symbol)}`, ); return data.earnings ?? []; }