| |
| |
| |
| |
| |
|
|
| import { mergeOptimizeResponse } from "@/lib/reportExport"; |
|
|
| const STORAGE_KEY = "ql-optimization-runs-v1"; |
| const MAX_RUNS = 100; |
|
|
| export type OptimizationRunSource = |
| | "executive_dashboard" |
| | "portfolio_lab" |
| | "quantum_engine"; |
|
|
| export interface StoredOptimizationRun { |
| id: string; |
| at: string; |
| source: OptimizationRunSource; |
| objective: string; |
| tickers: string[]; |
| constraints: { |
| weightMin: number; |
| weightMax: number; |
| kScreen?: string; |
| kSelect?: string; |
| }; |
| |
| payload: unknown; |
| } |
|
|
| function safeParse(raw: string | null): StoredOptimizationRun[] { |
| if (!raw) return []; |
| try { |
| const parsed = JSON.parse(raw) as unknown; |
| if (!Array.isArray(parsed)) return []; |
| return parsed.filter( |
| (x): x is StoredOptimizationRun => |
| x != null && |
| typeof x === "object" && |
| typeof (x as StoredOptimizationRun).id === "string" && |
| typeof (x as StoredOptimizationRun).at === "string" |
| ); |
| } catch { |
| return []; |
| } |
| } |
|
|
| export function getOptimizationRuns(): StoredOptimizationRun[] { |
| if (typeof window === "undefined") return []; |
| return safeParse(localStorage.getItem(STORAGE_KEY)); |
| } |
|
|
| export function getOptimizationRunById( |
| id: string |
| ): StoredOptimizationRun | null { |
| return getOptimizationRuns().find((r) => r.id === id) ?? null; |
| } |
|
|
| export function appendOptimizationRun( |
| data: Omit<StoredOptimizationRun, "id" | "source">, |
| source: OptimizationRunSource |
| ): StoredOptimizationRun { |
| const id = |
| typeof crypto !== "undefined" && crypto.randomUUID |
| ? crypto.randomUUID() |
| : `run-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; |
|
|
| const entry: StoredOptimizationRun = { ...data, id, source }; |
|
|
| if (typeof window === "undefined") return entry; |
|
|
| const prev = safeParse(localStorage.getItem(STORAGE_KEY)); |
| const next = [entry, ...prev.filter((r) => r.id !== id)].slice(0, MAX_RUNS); |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); |
| return entry; |
| } |
|
|
| export function clearOptimizationRuns(): void { |
| if (typeof window === "undefined") return; |
| localStorage.removeItem(STORAGE_KEY); |
| } |
|
|
| |
| export function formatOptimizationSource(s: OptimizationRunSource): string { |
| switch (s) { |
| case "executive_dashboard": |
| return "Executive Dashboard"; |
| case "portfolio_lab": |
| return "Portfolio Lab"; |
| case "quantum_engine": |
| return "Quantum Engine"; |
| default: |
| return s; |
| } |
| } |
|
|
| |
| export function summarizeStoredPayload(payload: unknown): { |
| sharpe: number | null; |
| retPct: number | null; |
| volPct: number | null; |
| nActive: number | null; |
| } { |
| const merged = mergeOptimizeResponse(payload) as Record<string, unknown>; |
| const sharpe = merged.sharpe_ratio; |
| const ret = merged.expected_return; |
| const vol = merged.volatility; |
| const n = merged.n_active; |
| return { |
| sharpe: typeof sharpe === "number" && Number.isFinite(sharpe) ? sharpe : null, |
| retPct: |
| typeof ret === "number" && Number.isFinite(ret) ? ret * 100 : null, |
| volPct: |
| typeof vol === "number" && Number.isFinite(vol) ? vol * 100 : null, |
| nActive: typeof n === "number" && Number.isFinite(n) ? n : null, |
| }; |
| } |
|
|