PLUTUS · ENGINE ROOM // PAPER ONLY · NO LIVE CAPITAL
ENGINEERED BY LEOPARD DATA

GMI Interoperability

Grade My Investments, the sister platform, has spent a year accumulating exactly what the backtester was missing. Plutus reads it through a deliberately narrow door: three coarse endpoints, one nightly export, and no database access at all.

The API that did not exist

The system design says Plutus "consumes GMI's published REST APIs as an ordinary client and never writes." A sweep of GMI's Azure Functions surface on 2026-09-01 found the first half untrue. The published endpoints are application-facing — support requests, billing analytics, share links, health checks, an FMP proxy. There is no data API serving regime state, grades, fundamentals or series to anyone outside GMI. The REST path the design assumed would have to be built.

So the real choice was between building that API and reading GMI's stores directly. The second option looked cheaper for about a day.

What GMI holds

Read from GMI's schema (83 tables) and design documents, not from live row counts — that caveat stands until someone runs the COUNT(*). The column that matters is point-in-time honesty: whether a backtester can consume the asset without lookahead.

AssetDepthPoint-in-timePlutus use
Fundamentals~5,500 symbols × up to 35 yearsGood, except RestatementCount > 0Fundamental strategies, grade replay
Macro series (FRED)Decades, with vintagesBest in the inventoryMacro state, the dollar, the recession label
Regime ribbonOne week lived; decades reconstructableExcellent structureRegime conditioning
Grades, historical10–35 years, computedGoodA backtestable grade signal
Daily pricesPer cached symbol, append-onlyExcellentCross-check only — our own bars reach 1993
Symbol universe2,516 curated tickersThe tradeable universe
Grades, currentSnapshotPoor — overwritten in placeUniverse and today's grade only
Profiles, sentimentCurrent onlyPoorCurrent sector; a lexicon cross-check
NewsDays, growingSnippetsA symbol-tagged second capture
Index constituentsCurrent confirmed; historical unprobedTBDUniverse construction, one probe away

Two rows changed existing decisions outright. DTWEXBGS, the Federal Reserve's broad dollar index, retired the plan to compute a DXY-style basket ourselves from six forex pairs. USREC, the NBER recession flag, is the ground-truth regime label for a thirty-year replay. And because GMI's regime determination is deterministic, published arithmetic over FRED inputs, its ribbon can be recomputed backwards — an open question in the backtester spec became a defined job, with the caveat that a reconstructed ribbon must be stamped as such.

The boundaryThe Plutus and GMI estates, the three endpoints, the export container, and the ingest job as the only thing that talks to GMI
Three coarse endpoints for the small daily reads, a blob export for the bulk history, and a red node: the backtester reaches GMI by no protocol at all.

Why direct SQL lost

Two problems, and the first is the one that decided it. The semantics are not in the database. fmpstatementcache.Dataset is a tinyint. It means CashFlow when it is 3 because Gmi.ClassLibrary/Models.cs says so — and so do PeriodType, the regime formula, and the grade computation. A Plutus that reads the tables directly has to duplicate that enum, and the day GMI adds Dataset = 6, Plutus does not fail. It silently misreads or drops rows. Drift with no error is the failure mode this whole architecture exists to avoid.

The second problem is proportion. GMI's MySQL has public access disabled inside its own VNet, so a direct read means VNet peering or private endpoints — an infrastructure project with an address-range collision risk, bought entirely to avoid writing code on the GMI side. A read replica fixes load and nothing else.

Decided: a small API on the GMI side for the recurring reads, a GMI-side blob export for the bulk, and no database access for Plutus. The split is by size, not preference. Regime state, macro series and the universe are kilobytes a day and every value is computed, so an endpoint that resolves GMI's own meaning is exactly right. The statement backfill is roughly 770,000 periods and 1.5–4 GB of JSON; over HTTP that is paginated requests against a 230-second function timeout, and as gzipped JSONL it is one file, written once.

The point-in-time problem

GMI's caches are read-through caches. They mutate — by their own design: not a system of record, profiles refreshed every 30 days, sentiment expiring, statements overwritten on restatement, grades recomputed every report run. Anything that queries a mutating store gets a different answer depending on when it runs, and that breaks the backtester's promise that two runs from the same manifest reproduce bit-identically. Worse, it imports lookahead: a 2019 backtest reading a statement restated in 2023 is trading on information that did not exist.

So a frozen, as-of-stamped copy is mandatory, and an endpoint is not a snapshot — an HTTP response is as live as a SQL query. The ingest job freezes on receipt, stamps provenance (lived or reconstructed), and writes to Plutus's own blob. Nothing in the trading path or the backtest path ever calls GMI. If the backtester can reach GMI by any protocol, the architecture is wrong.

The contract, and its cross-cutting rules

Appendix A of the interoperability document is the concrete version, proposed and not yet agreed. Three endpoints — /plutus/v1/regime, /plutus/v1/macro/snapshot, /plutus/v1/universe — and five export artifacts with a manifest per run. Six rules apply to all of them:

  1. Enums resolved to names, always. "dataset": "CashFlow", never 3.
  2. Every payload carries asOf and formulaVersion.
  3. Empty and error must be distinguishable. No rows is 200 with "count": 0; failure is non-2xx. Never a silently empty array — the FMP probe showed exactly that for Lehman, Enron and WaMu.
  4. Versioned routes; both versions run until Plutus has moved.
  5. Read-only by construction. GET only; no route accepts a body.
  6. Point-in-time fields are mandatory: when it became true, and when it was knowable.
Security by construction

Under direct SQL, GMI's customer PII, payments, Stripe events and support tickets would have been kept from Plutus by a grant list — one line of DDL between the two. Under the adopted design that data is unreachable by any credential Plutus holds: a function key scoped to a /plutus/ route prefix and a read-only credential for one export container, both in Plutus's Key Vault, separately revocable.

The "never writes" invariant is not a policy. Plutus has no credential capable of writing.

The surface
endpoints 3 exports 5 + manifest db creds 0 peering none status v0.2 · nothing built

The smell test: if a fourth endpoint is being added within a month, the boundary is in the wrong place — export more, query less.