Skip to content
Rahul Nainala
LiveOn my own · 202607 / 15

WealthPilot

A portfolio auditor that simulates every goal in a C++ Monte Carlo engine and flags what's off.

WealthPilot's overview: total value, risk figures, open issues, allocation and goals for the fictional demo portfolio
Overview: value, risk, what needs attention, allocation and every goal's odds.
backend tests
180
line coverage on the analytics engine
99%
tests on the C++20 simulation engine
34
writes the public demo accepts
0

Overview

WealthPilot reads a Zerodha account through Kite Connect and answers the question the broker's app can't: will each goal make it? The analytics engine is plain Python functions with no framework in them, so it's tested on its own. The one CPU-heavy job, simulating thousands of market paths per goal, runs in a C++20 service behind gRPC instead of in the request path. Around it: a daily snapshot job, a live price stream, and an access token that expires every day and is detected and recovered instead of failing. The public demo runs on a fictional investor, read-only.

More screens

WealthPilot's goals tab: targets, monthly SIP, a budget optimiser and per-goal progress
Goals: judged on the simulated odds of success, with a budget split that lifts them most.

Problem → built → result

Problem
A broker app shows balances. It can't say whether a goal will actually be met.
Built
Next.js on async FastAPI, a C++20 Monte Carlo engine over gRPC, a rules engine for risk.
Result
180 backend tests · 99% engine coverage · live demo

Features

  • Monte Carlo goal simulation in a multi-threaded C++20 service behind gRPC, with an in-process fallback so the backend runs and tests without it
  • A pure analytics engine (bucket classification, risk rules and goal glide-paths) at 99% line coverage, flagging concentration, sector clusters and drawdowns
  • Kite Connect's daily token expiry is detected, surfaced and recovered; a scheduled job snapshots the portfolio each weekday
  • Orders only through a human-in-the-loop sell flow: a dry run by default, placed only on explicit confirmation
  • A public demo mode that serves a fictional portfolio read-only: every write is refused and every key dropped, even if the environment sets one

Decisions

  1. 1

    Move only what's CPU-bound.

    Simulating thousands of market paths per goal is the one heavy job, so only it runs in a multi-threaded C++20 service behind gRPC. An in-process fallback keeps the backend running and testable without it.

  2. 2

    Keep the engine free of frameworks.

    Bucket classification, risk rules and goal glide-paths are plain Python functions with nothing to mock, which is what keeps 99% line coverage cheap to hold.

  3. 3

    Expect the token to expire.

    Kite Connect's access token expires every day, so expiry is a normal state that's detected, surfaced and recovered, not an error the page falls over on.

  4. 4

    A human places every order.

    The sell flow is a dry run by default and places an order only on explicit confirmation. The public demo goes further: every write is refused and every key dropped.

Why it's shaped this way

Only the simulation is CPU-bound, so only the simulation moved out of Python. Everything else stays where it's easiest to test, and every number the page shows comes from an engine with tests behind it.