2ff9b6f6c1
- _compute_text_similarity() now checks outcome names with bidirectional substring matching (0.85) and token overlap (0.7), not just event titles - Collect outcomes from ALL active markets per event, filter to >1% price - Reorder outcome_prices to surface topic-matching outcome before top-3 truncation - Add SKILL.md "Prediction Markets" synthesis section with structural/long-term market preference, domain examples, citation format, and narrative weaving - Add Polymarket to citation priority list between HN and Web - Update stats box template to show up to 5 market odds - Fix render.py "vol24h" label to "volume" - Add NCAA seed fixture event for outcome-only matching tests - 82 polymarket tests pass (14 new) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
186 lines
11 KiB
Markdown
186 lines
11 KiB
Markdown
---
|
|
title: "feat: Smarter Polymarket synthesis - surface the most interesting markets"
|
|
type: feat
|
|
status: completed
|
|
date: 2026-02-26
|
|
---
|
|
|
|
# feat: Smarter Polymarket Synthesis
|
|
|
|
## Overview
|
|
|
|
Polymarket data is one of the most powerful signals when relevant - real money on outcomes cuts through opinion. But the current system buries the most interesting markets and gives the LLM zero guidance on how to synthesize prediction market data.
|
|
|
|
For "Arizona Basketball", the skill found 2 markets but only highlighted "Big 12 title: 68%" in the stats and synthesis. The user cares MORE about:
|
|
- NCAA Tournament championship odds (12%, up 3%)
|
|
- #1 seed odds (85%)
|
|
- Next game: Arizona vs Kansas (71% to win)
|
|
|
|
For "Iran War", the skill found 9 markets with $559M volume but only highlighted "strikes by Feb 28: 10% (down from 65%)". The user wanted regime change / Khamenei odds - the "bigger picture" structural question.
|
|
|
|
The problem has two layers: (1) the Python scoring penalizes multi-outcome markets where the topic is an outcome, and (2) the SKILL.md gives the LLM zero instructions for interpreting or highlighting prediction market data.
|
|
|
|
## Problem Statement
|
|
|
|
### Layer 1: Scoring penalizes the most interesting markets
|
|
|
|
`_compute_text_similarity()` (polymarket.py:236) only compares the search topic against the **event title**. It never checks outcome names.
|
|
|
|
Example: Market titled "Who will be the #1 overall seed in the 2026 NCAA Tournament?" with outcomes ["Arizona", "Duke", "Houston", "Auburn"]:
|
|
- Topic: "Arizona Basketball"
|
|
- text_score: **0.0** (neither "arizona" nor "basketball" in event title)
|
|
- 30% relevance penalty from this alone
|
|
|
|
This means the most contextually interesting markets (seeding, championship, matchup odds) get pushed below less interesting but title-matching markets (Big 12 regular season).
|
|
|
|
### Layer 2: SKILL.md has zero Polymarket synthesis guidance
|
|
|
|
The SKILL.md Judge Agent section tells the LLM how to weight Reddit (higher), YouTube (high), WebSearch (lower), but says **nothing** about:
|
|
- How to interpret prediction market probabilities
|
|
- Which markets are "most interesting" (championship > regular season)
|
|
- When to lead with prediction market odds vs other sources
|
|
- How to connect a specific outcome in a multi-outcome market to the user's topic
|
|
- How to use odds as a signal alongside social media sentiment
|
|
|
|
### Layer 3: Stats box loses information
|
|
|
|
The Polymarket stats line only has room for 1-2 market highlights. When there are 5+ relevant markets, the user misses the most interesting ones.
|
|
|
|
## Proposed Solution
|
|
|
|
### 1. Outcome-aware text similarity scoring
|
|
|
|
Update `_compute_text_similarity()` to check if the topic appears in any outcome name, with **bidirectional** substring matching. The check must work in both directions since the topic ("Arizona Basketball") is longer than the outcome name ("Arizona").
|
|
|
|
Collect outcome names from ALL active markets in the event (not just top market), since Gamma API can structure multi-outcome events as separate binary sub-markets.
|
|
|
|
Only match outcomes with probability > 1% to avoid noise from near-zero outcomes.
|
|
|
|
```python
|
|
def _compute_text_similarity(topic: str, title: str, outcomes: list = None) -> float:
|
|
core = _extract_core_subject(topic).lower()
|
|
title_lower = title.lower()
|
|
if not core:
|
|
return 0.5
|
|
|
|
# Full substring match in title
|
|
if core in title_lower:
|
|
return 1.0
|
|
|
|
# Check if topic appears in any outcome name (bidirectional)
|
|
if outcomes:
|
|
core_tokens = set(core.split()) # Hoist outside loop
|
|
best_outcome_score = 0.0
|
|
for outcome_name in outcomes:
|
|
outcome_lower = outcome_name.lower()
|
|
# Bidirectional substring: "arizona" in "arizona basketball" OR "arizona basketball" in "arizona wildcats game"
|
|
if core in outcome_lower or outcome_lower in core:
|
|
best_outcome_score = max(best_outcome_score, 0.85)
|
|
elif core_tokens & set(outcome_lower.split()):
|
|
best_outcome_score = max(best_outcome_score, 0.7)
|
|
if best_outcome_score > 0:
|
|
return best_outcome_score
|
|
|
|
# Token overlap fallback against title
|
|
topic_tokens = set(core.split())
|
|
title_tokens = set(title_lower.split())
|
|
if not topic_tokens:
|
|
return 0.5
|
|
overlap = len(topic_tokens & title_tokens)
|
|
return overlap / len(topic_tokens)
|
|
```
|
|
|
|
### 2. Surface the topic-matching outcome in display
|
|
|
|
When the topic matches an outcome name, reorder `outcome_prices` to put the matching outcome first before truncating to top 3. This ensures the LLM sees the user-relevant odds.
|
|
|
|
### 3. Add SKILL.md synthesis instructions for Polymarket
|
|
|
|
Add a dedicated section telling the LLM:
|
|
- **Prediction markets are high-signal when relevant.** Real money on outcomes > opinions.
|
|
- **Prefer markets that answer structural/long-term questions** (championships, regime changes, major milestones) over near-term deadline markets (weekly matchups, short-term event deadlines). When in doubt, the bigger question is more interesting.
|
|
- **When the topic is an outcome in a multi-outcome market, call out that specific outcome's odds and movement.** Don't just say "Polymarket has a #1 seed market" - say "Arizona has 85% chance of a #1 seed, up from 72%."
|
|
- **Weave odds into the "What I learned" narrative as supporting evidence.** "Final Four buzz is building - Polymarket gives Arizona a 12% chance to win the championship (up 3% this week), and 85% to earn a #1 seed."
|
|
- **Citation format:** "Polymarket has Arizona at 85% for a #1 seed (up from 72%)" - include the specific odds and movement, not just "per Polymarket."
|
|
- **Stats box:** Show up to 5 most relevant markets with odds. If more exist, show count.
|
|
|
|
Domain examples:
|
|
- Sports: championship/tournament odds > regular season title > weekly matchup
|
|
- Geopolitics: regime change > near-term strike deadline > sanctions
|
|
- Tech: major milestones (IPO, product launch) > incremental updates
|
|
- Elections: presidency > primary > individual state
|
|
|
|
### 4. Improve stats box template
|
|
|
|
Show up to 5 markets with odds, capped for readability:
|
|
```
|
|
├─ 📊 Polymarket: 5 markets (Championship: 12%, #1 Seed: 85%, Big 12: 68%, vs Kansas: 71%, NCAA: 12%)
|
|
```
|
|
|
|
### 5. Fix render.py volume label
|
|
|
|
The render module labels volume as "vol24h" even when `volume1mo` is the actual data source. Fix to "vol/mo" when monthly volume is used.
|
|
|
|
## Technical Approach
|
|
|
|
### Implementation Plan
|
|
|
|
#### Phase 1: Fix text similarity to check outcomes
|
|
|
|
- [x] `scripts/lib/polymarket.py` - Update `_compute_text_similarity()` to accept optional `outcomes` parameter with bidirectional substring matching and token overlap
|
|
- [x] `scripts/lib/polymarket.py` - In `parse_polymarket_response()`, collect outcome names from ALL active markets (not just top market), filter to outcomes with price > 1%, and pass to `_compute_text_similarity()`
|
|
- [x] `scripts/lib/polymarket.py` - Reorder `outcome_prices` to surface the topic-matching outcome first before truncating to top 3
|
|
- [x] `fixtures/polymarket_sample.json` - Add fixture event: "Who will be the #1 overall seed in the 2026 NCAA Tournament?" with outcomes ["Arizona", "Duke", "Houston", "Auburn"] where "Arizona" is NOT in the title
|
|
- [x] `tests/test_polymarket.py` - Add tests for outcome-aware text similarity: bidirectional substring, token overlap, no-match, low-probability filtering
|
|
- [x] `tests/test_polymarket.py` - Add test: multi-outcome market where topic is an outcome should rank higher than tangential title-match markets
|
|
- [x] `tests/test_polymarket.py` - Add test: topic-matching outcome is surfaced to front of outcome_prices display
|
|
|
|
#### Phase 2: Add SKILL.md Polymarket synthesis instructions
|
|
|
|
- [x] `SKILL.md` - Add "Prediction Markets" subsection to the Judge Agent section with:
|
|
- General heuristic: prefer structural/long-term markets over near-term deadlines
|
|
- Domain examples (sports, geopolitics, tech, elections)
|
|
- Citation format with specific odds and movement
|
|
- Instruction to weave odds into "What I learned" narrative
|
|
- [x] `SKILL.md` - Add Polymarket to citation priority list (between HN and Web) with format guidance
|
|
- [x] `SKILL.md` - Update stats box template: show up to 5 markets with odds
|
|
- [x] `variants/open/references/research.md` - Add condensed Polymarket synthesis guidance matching the open variant's style
|
|
- [x] `scripts/lib/render.py` - Fix "vol24h" label to "vol/mo" when volume1mo is the data source (changed to "volume")
|
|
|
|
#### Phase 3: Tests and verification
|
|
|
|
- [x] Run full test suite (229 passed, 5 pre-existing failures unrelated to this change)
|
|
- [ ] Manual test: `/last30days "Arizona Basketball"` - verify championship, seed, and matchup odds appear in synthesis
|
|
- [ ] Manual test: `/last30days "Iran War"` - verify regime change and structural outcome markets appear
|
|
- [x] Run `bash scripts/sync.sh` to deploy
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [x] Multi-outcome markets where the topic is an outcome (e.g. "Arizona" in a seeding market) get text_score >= 0.7, not 0.0
|
|
- [x] Bidirectional matching works: "Arizona" as outcome matches topic "Arizona Basketball" (outcome_name in core)
|
|
- [x] Topic-matching outcome is surfaced to front of outcome_prices display (not hidden in "and N more")
|
|
- [x] Low-probability outcomes (< 1%) don't trigger outcome matching
|
|
- [x] SKILL.md instructs the LLM to highlight structural/long-term markets over near-term ones
|
|
- [x] SKILL.md provides citation format: "Polymarket has X at Y% (up/down Z%)"
|
|
- [x] Stats box shows up to 5 markets with odds
|
|
- [x] HN zero-result line is hidden (already fixed - verify on next run)
|
|
- [x] All existing tests pass + new outcome-aware similarity tests pass
|
|
- [x] render.py volume label is accurate
|
|
|
|
## Dependencies & Risks
|
|
|
|
**No blockers.** This is scoring improvements + SKILL.md instruction changes within the existing Polymarket module.
|
|
|
|
**Risk: Outcome name matching false positives.** A market "Will Arizona pass AI regulation?" would match "Arizona Basketball" on the word "Arizona" even though it's about the state, not the team. Mitigation: outcome match gets 0.7-0.85 (not 1.0), and volume/liquidity/movement signals still differentiate. A false positive at 0.85 text_score won't outrank a true title match at 1.0.
|
|
|
|
**Risk: Common-word false positives.** Words like "war," "AI," "US" could match generic outcomes. Mitigation: at 0.7 text_score (30% weight = 0.21 relevance), this is a small boost that won't override strong volume/liquidity signals from actually relevant markets. Monitor in testing.
|
|
|
|
**Risk: LLM still ignores synthesis instructions.** Mitigation: use CRITICAL formatting, specific do/don't examples, and concrete citation format.
|
|
|
|
## Sources & References
|
|
|
|
- Current text similarity: `scripts/lib/polymarket.py:236`
|
|
- Render format: `scripts/lib/render.py:282`
|
|
- SKILL.md synthesis: `SKILL.md:196` (Judge Agent section)
|
|
- Previous quality ranking plan: `docs/plans/2026-02-25-feat-polymarket-quality-ranking-plan.md`
|