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.
🐉 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_fillto readOrderResult.rawdict properly, handle both "CANCELED" and "CANCELLED" spellings - Added
_seed_inventorycross-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$200from config - Edge-scaled sizing was using
model - 0.5instead ofmodel - 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)
- Daily loss limit was
- 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_pipelineintry/finallyso blob upload always runs even on crash
/alpha Track Record
- Built P&L chart and bet history table for the token-gated
/alphapage (+370 lines inAlphaContent.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_180to 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 intodaily_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.