Arbitrage read-path: the latent, unbuilt cross-link, sized in the client's own model.

ScopeCompanion to the M3 delta report. This sizes the thing the drain feeds into: the PSA arbitrage read-path. The honest finding is that the read-path is a cross-link the platform does not have yet. The pieces exist on both sides; nothing joins them. AuditorMuhannad Ahmed (database and verification side; the platform's own developer owned the product side). PinWhole-platform sizing read-only at 2026-06-09 12:27 UTC (recon22 / recon22b). The drain slice is the M3 validation record, read 2026-05-30 (recon20 / recon20b). Live figures move as eBay listings age in and out. ModelThe client's own: the platform's stored arbitrage-opportunities table plus its spread formula. The spread is computed from TCGplayer raw prices and PSA pop/sales, net of the $20 grading cost; it does not read eBay raw listings. Public renderingClient, product, and niche-game names withheld; per-card rows, real listings, repo identifiers, and client-code excerpts removed or generalized. Every aggregate metric is carried unchanged from the private deliverable. Re-rendered 2026-07-05.
Encoded in the platform's code
"Wrong matches = wrong arbitrage = [the client] buys the wrong card. Return null rather than guess."
Verbatim from the platform's strict-matcher header comment; the client's name is replaced. The gate this analysis tests against: the M3 name-check found 231 of 232 surfaced buy-candidates name-consistent, 1 foreign-printing collision (2026-05-30). Live, that class is still open and reproducible: 212 / 105 / 7 / 0 foreign-title committed matches across the four ingested games (2026-06-09).
01

The two surfaces that do not talk to each other

The platform already computes both halves of the arbitrage trade. They live on separate surfaces, and the product does not connect them.

The opportunity half. The PSA arbitrage tab (backed by a stored opportunities table) keys every opportunity on a tcg_product_id: one row per card, carrying the raw market price, the PSA10 median sold price, and the stored spread. Critically, that spread is computed from TCGplayer raw prices and PSA pop/sales data, net of the grading cost (verified: the residual of median minus raw minus spread is exactly the $20 grading fee on every row). It does not read eBay raw listings at all. The drain neither creates nor changes the spread. What the tab then shows is the opportunity and the graded PSA10 comps, the sell side. It does not surface the raw buy listings.

The buy half. Raw card listings surface in a different place: the eBay deals browser (the auction deals view, matched-only filter). That view shows buyable raw cards matched to catalog products. It is where the drain's work lands.

The missing join. Nothing cross-links the two. Standing on a chase card in the arbitrage tab, the product cannot show you the buyable raw listings for that exact card, and standing in the deals browser you are not told which of those cards carry a live PSA10 spread. The buy side and the opportunity side both exist in the data, keyed on the same tcg_product_id, and the platform never joins them on the read path. That join is the arbitrage read-path the client sketched, and it is unbuilt. This page is about sizing it, not claiming it.

02

The size of the unbuilt opportunity

The whole platform, not just the drain. Across active matched listings (recon22.out B5 and B4), read 2026-06-09 12:27 UTC:

The arbitrage surface today Count
Distinct matched cards (active, by tcg_product_id) 13,219
Of those, cards with ANY computed arb opportunity row 1,377
Of those, with a positive spread 1,174

That is the headline, and it is a coverage headline: only about 10% of matched cards (1,377 of 13,219) have any arbitrage opportunity computed at all (about 9% carry a positive spread). The other ~11,800 matched cards have no arb row, because the PSA pop and sales ingestion that feeds the opportunities table has not reached them. So whatever the read-path is worth, it currently sits on a roughly 10%-built surface.

On that slice, the buyable inventory already exists. Joining active matched raw card listings to the surface-eligible positive-spread opportunities (at least 2 PSA10 sold comps, positive stored spread, published 2023 or later or undated):

Buyable raw inventory on the arb surface Count
Active matched raw card listings on surface-eligible positive-spread cards 587
Distinct cards those listings cover 252

So 252 distinct cards right now have both buyable matched raw inventory AND a surface-eligible positive PSA10 spread. The buy side and the opportunity side coexist on these 252 cards; the product just does not connect them on a single screen.

And the buy-raw/sell-graded unit is observable in the data: 1,263 distinct cards carry BOTH an active matched raw card listing AND an active matched graded PSA listing at the same time (recon22.out B6). The full trade (acquire raw, grade, list as PSA10) is already represented in the listing pool. The cross-link would make it navigable.

03

Addressable spread on the 252 cards

Across those 252 distinct cards with buyable raw inventory and a live positive spread, the aggregate addressable spread is $56,598 (one PSA10 copy per card, net of the $20 grading cost; recon22.out B4b). The distribution is heavily skewed, so the median is the honest "typical" figure:

Spread across the 252 cards with buyable raw + live spread USD (net grading)
Total (one PSA10 copy each) $56,598
Median per card $94.29

The top of the surface is real chase-card territory: the single largest per-card spread on the live read exceeded $2,400 on a roughly $400 raw price, and the long tail runs to four-figure ROI percentages on double-digit raw prices. (The per-card table, names and prices, is withheld from this public rendering: it is the client's live product output. The aggregate total and median above are carried unchanged.)

Honesty on what the spread is and is not. It is the gross profit on one copy IF it grades PSA10. The realized expected value weights by the PSA10 hit rate, which the surface exposes where populated. It is not populated for every card, so this page states the per-PSA10 spread and does not multiply through to a single EV number that would imply more certainty than the data carries. And the headline caveat sits above all of it: $56,598 is the spread on the 252 cards inside the roughly 10%-covered slice. It is a floor on a fraction, not the size of the full opportunity. The full opportunity is what the pop/sales ingestion plus the cross-link would light up across the other ~90%.

04

What the drain actually did (and did not do)

This section is the M3 validation record, measured 2026-05-30 against the drain's clean-commit cohort, not a live read. The drain set is a closed cohort (the figures below do not move with the eBay feed); the surface it sat on was larger then (383 cards) than the live read in the sections above (252 cards), because the listing pool has aged over since.

The honest, narrower claim. The M3 queue drain populated raw buy-side inventory. Its clean commits put buyable raw card listings into the matched pool that the eBay deals browser reads from; while those same listings sat in the near-miss band they were unmatched (null tcg_product_id) and the deals browser, which filters to matched listings, never showed them. The drain moved them from invisible to visible on the buy surface. Near-miss listings stay hidden from that browser by design, which is the correct conservative behavior (a low-confidence guess is exactly the wrong card to surface).

How much of the buy side the drain touched (as of the 2026-05-30 drain measurement). Of the 383 cards that then had buyable raw inventory and a live spread, 83 carried at least one drain-committed raw listing (recon20b.out: 232 drain buy-candidates across 83 distinct cards). Six of those 83 were sole-source: every active raw listing on that card came from this drain, so the drain took them from zero buyable listings to one or more. The other 77 already had a raw listing; the drain added depth.

What the drain did NOT do. It did not unlock executable arbitrage, because executing the arbitrage requires the cross-link, and the cross-link is not built. The drain made more raw cards visible on the buy surface. It did not connect that surface to the opportunity surface. A user who wants to act on a spread still has to leave the arbitrage tab, go to the deals browser, and search for the card by hand. The drain improved one half of an unbuilt read-path. That is the accurate framing, and it is the one this page leads with.

05

Accuracy gates this (wrong match = wrong arbitrage)

The gate, encoded verbatim in the platform's strict-matcher header comment (the client's name is replaced): "Wrong matches = wrong arbitrage = [the client] buys the wrong card. Return null rather than guess." The buy-side inventory is only as trustworthy as the matches under it. Two readings, one historical and one live, both point at the same gap.

The M3 validation name-check (2026-05-30). Every one of the drain's 232 surface-eligible raw buy-candidates was name-checked: does the catalog card the listing was committed to actually match the listing title (arb_unlock_classify.py, distinctive-token plus foreign-printing heuristics)? Result: 231 of 232 were name-consistent, 1 was a confirmed wrong-card pair (about 0.4%). The bad pair was a Japanese-print Togekiss listing committed to the catalog card Magikarp 203/193 (Paldea Evolved): the listing is a Togekiss, the catalog row a Magikarp, a foreign-printing card-number collision where a Japanese card's printed number lines up with a different English card. (A note on method, because it cuts both ways: the heuristic first flagged 6, but reading each one, 5 were false positives where the distinctive-token picker chose "Alternate" from "(Alternate Art)" rather than the character name. Reporting the raw 6 would have been a fabricated number; the verified count was 1.)

What the live read shows now. That specific Japanese Togekiss listing has since aged out of the feed, so the phantom spread is gone, and the Magikarp 203/193 card now carries a correctly-matched English listing (0.98 confidence, print_language=en). The card was never the problem; the one foreign listing under it was, and it is no longer there. The class behind it is still wide open, though, and reproducible live: active committed card matches carrying a foreign-language title number 212 on one-piece, 105 on dragon-ball-super, 7 on pokemon, and 0 on the flagship game (recon22.out A5, read 2026-06-09 12:27 UTC). The seven surviving pokemon rows are all the same shape: European-language prints whose printed numbers line up with different English cards. The flagship game returns zero because its foreign-print guard exists; the expansion games have no equivalent, so a foreign-language print can resolve to the wrong English card on any of them.

This is the same failure class the drain held 141 commits back for (graded-via-fuzzy and Latin-script foreign), and the same class as a French promo-print Pikachu caught in the M3 revert set. It argues for one concrete follow-up: extend the foreign-language hold guard from the dry-run's flag step into the live auto-commit path, so the production sync abstains on a foreign-printing card-number collision instead of committing it. This matters more, not less, the moment the cross-link exists: a wrong match becomes a wrong arbitrage the instant a buy listing is shown next to a spread.

06

The recommended next build

The read-path is the scoped work. Two pieces, both on the database/low-level side:

The cross-link. Join the opportunity surface to the matched raw inventory on the read path, keyed on the tcg_product_id both already carry, so a chase card in the arbitrage tab shows its buyable raw listings and the deals browser flags which matched cards carry a live spread. This is the join that turns two coexisting datasets into one navigable read-path. It is unbuilt today.

The coverage expansion. Extend PSA pop and sales ingestion past the current ~10% of matched cards, so the opportunity surface covers more than 1,377 of 13,219 cards. The $56,598 addressable spread is the floor measured on the built ~10%; the expansion is what scales it.

Built to scale from the start. Because the read-path is a per-card join against a surface that grows with every sync, the materialized-view and hot-cache work from the M1 audit folds in here, not as a separate effort: build the cross-link to scale from the get-go rather than retrofitting it.

The accuracy guard travels with it. The foreign-hold-on-auto-commit fix from the section above is part of the same work-stream, because the cross-link is exactly where a wrong match becomes a wrong arbitrage.

Dollar figures on this page ($56,598 addressable spread, $94.29 median) are card-market domain data, the subject of the analysis. No engagement pricing appears here.

07

Verification trail

Live figures read 2026-06-09 12:27 UTC (recon22.out / recon22b.out); drain-cohort figures are the M3 validation record, read 2026-05-30 (recon20.out / recon20b.out).

Claim As of Verified against
Coverage gap: 13,219 distinct matched cards, 1,377 arb rows, 1,174 positive (~10% covered) 06-09 live recon22.out B5 / B5b (live opportunities table + active matched listings)
587 buyable raw on 252 surface-eligible positive-spread cards 06-09 live recon22.out B4
Addressable spread $56,598 total / $94.29 median over the 252 cards 06-09 live recon22.out B4b
1,263 cards with active matched raw AND active matched graded-PSA sibling 06-09 live recon22.out B6
The Magikarp 203/193 live listing is a correct EN match (0.98, print_language=en); the Japanese Togekiss listing has aged off 06-09 live recon22b.out C4
Foreign-title committed card matches per game: one-piece 212, dbs 105, pokemon 7, flagship 0 06-09 live recon22.out A5 + recon22b.out C1-C3
Grading cost baked into the stored spread (residual exactly $20 on every row) 06-09 live recon22.out B8
Drain cohort lands on 83 of the then-383 cards; 232 buy-candidates; 6 sole-source / 77 augmented 05-30 M3 record recon20.out + recon20b.out
231 name-consistent / 1 confirmed wrong pair (Togekiss listing on the Magikarp row); 5 heuristic false positives read and discarded 05-30 M3 record arb_unlock_classify.py on the 232-row buy-candidate set + manual review
Accuracy gate quote stable the strict matcher's header comment, verified in a local clone of the platform repo
Arbitrage model (spread formula, tcg_product_id keying, opportunity-vs-deals split) stable the platform's arbitrage API route and views, read in the repo
End of arbitrage read-path sizing.
Audit owner: Muhannad Ahmed · The opportunity side and the buy side both exist in the data, keyed on the same product id, but the platform does not cross-link them. Only about 10% of matched cards (1,377 of 13,219) carry a computed arb opportunity; on that slice, 252 cards have buyable raw inventory and a live spread, $56,598 addressable (median $94.29). The cross-link is the unbuilt read-path. (Live read 2026-06-09; the surface was larger on 2026-05-30 and moves as listings age.)
What the drain did (M3 validation record, 2026-05-30): it populated raw buy-side inventory (83 of the then-383 cards carry drain commits), moving those listings from invisible to visible on the deals browser. It did not build the cross-link. The M3 name-check found 231 of 232 drain buy-candidates name-consistent, 1 foreign-printing wrong pair flagged. Pairs with the M3 delta report.