Harsh Shah All projects

Case study

DiamondHands

A research terminal for long-term investors. Backtest a portfolio of US stocks, ETFs and crypto over the last decade, project it forward with a Monte Carlo simulation, and paper-trade the idea before any real money moves.

Role
Sole developer, design to deploy
Period
Rebuilt April 2026 – present
Stack
Next.js 15, Supabase Postgres, Python ingest, GitHub Actions, Vercel
Status
Live · v0.1 · backtest needs no account

Visit site

The problem

"What if I had put money into this every month for ten years?" is the question a long-term investor actually has, and most free tools answer it badly. They take a lump sum, ignore contributions and rebalancing, report one headline return, and say nothing about what the number leaves out.

DiamondHands answers it with contributions, rebalancing and honest metrics, lets you push the same portfolio forward through thousands of simulated futures, and then lets you run it as a paper portfolio with a real transaction log.

What I built

A Next.js application on Vercel, a Supabase Postgres database, and a Python worker that GitHub Actions runs every night to pull end-of-day prices. Three parts carry the design.

How it works

Prices change once a night, so everything is arranged around that. Fetching prices happens on the server, in one request per question, with a URL built so that the CDN can answer the next person who asks the same thing. Computing the backtest stays in the browser, because the engine is pure, the data is already in the page, and the visitor's processor costs nothing.

nightly · 22:00 UTC per request · cached in the browser, then shared yahoo finance python ingest asset_prices /api/prices paged read one price matrix backtest engine equity curve, metrics save share page
Figure 1: one nightly write, one cacheable read per question, and the compute on the visitor's machine.

The metrics are defined to survive contributions. Compound annual growth is the mean daily log return net of that day's deposit, annualised over 252 trading days, rather than end value over start value, which a monthly deposit would inflate. Yearly and monthly returns both chain from the previous period's close, so every trading day belongs to exactly one period and the periods compound back to the total. Public share links render the result saved with the scenario, so a shared page is fast and never recomputes.

See it run

No video yet. The backtester is the demo and needs no account: diamondhands.space/backtest. Simulation is at diamondhands.space/simulate.

The hard part

A ten-year, three-ticker backtest charted a decade and used sixteen months of it.

The query asked Postgres, through Supabase's REST layer, for 7,296 daily prices. That layer caps a response at 1,000 rows, and it does not fail when it caps: it returns a normal 200 with a shorter body and a content-range header that nothing was reading. The engine received 1,000 rows, from May 2016 to August 2017, and charted them as if they were the whole decade.

The fix was a helper that pages every price read until a page comes back short, and one detail made the difference between fixed and nearly fixed. Paging by offset over rows ordered by date alone lets rows that share a date swap places between requests, which silently duplicates some prices and drops others at the page boundaries. The reads now order by date and asset, so every row has one position.

The same lesson came back twice more. The nightly price job logged success for eighteen months on a renamed crypto ticker, because Yahoo answers a request for a dead symbol by repeating its last price; the job now checks that each price actually advanced and fails the run by name when one has not. And the yearly returns table left the move between the last trading day of December and the first of January out of every year, by up to 1.77 percentage points, which only surfaced when a separately written monthly heatmap disagreed with it.

Numbers

Only numbers that can be checked, each next to what produced it.

264unit tests, 19 files, all passing (vitest, 16 Sep 2026)
161active tickers priced nightly
7,296rows a decade backtest reads, where 1,000 arrived
3modes: backtest, simulate, paper trade

The tests cover the three pure engines, and every expected figure in them is computed by hand from the fixture rather than recorded from an earlier run, so they prove the maths is right, not merely unchanged.

What I would do differently