import { useCallback, useMemo } from "react"; import { Box } from "../../../ui"; import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState, useShortcut, useUpdatedAgo } from "../../../public/react"; import { CompositeChart, CurveSurface, EmptyState, MarketBoardStack, PaneStatusBody, StatGrid, statGridRows, Tabs, usePaneHeaderTabs, usePaneNoticeFooter, usePaneStatusLinkFooter, type MarketBoardRow, type StatItem } from "../../../components"; import { colors } from "../../../theme/colors"; import { useThemeColors } from "../../../theme/theme-context"; import { ApiRequestError } from "../../../api-client/errors"; import type { MoneyMarketRow } from "../../../api-client/money-markets"; import { staticSeries } from "../../../components/chart/static/series"; import type { PaneProps } from "../../../types/plugin"; import { isPlainKey } from "../../../utils/keyboard"; import { useResearchCloudSession } from "../shared/research-cloud-session"; import { getCachedMoneyMarkets, loadMoneyMarkets } from "./client"; import { moneyMarketChange, moneyMarketCurves, moneyMarketHistory, moneyMarketNotices, moneyMarketRows, moneyMarketValue } from "./model"; const TABS = [{ value: "rates", label: "Rates" }, { value: "bills", label: "Bills" }, { value: "liquidity", label: "Liquidity" }]; const PANELS = [{ id: "main" }]; const clearDenied = (error: unknown) => error instanceof ApiRequestError && [401, 403].includes(error.status ?? 0); interface BoardRow extends MarketBoardRow { observation: MoneyMarketRow } function boardRow(row: MoneyMarketRow): BoardRow { return { id: row.id, label: row.label, value: row.value, valueText: moneyMarketValue(row.value, row.unit), change: row.change, changeText: moneyMarketChange(row.change, row.changeUnit), percentile: row.percentile.value, asOf: row.asOf, status: row.status, observation: row, history: moneyMarketHistory(row).flatMap((point) => point.value == null ? [] : [{ date: new Date(point.date), close: point.value }]), }; } function ObservationChart({ row, width, height, focused = false }: { row: MoneyMarketRow; width: number; height: number; focused?: boolean }) { const series = useMemo(() => [staticSeries(moneyMarketHistory(row).map((point) => ({ date: new Date(point.date), observedAt: new Date(point.date), value: point.value })), { id: row.id, label: row.label, color: colors.positive, calendarSpaced: true })], [row]); if (row.history.every((point) => point.value == null)) return ; return moneyMarketValue(value, row.unit)} remoteKind="money-market-history" />; } function ObservationDetail({ row, width, height, focused = false }: { row: MoneyMarketRow; width: number; height: number; focused?: boolean }) { const p = row.percentile; // The chart below shows the 1Y window, so the range carries no sample count or window dates. const items: StatItem[] = [ { id: "level", label: row.unit === "percent" ? "Rate" : "USD billions", value: moneyMarketValue(row.value, row.unit), detail: `${p.value == null ? "--" : p.value.toFixed(0)} pctl 1Y · ${row.asOf ?? "--"}` }, { id: "change", label: "Change", value: moneyMarketChange(row.change, row.changeUnit), detail: `since ${row.previousAsOf ?? "--"}` }, { id: "range", label: "1Y range", value: `${moneyMarketValue(p.min, row.unit)} to ${moneyMarketValue(p.max, row.unit)}` }, { id: "series", label: "FRED", value: row.sourceSeriesIds.join(", "), detail: row.frequency }, ]; const statRows = statGridRows(items, width); return point.value == null)} subject="history" emptyTitle="No history available."> ; } export function MoneyMarketsPane({ width, height, focused }: PaneProps) { const colors = useThemeColors(); const session = useResearchCloudSession(); const loader = useCallback((force: boolean) => loadMoneyMarkets(force), [session.requestKey]); const resource = useAsyncResource(loader, { initialData: getCachedMoneyMarkets, clearOnError: clearDenied }); const [tab, setTab] = usePaneSettingValue("tab", "rates"); const [selectedId, setSelectedId] = usePluginPaneState("selected", null); const [openId, setOpenId] = usePluginPaneState("open", null); const data = resource.data?.payload; const rows = useMemo(() => data ? moneyMarketRows(data, tab).map(boardRow) : [], [data, tab]); const curves = useMemo(() => data ? moneyMarketCurves(data, { current: colors.positive, ghosts: { "1W": colors.textMuted, "1M": colors.warning, "1Y": colors.textDim } }) : [], [data, colors]); const openRow = rows.find((row) => row.id === openId); const selected = openRow ?? rows.find((row) => row.id === selectedId); const slope = data?.billsCurve.slope; // The curve's slope is the Bills tab's summary figure; the legend dates the latest curve. const billsItems: StatItem[] = slope ? [{ id: "slope", label: "1Y-4W", value: slope.valueBps == null ? "--" : `${slope.valueBps > 0 ? "+" : ""}${slope.valueBps.toFixed(1)}bp`, detail: [`${slope.percentile.value == null ? "--" : slope.percentile.value.toFixed(0)} pctl 1Y`, slope.asOf && slope.asOf !== data?.billsCurve.asOf ? slope.asOf : null].filter(Boolean).join(" · ") }] : []; const rateChartRow = tab === "rates" ? (selected ?? rows[0])?.observation : undefined; const updatedAgo = useUpdatedAgo(resource.updatedAt); const tabsInHeader = usePaneHeaderTabs({ tabs: TABS, activeValue: tab, onSelect: setTab, focused }); const tabRows = tabsInHeader ? 0 : 1; const boardHeight = Math.max(3, Math.min(rows.length + 2, Math.floor((height - tabRows) * 0.45))); const curveHeight = Math.max(8, height - tabRows - boardHeight); useAutoRefresh(resource.updatedAt, resource.load); useShortcut((event) => { if (focused && isPlainKey(event, "r")) { event.preventDefault(); void resource.reload(); } }); usePaneNoticeFooter({ registrationId: "money-markets:notices", focused, notices: [...(data ? moneyMarketNotices(data) : []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] }); usePaneStatusLinkFooter({ registrationId: "money-markets", focused, loading: resource.loading, error: resource.error, url: selected?.observation.sourceUrl ?? null, showOpenHint: true, info: data ? [ ...(updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : []), ...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []), ] : [], }); return {!tabsInHeader && } {data ? } rootBefore={tab === "bills" ? <> point.seriesId === selected?.observation.seriesId)?.tenor ?? null} valueLabel="Discount yield (%)" formatValue={(value) => `${value.toFixed(2)}%`} formatX={(value) => `${(value * 12).toFixed(1)} months`} /> : tab === "liquidity" ? // Rates charts the selected funding rate's year, like Liquidity. : rateChartRow ? : undefined} /> : null} ; }