Inspiration

Most of us got the same first piece of investing advice: set up an automatic transfer and put it in an index fund. For someone living paycheck to paycheck, that advice has two problems.

Timing. A fixed auto-invest doesn't know rent is due in four days. People overdraw once, get scared, and stop investing for years.

Distance. "VTI" means nothing to a 22-year-old. Costco, Netflix, and Target do — because they buy from them every week.

We wanted a fund that starts from the customer's actual bank account — not one they describe, but the real one, with real transactions. Capital One's Nessie API made that possible: we read paychecks, bills, and 90 days of purchase history directly from the source, and let that data shape the fund. When you shop at Costco every week, Costco is in your fund. When rent is due in four days, the app doesn't invest until it's paid.

That became FirstFund: your own index fund, built from where you spend, invested every payday, never before rent is paid.

## What it does

  • Reads your bank. Paychecks, bills, purchases, and balances come from Capital One's Nessie API — the same data your bank sees. In live mode, nothing is cached in our own database; every balance is rebuilt from Nessie's transaction ledger on each page load.
  • Asks eight questions: your goal, a starting amount, what percent of each paycheck to invest, two questions about risk tolerance, the biggest one-year drop you could live with, when you'll need the money, and which industries excite you.
  • Builds a fund shaped by your spending, in six slices:
    • Bonds (BND), sized by your risk score;
    • International stocks (VXUS);
    • Your US index: the 55 biggest US companies across all 11 sectors, weighted by market cap;
    • Where you spend: Nessie's GET /accounts/{id}/purchases returns your last 90 days of transactions. We map each merchant to its public parent company and weight holdings by dollars spent. Shop at Costco, own a bit of Costco;
    • Your interests: the biggest companies in the industries you picked;
    • Companies you added yourself.
  • Invests every payday, only what's safe to invest. When your paycheck lands in Nessie, we check the bills due before your next paycheck, your recent spending rate from the Nessie activity feed, and a $200 buffer — then move only the remainder to investing via two Nessie calls.
  • Explains everything. A "How your fund was built" page and an AI chat both trace every decision back to a structured reason the engine produced — including "You spent $180 at Costco last quarter, so Costco is in your fund."

## How we built it

Stack: Python, FastAPI, HTMX, Jinja, and Tailwind. Capital One Nessie for all banking data. Claude for conversation. Alpaca paper trading as an option; otherwise a simulated broker. Nasdaq's public feed for delayed prices. Deployed on Render.

One rule shaped the whole architecture: Claude never generates a number. Python computes every figure; Jinja renders it; Claude only explains facts we hand it. Every decision the engine makes produces a structured Reason(kind, text, ticker, facts). The explanation page and the chat tool are generated from those reasons, so they can't disagree with the engine — and "You spent $180 at Costco last quarter" is a fact from Nessie, not a hallucination.

## The payday rule

When a paycheck \(P\) lands — recorded as a [payroll] deposit in Nessie — we invest:

$$\text{invest} = \min!\Big(\text{planned},\,\Big\lfloor\tfrac{1}{5}\Big(P - \sum_{\text{bills due before next payday}} b_i - S\cdot\tfrac{14}{30} - \$200\Big)\Big\rfloor \cdot 5\Big)$$

Here \(S\) is monthly spending, read from the Nessie activity feed, and $200 is a buffer that always stays in checking. Nessie only moves whole dollars, so we round down to $5. Our demo persona Sam gets a $2,050 paycheck with rent due in four days, so he invests $50 of the planned $150 — and the app tells him exactly why. The transfer itself is two Nessie calls: a POST /accounts/{checking}/withdrawals tagged [firstfund] payday auto-invest, and a matching POST /accounts/{investing}/deposits. Both appear in the activity feed on the Money tab.

## Risk without a gate

Two preference answers become a score \(s \in [0,100]\). The bond share follows it smoothly:

$$\text{bonds}(s) = \operatorname{round}_{5\%}!\Big(65\% - 55\% \cdot \tfrac{s}{100}\Big)$$

Two answers set ceilings, not averages. When you need the money caps the risk level (under three years means conservative). The drop you said you could live with caps the mix by real worst-year returns:

$$\text{worst}(w) = \min_{y \in {2008,\,2022}} \sum_i w_i\, r_{i,y}$$

We used real 2008 and 2022 returns for VTI, VXUS, and BND — which revealed that no mix stayed within a 15% drop in 2022, because bonds fell too. So we tell customers that instead of promising something impossible. If a limit lowered your fund, we say "Your answers pointed to Aggressive; we built Conservative because…" and let you override it. We advise; the customer decides.

