📐 Methodology

How the quant score works.

No black boxes. Every input, every weight, every threshold — published here so you can stress-test the model yourself.

The composite score in one formula

score = 0.55 · momentum + 0.20 · scarcity + 0.15 · value + 0.10 · liquidity

Each component is normalized to [0, 100]. The weighted sum is bounded at 100. Three strategy modes change the weights:

55%
Momentum
20%
Scarcity
15%
Value
10%
Liquidity

Mode: Balanced (default)

55 / 20 / 15 / 10 — covers most use cases.

Mode: Momentum (chase the runners)

70 / 10 / 10 / 10 — weights recent moves heavily, less concerned with scarcity.

Mode: Long-Term Hold (grail premium)

30 / 40 / 15 / 15 — flips weights toward PSA pop scarcity, smooths over short-term noise.

Component math

Momentum (0-100)

momentum = clamp(0, 100, 50 + (d7 · 0.6 + d30 · 0.4) · 200)

Recent-weighted (60% 7-day, 40% 30-day). Centered at 50 (flat = neutral). A 25% 30D run produces momentum ≈ 70.

Live delta source: Until 5 days of price_history.jsonl accumulates, deltas are workbook-formula based and are suppressed in the public-facing ticker. Real 30D % activates after the 5-day mark.

Scarcity (0-100)

scarcity = clamp(0, 100, 100 - log₁₀(pop_psa10) · 16)

Logarithmic decay against PSA 10 population. Pop 100 → scarcity ≈ 68. Pop 10,000 → scarcity ≈ 36. Pop unknown → default 60.

Value (0-100)

value = clamp(20, 100, 50 + (target_buy - price) / target_buy · 100)

Where target_buy = 0.80 × live_price. Cards trading below the 80% MV threshold score higher. Floored at 20 so chase cards never zero out.

Liquidity (0-100)

Lookup by tier:

Signal thresholds

ScoreSignalWhat it means
≥ 75STRONG BUYAlgo says load up. Top-decile composite.
60–74BUYPositive — worth a position.
40–59HOLDNeutral. Existing positions OK, no new entries.
25–39TRIMReduce exposure; rotate capital.
< 25SELLExit. Algo flags structural weakness.

Data sources

1. PriceCharting (primary for graded slabs)

Per-grade aggregate prices (PSA 7/8/9/9.5/10). 3-6 month trailing window. This is the trusted benchmark — when PPT and PC disagree, PC wins.

2. PokemonPriceTracker (graded fallback + ungraded reference)

Per-grade eBay sold-comp medians (last ~30 days). More responsive to recent volatility but sensitive to thin-sample outliers. Used when PC has no data for the card, or paired with TCGPlayer market for raw NM.

3. TCGPlayer mp-search-api (live raw market)

No-auth public endpoint. Best signal for raw NM cards because it's the price Brandon's competitors actually see on TCGPlayer.

4. PokemonTCG.io (card universe + images)

20,000+ card catalog with TCGPlayer + Cardmarket price overlays. Powers the searchable Card Universe + card image enrichment.

5. eBay sold-listings scrape (when available)

Browse API integration pending. Direct scraping is blocked from data-center IPs (Akamai protection); GitHub Actions cron cannot reach eBay sold pages from its runners.

Sanity guards

Every price overlay passes through bidirectional sanity guards before reaching production:

Rejected entries are visible in the Price Audit tab with their original price, source, and rejection reason. Users can manually override via the same tab.

What we explicitly don't model

Flip Bot — TCGPlayer + eBay BIN arbitrage scanner

A separate engine on top of the quant model. Where the score answers "should I want this card?", the flip bot answers "is there an underpriced live listing I should grab right now?".

Core formula (per live listing)

buy_total = listing_price + shipping sell_net = fair_value × (1 − ebay_fee_rate) # default fee = 0.13 net_profit = sell_net − buy_total roi = net_profit / buy_total qualified = (net_profit ≥ $15) AND (roi ≥ 0.20)

