Projects · AI & Automation

Investment Performance Dashboard

Executive Summary

A self-hosted portfolio reporting system that turns raw brokerage data into institution-grade performance reporting. Built in Python against the Schwab API and run by scheduled local automation, it consolidates four account types — taxable, IRA, Roth, and trust — into a single live view, then applies the same discipline used in board-level financial reporting: benchmarked returns, separation of contributions from growth, a clean split between realized and unrealized results, and closed periods that never change. The design principle is simple: numbers you saw yesterday should mean the same thing today. It runs entirely on my own hardware — no aggregator holds my credentials, and no third party sees the data.

The Problems It Solves

Brokerages tell you what you have, not how you're doing. Account statements show balances, but they blur the one question that matters: is the portfolio beating what the same dollars would have earned in an index fund? Each month, the system runs an apples-to-apples experiment: it computes the portfolio's Modified-Dietz return — the methodology institutional managers use — and, in parallel, simulates the identical dollars and deposit dates invested in SPY and QQQ at official closes. Deposit timing and account size can't flatter the result.

Contributions masquerade as growth. A rising balance feels like winning even when it's just new deposits. The system tracks net external cash separately from market gains, dividends, and option premium, so "invested" and "earned" never mix.

Options income is invisible in most tools. For a covered-call (wheel) strategy, premium is a real income stream. The dashboard treats it as a first-class metric — net premium collected, contracts opened and closed, and each short leg shown inside the positions table where it belongs.

Multiple accounts, no single picture. Positions held across four account types roll up into one weighted view with sector allocation, day moves, and staleness flags on every quote — so a stale price can never be mistaken for a live one.

Built Like a Control Environment

The part of this project that reflects my day job: the dashboard treats data integrity as a first-class feature, the way a finance department treats internal controls. Completed months are computed once, validated, and frozen permanently — the same principle as a locked accounting period; if a month says +5.14%, it says +5.14% forever. Anything estimated or provisional is visibly flagged rather than silently blended. Broker data is cross-checked against the system's own price history, and when the feed reports an impossible figure, the bad value is substituted and a warning is raised — caught and flagged, never silently displayed. Historical values preserve both the raw broker mark and an official-close-anchored version, so every adjustment is reversible and auditable. The external holdings log is append-only — rows can be added, never rewritten. And every panel is isolated, so a failure in one chart can never take down the reporting.

Sample Output

All figures below are illustrative sample data — not actual holdings or results.

Invested$84,000
Portfolio Value$101,250
Lifetime Gain+$17,250
Net Option Premium+$3,180

Positions (sample)

SymbolSectorQtyValueDayUnrealizedWeight
AAPLTechnology120$27,400+$310+$4,12027.1%
MSFTTechnology45$21,150-$95+$3,86020.9%
VTIBroad Market60$18,300+$120+$2,24018.1%
JPMFinancials70$16,800+$210+$1,93016.6%
XOMEnergy90$10,350-$140-$62010.2%
PGConsumer Staples45$7,560+$45+$5107.5%
AAPL 12/19 C240Covered Call-1-$310+$40+$185-0.3%

Portfolio vs. Benchmark — trailing 12 months (sample)

+20%+15%+10%+5%0%AugOctDecFebAprJunJulPortfolio (+15.7%)S&P 500 (+10.6%)

Return vs. Market — monthly (sample)

Portfolio +9.8%SPY +6.1%QQQ +7.9%6M compounded+3%+2%+1%0-1%-2%Feb *MarAprMayJunJul ·

Each month is an independent experiment: the portfolio's Modified-Dietz return next to the same dollars and deposit dates invested in SPY and QQQ. Frozen months never change;* marks a month backfilled once from complete transaction history, and· marks the current month, provisional until it freezes five trading days after month-end.

Contributions vs. Growth (sample)

$100k$90k$80k$70k$60kgap = gainPortfolio valueCumulative depositsAugOctDecFebAprJun

Deposits are what went in; the gap to total value is lifetime gain — market moves, dividends, and option premium. A rising balance only counts as winning when the gold line pulls away from the blue one.

Options Premium Income — monthly (sample)

$600$400$2000-$200FebMarAprMayJunJul

Net cash from a covered-call strategy by month: premium collected on opens, minus buybacks and assignment costs — treated as a real income line, not a footnote. Red months happen; the discipline is measuring them.

How It's Built

A Python daemon (FastAPI) runs continuously on my own machine under scheduled local automation, pulling quotes every minute during market hours, snapshotting positions every fifteen minutes, syncing the Schwab transaction feed daily, and re-anchoring each day's marks to the official close. Data lives in a local SQLite store with automated nightly backups; brokerage credentials never leave the macOS Keychain. The front end is React with ECharts, served privately from the same machine. AI-assisted development throughout: the system was designed, built, and iterated with large-language-model tooling as a force multiplier.