## Your own index

The US index weights each company \(j\) by market cap \(m_j\), capped at 6% of the slice:

$$w_j = \min!\Big(c,\,\lambda\,m_j\Big), \qquad \sum_j w_j = 1$$

The spending slice weights each company by dollars spent there — pulled directly from GET /accounts/{id}/purchases. A local shop or private chain (Trader Joe's, a corner cafe) maps to the biggest public companies in its category. A company held for two reasons — Costco in the US index and in the spending slice — is one position with a combined note. No position reaches 8% of the fund.

Personalization, not prediction. Buying the companies you shop at makes a fund feel like yours. It doesn't make those companies better investments, so the app says so every time.

## Nessie as the source of truth

This is where most fintech demos cheat: they mock the bank. We don't. In live mode, every balance, bill, and transaction is read from Nessie on every page load. Nothing is duplicated in our own database.

We seeded four personas — Maria, Jordan, Sam, and Alex — each with real Nessie accounts, two months of tagged transaction history, and purchases at real brands. When Render wipes its disk on a free-tier restart, nothing is lost: balances are recomputed from the transaction ledger, and even the mapping from persona to account lives in Nessie itself.

Every transaction we write carries a tag: [payroll] for paychecks, [firstfund] for auto-invest transfers, [spend] for day-to-day spending, [bill] for bill payments, [seed] for seeded history. Those tags are how we distinguish our records from anything else in the account, and how the activity feed rebuilds itself from scratch each time.

The registry account. Every time we save account ids, we also write them to a "FirstFund registry" Nessie account as a tagged $1 deposit — the description field holds the entire ids file as JSON. On startup, the server reads every [registry] deposit and restores the current state from the one with the highest sequence number. A server that has never seen a customer can still find their accounts.account as a tagged $1 deposit — the description field holds the entire ids file as JSON. On startup, the server reads every [registry] deposit and restores the current state from the one with the highest sequence number. A server that has never seen a customer can still find their accounts.

Challenges we ran into

  • Nessie's quirks. We measured the real API before writing a line of product code:
    • Balances never update after a transaction, so we compute every balance as a ledger: opening balance plus our own tagged transactions.
    • Transfers have no destination field, so a move is a withdrawal plus a deposit.
    • Amounts must be whole dollars.
    • An account with no transfers returns 404 instead of an empty list.
    • A purchase missing one field makes the whole purchases list unreadable.
    • Lists don't come back in creation order.

Each quirk has a test, against a fake Nessie that enforces the same rules.

  • Demos die on bad wifi. If Nessie times out mid-click, the app switches to demo data starting from the exact numbers on screen, and the click still completes. Writes are only retried when the connection was refused before the request went out, so a payment is never sent twice. We also bundled Tailwind, htmx, and Chart.js so the page works with no internet at all.

  • Honest numbers. Our first worst-year figures were placeholders. Replacing them with real 2008 and 2022 returns revealed a constraint we hadn't expected (no mix stays within 15% in 2022). So we tell customers that instead of hiding it.

  • An index that behaves like an index. Normal price moves made 57 tiny holdings "drift," and our demo opened on a 57-trade rebalance. A real market-cap index doesn't trade when one company outgrows another. Inside the index, targets now follow prices; only the index's total share and your personal picks count as drift.

  • Four people, one fund engine. Two teammates independently built spending-driven fund engines on the same night. We compared them side by side, kept one, and merged in everything unique: spending insights, a backtest page, auto-execution for large accounts, and brand rules.

What we learned

  • Test against the real API before writing code. An hour spent measuring Nessie's actual behavior — balances don't update, transfers have no destination, a missing purchase field breaks the whole list — saved days of debugging.
  • Personalization and prediction are different promises. Buying the companies you shop at makes a fund feel like yours. It doesn't make those companies better investments, and the app says so every time: "Spending picks are personalization, not predictions."
  • Explainability is an architecture decision, not a UI feature. Once every decision produced a structured reason, the explanation page, chat answers, and warnings all came almost for free — and they can't disagree with the engine, because they're generated from the same Nessie data we handed the fund builder.
  • Property tests catch what examples miss. Our test grid builds about 7,000 funds across every combination of answers. It caught rounding that pushed a slice 0.01% past its cap, twice.

By the numbers

787 automated tests passing against both demo data and the live Nessie fake. 4 seeded Nessie personas with real account history. A 55-company US index across 11 sectors. 0 numbers written by the AI.

Built With

Share this project:

Updates

Submission history