Fitting 27 years of daily stock returns into one 600KB HTML file

Cooper Brown ยท October 10, 2026

PortfolioAutopsy is a portfolio risk report that runs entirely in the browser. You paste the positions CSV from your broker, and it replays today's weights through daily returns back to January 1999. Nothing leaves the tab. There is no API and no database, and the market data ships inside the page itself.

I'm a student at USC, and this post covers how the data fits and what that design costs.

The data format

The page holds one object with three fields.

const PACK = {
  dates: "YykAAGQpAABlKQAA...",   // base64 Int32Array, days since 1970-01-01
  series: {
    SPY:  { off: 1,  ret: "cgDxAM//SgCh/0z/..." },  // base64 Int16Array
    NVDA: { off: 14, ret: "..." },
    // 35 tickers in total
  },
  built: "2026-10-09"
};

Each trading day is an Int32 count of days since the Unix epoch. Each ticker stores daily returns as Int16 values in basis points, so a 1.14% day is 114. The off field says which date index the ticker's history starts at, because NVDA, VTI and SCHD all started trading at different times.

An Int16 holds plus or minus 32,767 basis points, which covers any single day these tickers have had. About 6,985 trading days times 35 tickers at 2 bytes each comes to under 500KB before base64, and around 585KB after.

Decoding

function b64bytes(s) {
  const bin = atob(s);
  const u = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) u[i] = bin.charCodeAt(i);
  return u;
}
const b64i16 = s => new Int16Array(b64bytes(s).buffer);
const b64i32 = s => new Int32Array(b64bytes(s).buffer);

function loadPack(p) {
  DATES = b64i32(p.dates);
  N = DATES.length;
  SER = {};
  for (const sym in p.series) {
    const o = p.series[sym].off, v = b64i16(p.series[sym].ret);
    const a = new Float64Array(N).fill(NaN);
    for (let i = 0; i < v.length; i++) a[o + i] = v[i] / 1e4;
    SER[sym] = a;
  }
}

Every series becomes a Float64Array aligned to the same date index, with NaN before the ticker existed. From there the report is plain array math. It builds a weighted daily return, compounds it into an equity curve, and then makes one pass for drawdown, one for rolling 12-month windows, and one per horizon for the share of windows that lost money.

Refreshing the data

A small Python script pulls adjusted closes, turns them into daily returns, rounds to basis points and appends them to the existing arrays. Before writing, it recomputes the last few days that already exist in the pack and checks them against the stored values. On the last refresh the overlap matched to the basis point, which told me the source and the adjustment method hadn't drifted.

What this design costs

The ticker list is fixed. A holding outside the 35 gets flagged as not analyzed instead of guessed at.

Rounding to a basis point adds up to roughly 1bp of error per day. Over decades the compounded drift is small next to the drawdown numbers the report cares about, but it isn't zero.

The hardest problem is fund inception. When a fund didn't exist yet, the current code spreads its weight across the holdings that did. That makes early windows for portfolios full of newer ETFs look like whatever older fund carried the weight, which can exaggerate or soften the worst case. I'm weighing two fixes. One starts the analysis at the date when every holding existed. The other substitutes a close proxy, such as SPY for a total market fund, before inception. If you've dealt with this in a backtester, I'd like to hear which one you trust.

Try it

It's free at portfolioautopsy.pages.dev, and the demo button loads a sample portfolio. Questions about the data packing are welcome in the comments.

Try it at portfolioautopsy.pages.dev. The demo button loads a sample portfolio.