Fix Plan — Top 5 by ROI
Fix 1 — Make the matcher produce pairs (unblocks everything)
Files:
- arbitrage/pairing.py:902 — reads matcher.expiry_tolerance_sec, but config writes expiry_tolerance_seconds. Key mismatch.
- arbitrage/pairing.py:903 — require_exact_threshold=True default.
- arbitrage/pairing.py:904 — min_question_similarity=0.82 default.
- arbitrage/pairing.py:994–997 — hard continue on any non-zero threshold_delta.
- config.yaml:36–38 — matcher block.
Change:
1. In config.yaml under matcher: rename expiry_tolerance_seconds → expiry_tolerance_sec: 120 (or add both for transitional safety).
2. Add to config.yaml:
yaml
pairing:
require_exact_threshold: false
threshold_delta_max: 0.005 # 0.5% tolerance
min_question_similarity: 0.70
3. In pairing.py:994, change to: read tolerance from config; allow threshold_delta up to threshold_delta_max (new) with graduated score penalty — the 0.0025 hard cap at line 996 also needs to become configurable.
Expected impact: goes from 0 matched pairs to dozens–hundreds per sweep, based on the 7,285 normalized Polymarket × 477 Kalshi contracts currently indexed (a 3.4M naive pair space). Even 0.01% match rate = 340 candidates.
Validation: new unit test adding two near-duplicate markets with $X vs $X+1 strikes, 60s expiry mismatch — assert both paths produce a pair. Run find_equivalent_pairs on the current DB snapshot and assert len(matches) > 10.
Fix 2 — Kalshi inventory sync breadth
File: tools/overnight_overlap_loop.py:52–58, runtime_config.py (cfg keys catalog_inventory.kalshi_pages_per_run, kalshi_page_size).
Change:
- Bump kalshi_pages_per_run from 2 → 20 and kalshi_page_size from 100 → 200. Per-iteration cost: ~4000 rows/5min, manageable under Kalshi 60 req/min rate limit.
- Add full_sweep_every_n_iterations: 12 — every hour, ignore cursor and do a full re-sync so new markets aren’t missed.
- Add status filter: only fetch status=open markets (Kalshi API param).
Expected impact: DB Kalshi count 477 → 5,000–10,000 (active markets). Roughly 10–20× match surface.
Validation: after one hour of looped sync, SELECT COUNT(*) FROM catalog_market_inventory WHERE venue='kalshi' AND status='active'; ≥ 5000.
Fix 3 — Lower _qualified_candidate gate + separate “found” from “executable”
File: tools/overnight_overlap_loop.py:24–36.
Current gate: equivalence_score >= 0.995 AND budget_usd <= 10 AND passes_hurdle AND rebalance in (healthy,monitor). Effectively impossible.
Change:
1. Split into two tiers:
- watchlist gate: equivalence_score >= 0.80 AND passes_hurdle
- executable gate: current (0.995, etc.)
2. Log both counts in run_summary.counts so you can see the funnel narrow.
3. Push watchlist entries to a Telegram /pam-candidates feed for Joshua’s eyeballs (human-in-the-loop recovery path).
Expected impact: visibility into what’s actually happening. You’ll know in one night whether the matcher is producing near-pairs that just fail final gating, vs whether there’s an upstream bug.
Validation: after fix, run 1 overnight iteration and len(report["top_candidates"]) > 0.
Fix 4 — Boot script that loads .env and enforces dry-run
Files: new scripts/run_watcher.sh, update DEPLOYMENT_STATUS.md.
Current state: background_monitor.py starts without dotenv → Polymarket is in limited mode. Also .env has FORCE_DRY_RUN=0 despite CLAUDE.md mandating dry-run default.
Change:
1. scripts/run_watcher.sh:
bash
set -euo pipefail
cd "$(dirname "$0")/.."
source .venv/bin/activate
set -a; source .env; set +a
export FORCE_DRY_RUN=1 # safety unless explicitly overridden
exec python tools/overnight_overlap_loop.py --sleep-seconds 300
2. In main.py, assert-fail at start if FORCE_DRY_RUN env is “0” AND --live flag not explicitly passed.
3. Move .env out of repo root; reference via KALSHI_PRIVATE_KEY_PATH only.
Expected impact: prevents accidental live trades + fixes Polymarket data quality.
Validation: start the script; grep log for “Polymarket API initialized” (not the warning). Try to start without --live with FORCE_DRY_RUN=0 and see it refuse.
Fix 5 — Threshold/strike canonicalization
Files: matcher/ontology.py:29–33, arbitrage/pairing.py threshold extraction.
Change: implement the ordered-pattern + validation-bounds extractor from ANALYSIS_matching_algorithm.md §Improvement #2. Specifically:
- Preferred: “$X,XXX” explicit dollar patterns.
- Secondary: “Xk” patterns.
- Validation bounds per asset (BTC 10k–500k, ETH 500–20k, SOL 10–1k).
- Round-trip test: extract_threshold("Bitcoin above $110,000 on Aug 28") == 110000 (today this can return 28).
Expected impact: reduces false-negative pair rejections at line pairing.py:994. Combined with Fix 1’s softer gate, turns ~10–30% more same-strike pairs into matches.
Validation: new test file tests/test_threshold_extraction.py with 20 real titles from catalog_market_inventory payloads; expect ≥ 17/20 correct.
Also critical (not in top 5 but low-effort)
- Kill stale PIDs.
data/overnight_overlap_loop.pid = 53818but no such process. Add pidfile-validation at startup. - Archive
FINAL_QA_REPORT.md/DEPLOYMENT_STATUS.md. They claim “✅ DEPLOYED” / “✅ ACTIVE” but the system has been off for months. Misleading. - Drop the
opportunity_hunter.pyinteractive prompt (line 184) or add a--headlessflag so it can be supervised.