pam ▸ docs/archive/2026-05-02-pam-hardening/diagnostics/overnight-diagnostic/FIX-PLAN.md
updated 2026-04-16

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:903require_exact_threshold=True default. - arbitrage/pairing.py:904min_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_secondsexpiry_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)