Fair-value selection (variant-aware)

Confidence tiers

ROI magnitude is a tell — real arbs cluster 20–50%. Above 50% means either the listing is a steal or (more often) the fair-value reference is stale. The bot labels rather than auto-hides:

ROITierWhat it means
20–49%OKReal spread within historical norms. Trust the alert.
50–99%VERIFYBig spread — eyeball before buying. Often legit but check the listing.
≥ 100%SUSPECTAlmost always bad fair-value data or a fake/scam listing. Don't blind-click.

False-positive guards

Two sourcing channels

TCGPlayer path: public mp-search-api.tcgplayer.com endpoint, ~3 minutes for the full 320-card sweep. TCGP sellers are mostly informed dealers so spreads tend to be tight but frequent.

eBay BIN path: scrapes ebay.com/sch/i.html?LH_BIN=1 sorted price-ascending. eBay has casual sellers who under-price 30–50% below sold median on the same card — bigger spreads but rarer hits. A circuit breaker aborts the scan if Akamai flags the IP (typically 8 consecutive blocked responses), so TCGP results remain valid even when eBay is throttled.

Source code: flip_bot.py. Run output (qualified + near-misses + thresholds) is published to flip_bot_results.json on every scan and rendered live on the homepage and the 💸 Flip Bot tab.

Refresh cadence

Open data, audit-ready

Every artifact mentioned here is in the public trading-desk repo. The scoring code is in streamlit_app.py (function compute_score). The scraper is live_prices.py. The sweep tools are sweep_pc_now.py and sweep_ppt_holdouts.py. Fork, validate, suggest changes.


Layer 2: Self-Learning + Forward Models

The composite score above is the static foundation. Layer 2 is the adaptive layer — models that re-tune themselves, surface forward-looking opportunities, and prove their own batting average.

🔮 Future Winners — 6-component predictive model

The composite score is backward-looking (it summarizes what the price IS). The Future Winners model is forward-looking (what the price IS LIKELY TO BECOME in 30-90 days).

fw_score = w₁·momentum_rising + w₂·scarcity + w₃·accessibility + w₄·hidden_alpha + w₅·tier_premium + w₆·liquidity

Seed weights: 0.25 / 0.20 / 0.15 / 0.15 / 0.15 / 0.10 (re-tuned by the optimizer — see next).

Output: future_winners.json. Picks visible on the homepage's 🔮 Future Winners section.

🧠 Self-Learning Engine — Pearson-correlation optimizer

Static weights are a guess. The Self-Learning Engine retunes them empirically using realized 7-day returns. The process:

  1. Every Future Winners emission logs its 6 component scores to fw_emissions.jsonl
  2. After 7 days, the emission's 7-day-forward realized return is computed from price_history.jsonl
  3. Pearson correlation is computed between each component's score and realized return
  4. New weights = 0.7 × old_weights + 0.3 × correlation-derived_weights (Bayesian-style smoothing)
  5. Weights clamped to [0.05, 0.40] per component, renormalized to sum to 1.0
  6. Updated fw_weights.json is consumed by the next Future Winners build
new_weight[c] = clamp(0.05, 0.40, 0.7·old + 0.3·correlation(c, realized_return))

Maturity threshold: minimum 30 mature samples (≥7 days old) before any retrain runs. Below threshold, system falls back to seed weights. Initial retrain ~ 7-10 days after launch as emissions accumulate.

Outputs: fw_weights.json (current), fw_weights_history.jsonl (every update with sample count + correlations for auditability). Visible on the homepage 🧠 Self-Learning Engine card.

💰 Arbitrage Scanner v2 — multi-path EV model

For every card, the scanner enumerates 4 profit paths and reports the one with highest expected value:

Path A · RAW → PSA 10 (grade-and-flip)

EV_rev = Σ probₖ × (priceₖ × (1 − ebay_fee) − ship_out) EV = EV_rev − (raw_buy + grade_fee + ship_to_psa)

