Inspiration

We were tracking our own investments across stocks, crypto, gold, REITs, which were spread across multiple brokers and exchanges, and the only "system" we had was a Google Sheet we manually updated every time prices moved or we made a trade. It didn't scale: live prices meant retyping numbers, multi-currency holdings meant manual FX conversions, and there was no way to tell whether a gain was because the asset went up or because the currency moved in our favour. We wanted something that did automatically what we were doing by hand, so we built it.

What it does

Here's the problem in one example: say you hold a US stock that's flat in USD this month, but the dollar weakened against SGD. Open most portfolio trackers and you'll see a gain — and you'd be wrong to celebrate it, because none of that gain is the stock; all of it is currency. Vantage is the only tracker we could find that actually splits the two apart, for every holding, automatically:

assetGain = (currentPrice − buyPrice) × units × currentFxRate
fxGain    = buyPrice × units × (currentFxRate − buyFxRate)
totalGain = assetGain + fxGain

That decomposition runs on every position across stocks, ETFs, REITs, crypto, gold, and property, converting everything into a single base currency (SGD) so a 12% "gain" never secretly turns out to be an 11% currency swing and a 1% stock move. It's the difference between thinking you picked a winner and knowing whether you actually did.

Beyond that core feature, Vantage pulls live prices across 30+ global exchanges including a dedicated Singapore Exchange integration, reads broker PDF statements (FSMOne, DBS Vickers) directly into the add-holding form instead of manual entry, surfaces dividend yields and per-holding news sentiment, and includes an AI analyst tab — currently running on Claude — that comments on your portfolio grounded in real position data and 30-day price trends rather than guessing. An admin layer lets us toggle individual data providers on or off live and audits every change.

How we built it

The stack is Next.js 16 with the App Router, using React Server Components so the dashboard loads with data already hydrated instead of showing loading spinners. Supabase handles auth (magic link and Google OAuth) and Postgres with row-level security, so access control is enforced at the database layer, not just in app code. We picked SGD as the single internal unit of account, so every value is stored in one currency and converted client-side. This decision turned what could've been a tangle of conversion logic into one function call. For prices we stitched together EODHD, Yahoo Finance, SGX's own API, and CoinGecko, with GoldAPI for gold and Frankfurter for FX rates. For AI, the analyst route can switch between Anthropic and OpenRouter depending on admin-configured flags, with prompts built server-side and user data wrapped in escaped, clearly-delimited tags so it can't be mistaken for instructions.

Challenges we ran into

Getting date ranges to update correctly when new data arrived, without a visible flash of wrong dates, took digging into React's render-phase update pattern instead of relying on useEffect. Parsing broker statements was its own challenge, since different brokers (FSMOne, DBS Vickers) format trade confirmations completely differently, so we wrote a dispatcher that detects the broker first and runs a tailored parser. Securing the AI analyst route meant treating every piece of user data, be it holding names or free-text questions, as untrusted input that could try to hijack the prompt, which meant building sanitization and escaping layers before a single token reached the model. We also hit real Postgres permission gotchas: revoking column-level privileges does nothing if a table-level grant already covers it, and a security policy referencing its own table causes infinite recursion unless the check is wrapped in a SECURITY DEFINER function.

Accomplishments that we're proud of

We're proud of the FX-vs-asset gain decomposition, it's the single feature that actually answers the question we built this for, and it required getting the math (and the UI) right across every currency pair, not just one happy path. We're also proud of the admin and rate-limiting layer: a three-tier role hierarchy with a database-trigger lockout that prevents the last superadmin from ever being demoted, even under concurrent requests, plus an audit log and provider kill-switches that let us flip data sources off live without redeploying. And we're proud that every migration is idempotent. The same set of files applies cleanly to a brand-new database and to our live, slightly-drifted one.

What we learned

We learned that an admin role model isn't really binary. Once you ask "who can promote an admin," you need a strict hierarchy with its own rules, not just an is_admin flag. We learned to design failure modes deliberately once real money-costing calls were involved: most things fail open so a database hiccup doesn't lock a user out, but the AI analyst route fails closed, because an outage shouldn't turn into an unbounded bill. And we learned that prompt security isn't optional once user-supplied text reaches a model. Anything from a holding name to a free-text question needs to be sanitized and clearly separated from instructions before it's safe to send.

What's next for Vantage

We've got a CPF LIFE payout calculator partially wired up on the backend (Singapore's retirement payout rates by plan and start age) that needs a front-end home, and a persisted chat history type already defined for the analysis tab so questions and answers survive a page refresh instead of disappearing. Beyond that, we'd like to add more brokers to the PDF parser beyond FSMOne and DBS Vickers, support more base currencies, and explore portfolio-level alerts so users get notified about moves instead of having to go check.

Built With

+ 18 more
Share this project:

Updates

Submission history