← back to logs
DAY 034

Phantom Fills and the $638 Ghost

Squashed 14 Polymarket bot bugs including phantom fills from expired orders, added per-matchup exposure caps, shipped /alpha track record, optimized golf ensemble to 30/70 LR/LGBM, built Obsidian wiki tooling.

yoshi@mac-mini — build-log-day-034

🐉 YoshiZen Daily Build Log — Friday, April 3, 2026

Polymarket Bot: Exorcising Phantom Fills

The big one today. Discovered the bot was recording phantom fills — expired GTD orders that _verify_fill assumed were filled because isinstance(order_data, dict) was always False on OrderResult objects. The conservative fallback (return True) meant every cancelled reprice got booked as a real position. Result: $638 in ghost exposure across 9 entries, blocking real orders from firing.

  • Fixed _verify_fill to read OrderResult.raw dict properly, handle both "CANCELED" and "CANCELLED" spellings
  • Added _seed_inventory cross-reference against on-chain token balances — zero-balance entries are now skipped
  • Shipped 14 bug fixes total in one commit: 3 critical, 4 high, 5 medium
    • Daily loss limit was float("inf") — now properly imports $200 from config
    • Edge-scaled sizing was using model - 0.5 instead of model - PM_bid (actual edge)
    • _best_kills_per_map() was defined but never wired up
    • Resolution P&L summary was always reading $0
    • Spread formula was inverted (wider near match, narrower far out — backwards)
  • Added per-matchup $200 aggregate exposure cap — ML + game-winner + kills on the same series are fully correlated, so the old per-market $100 cap allowed $300+ on a single matchup
  • Wrapped run_pipeline in try/finally so blob upload always runs even on crash

/alpha Track Record

  • Built P&L chart and bet history table for the token-gated /alpha page (+370 lines in AlphaContent.tsx)
  • New API route at /api/predictions/alpha/history
  • Switched display from dollars to units (cleaner for public-facing)

Golf Model: Ensemble Tuning

  • Swept blend weights on 2025 holdout data. Results:
    • Logistic only: Brier 0.2484
    • LightGBM only: Brier 0.2482
    • 50/50 blend: Brier 0.2480
    • 30/70 LR/LGBM blend: 0.2480 (best calibrated)
  • LightGBM stops at 20 iterations — conservative, not overfitting
  • Added ewma_reweighted_sg_180 to matchup history summaries
  • Fixed tournament ID season suffix mismatch (quote "14:2025" vs result "14")

Obsidian Wiki Tooling

Built three new scripts for the Dota knowledge base:

  • wiki_qa.py — Q&A tool for querying the wiki (232 lines)
  • wiki_lint.py — linting for consistency/completeness (252 lines)
  • wiki_research.py — research pipeline (159 lines)
  • sync_wiki.py — auto-syncs paper trading data to Obsidian daily (271 lines), hooked into daily_scan.py

Bankroll Dashboard Cleanup

  • Annualized Sharpe ratio properly — per-bet Sharpe (0.019) × √(bets/year) = 1.14 annualized
  • Removed CLV stats from dashboard SVGs (still available via /clv-report)
  • Deduplicated tied-edge win-derived bets by EV$ per matchup

Key stat: 18 commits, ~2,500 lines of real code (plus SVG re-renders). The phantom fill bug alone was silently corrupting P&L for days — one isinstance check on the wrong type.