import type { Metadata } from 'next'; import Link from 'next/link'; import { requireUser } from '@/lib/auth/session'; import { indexSeries, listCollections, loadItems, portfolioHistory } from '@/lib/account/queries'; import { rebase, summarizePortfolio } from '@/lib/account/portfolio'; import { getDisplay } from '@/lib/account/display'; import { INDICES, indexForCategory } from '@rareindex/taxonomy'; import { PageHeader, btnSecondary } from '@/components/account/page-header'; import { LineChart } from '@/components/account/charts'; import { Card, CardHeader, Delta, Stat } from '@/components/ui/primitives'; import { pctChange, volatility, maxDrawdown } from '@rareindex/shared'; import { fmtPct } from '@/lib/format'; export const metadata: Metadata = { title: 'My Index', robots: { index: false } }; /** Personal index: the member's portfolio value rebased to 1000, against RARE and the relevant subindices. */ export default async function MyIndexPage() { const u = await requireUser('/my-index'); const d = await getDisplay(); const cols = await listCollections(u.id); const items = await loadItems(cols.map((c) => c.collection.id)); const summary = summarizePortfolio(items); const history = await portfolioHistory(u.id); const since = history[0]?.date; const mine = rebase(history.map((h) => ({ date: h.date, value: h.valueUsd }))); const tickers = ['RARE', ...new Set(summary.allocationByCategory.map((b) => indexForCategory(b.key)?.ticker).filter((t): t is string => Boolean(t)))].slice(0, 5); const bench = await Promise.all(tickers.map(async (t) => ({ ticker: t, points: rebase(await indexSeries(t, since)) }))); const values = history.map((h) => h.valueUsd); const last = values[values.length - 1] ?? null; const nowMs = new Date().getTime(); const at = (days: number) => { if (!history.length) return null; const target = new Date(nowMs - days * 86_400_000).toISOString().slice(0, 10); const p = [...history].reverse().find((h) => h.date <= target); return p ? pctChange(p.valueUsd, last) : null; }; const vol = volatility(values, 365); const dd = maxDrawdown(values); return ( <> Collections} />
} /> } />
b.points.length > 1).map((b, i) => ({ name: b.ticker, points: b.points, color: INDICES.find((x) => x.ticker === b.ticker)?.color ?? `var(--ri-fg-subtle)`, dashed: i > 0 }))]} height={260} formatY={(v) => v.toFixed(0)} />
My index {bench.map((b) => ( x.ticker === b.ticker)?.color ?? 'var(--ri-fg-subtle)' }} /> {b.ticker} {b.points.length <= 1 ? (no history yet) : null} ))}

Your index reflects valuation changes and additions/removals; it is not a time-weighted return. Index values are published only when enough constituents are priced.

); }