Most portfolio projects in this space predict prices. This one doesn't — it takes the position that in finance the hard problem is correctness: knowing what was true at a given moment, and refusing to report a number that can't be traced back to evidence. A bitemporal price store, a dataset-level quality gate, and an AI analyst whose every numeric claim is checked against the tool output its own prompt was built from.
Most financial-data portfolio projects predict prices. This one takes the position that the harder, less glamorous problem in finance is correctness — knowing what was true at a given moment, proving a figure came from somewhere, and refusing to report one that didn't. Three properties follow, and each is measured rather than asserted: point-in-time correctness (prices are stored bitemporally — what was observed, and when it became known), verified answers (the AI analyst's numeric claims are checked against the tool output its prompt was built from), and honest measurement (every claim has a runnable demo that reports what it found, including when that was nothing).
A dataset-level quality gate independent of row validation runs before every publish. Row validation alone can't see a batch that's well-formed and wrong — a provider that truncates a year of history to three days still returns three perfect rows. The gate catches what row rules can't.
Yahoo Finance data lands through a daily Airflow-orchestrated ingestion, passes a dataset-level quality gate, and is stored bitemporally in PostgreSQL — every read requires an as-of. An AI analyst answers questions over that data, and every numeric claim it makes is checked against the tool output its prompt was built from before it reaches the user.
On its first run against a real Airflow scheduler and real market data, the quality gate flagged an apparent 9751% single-day price move as implausible — an expectation designed for exactly this kind of dataset-level anomaly, not a row-level format check. 460 tests were green when it shipped. No exception, no failed test.
Tracing it by hand through raw SQL, by session-open time, found the root cause: a fix for an earlier missing-data problem had silently merged two different financial instruments into one database row — Infosys's USD-priced NYSE listing and its INR-priced NSE listing, same symbol, same day, two currencies stored as one price series. The bug was in my own fix for a prior issue, introduced hours earlier. It was root-caused and fixed with a regression test the same day.
Every row below runs offline from one command — no API key, no network, no database server. The point of each isn't a flattering number; several exist specifically to show where the system's own defenses were insufficient until proven otherwise.
| RESULT | COMMAND |
|---|---|
| Survivorship bias | Headline conclusion changed once run across multiple seeds |
| Ingestion quality | 7 synthetic batches pass every row-level rule; 5 refused publication |
| Gold lookahead | Two independent ways a derived table leaks the future |
| Backtest lookahead | Either protection alone blocks an exploit that lands 10/10 with both off ★ |
| Live gate catch | Gate blocked a real ingestion failure on first live run; the fix introduced a worse bug — caught the same way |
| Claim verification | 12/12 agreement, 0 misses — after the harness found a bug in itself |
| Grounding evaluation | A provider that states no figures scores a perfect grounding rate |
| Provider comparison | Gemini scores 91.7% grounding at 1.7 claims/answer |
| Direction prediction | Test AUC 0.5458 ± 0.1245 — ambiguous, not reported as an edge |
The direction-prediction row is the one worth reading first. Grounding rate alone ranks a useless analyst joint-first with a correct one — it's never reported without claim density beside it, because a metric that silence can win isn't measuring honesty.
The backtest's two lookahead protections were only "obviously" independent until an exploit was actually run 10/10 with both disabled — the number, not the intuition, is the finding.
Claim verification turns "don't invent figures" from a prompt instruction into an enforced postcondition — and a contradicted verdict, where the model read a labelled field and reported it wrong, is worth more than an unsupported one.
460 green tests didn't catch the currency collision — a live scheduler against real market data did, on its first run, the same day the bug shipped.