Probabilities by era: Modern (SwSh+) = 25/45/20/10 for PSA 10/9/8/<7. Mid (XY-SM) = 15/40/25/20. Vintage excluded from this path because PriceCharting "ungraded" prices reflect damaged copies — distorts the math.

Path B · RAW → PSA 9 Floor

Conservative variant: PSA 10 hits treated as PSA 9 (locks in lower upside, lower variance). Same probability table.

Path C · PSA 9 → PSA 10 Cross-Grade

Buy a PSA 9 slab, crack it, resubmit. Probabilities: 20% upgrade, 60% stays at 9, 20% downgrade. Only viable when p10/p9 ≥ 3.0× and PSA 9 entry < $5,000.

Path D · RAW Retail Flip

Instant arbitrage: TCG-listed vs eBay-sold spread ≥ 15%. No grading required, ~7-day hold.

Ceiling: 350% ROI cap — anything above is treated as data artifact, not opportunity. Fees modeled: 13.25% eBay FVF, $25 PSA grading, $5 inbound ship, $10 outbound. Output: arbitrage_v2.json, top 100 by EV-profit.

📊 Past Picks Scoreboard — model batting average

For every signal logged in signal_log.jsonl, compute realized return from emission-time price to current price:

realized_return = (current_price − emission_price) / emission_price is_win = (BUY-signal AND return > 0) OR (SELL-signal AND return < 0)

Bucketed by signal type and age (<1d / 1-7d / 7-30d / 30d+). Win rate + avg return reported per bucket. Anything < 24h flagged as "pending" (too fresh to call). Visible in the 🧠 card as a strip beneath the weights.

📒 Trading Journal — Brandon's real trades

Reads two workbook sheets:

Computes portfolio ROI, win rate, average hold days, LT vs ST tax split (LT = >365 days, qualifies for capital gains rate). Output: trading_journal.json. Visible in the homepage 📒 Track Record section.

🔥 Set Heat Tracker — macro-level signal

heat_score = 0.4·avg_score + 0.3·(BUY_density·100) + 1.5·avg_d7 + 1.0·avg_d30

Per-set composite. When a set's heat_score > 100, the whole set is structurally heating up — individual card moves tend to correlate. Useful for "is this an Evolving Skies pump or just one card?" decisions.

📦 Sealed Market Scanner

Separate market from singles. Buckets products into Booster Box, ETB, Booster Bundle, Premium Collection, Collection Box, Theme Deck, Other. Surfaces:

🔍 Pipeline Health Self-Audit

Every cron cycle ends with pipeline_self_audit.py checking 19 output files for:

Each file gets GREEN / YELLOW / RED. Overall pipeline health surfaces as a colored dot in the homepage sub-nav. Output: pipeline_health.json.

Full automation cron pipeline

Every 4 hours, GitHub Actions runs:

1. live_prices.py — scrape PC, PPT, TCGP, eBay (~25min) 2. reconcile_prices.py — multi-rule price reconciler 3. auto_data_quality_check.py — DQ A-F grading 4. build_price_diff.py — today's moves 5. build_morning_brief.py — daily picks 6. build_price_alerts.py — BUY/SELL triggers 7. track_source_reliability.py — per-source stats 8. build_daily_recap_v2.py — email-ready recap 9. learn_signal_outcomes.py — log signals 10. optimize_fw_weights.py — Pearson retune 11. track_psa_pop_changes.py — pop deltas 12. build_learning_summary.py — surface weights 13. arbitrage_scanner_v2.py — EV paths 14. build_trading_journal.py — workbook P/L 15. build_past_picks_scoreboard.py — batting avg 16. build_set_heat.py — per-set composite 17. build_sealed_market.py — sealed scanner 18. build_email_digest.py — HTML email 19. pipeline_self_audit.py — health check 20. build_homepage_feed.py + cards + commit + push