1. Bot Identity
| Layer | Discovery Discovery |
|---|
| Bot class | Signal Service |
|---|
| Authority | Read-onlyRecommend |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Strategy OrderIntent generation |
|---|
| Runs after | MarketScanner scan cycle |
|---|
| Applies to | All live Polymarket markets with a known resolution horizon |
|---|
| Default mode | shadow_only |
|---|
| User-visible | Advanced details only |
|---|
| Developer owner | Polytraders core — Intelligence pod |
|---|
2. Purpose
Map every Polymarket market to a known real-world calendar event (elections, sports fixtures, court dates, FOMC releases, earnings, debates) and emit a time-tagged ObservationReport so strategies can act on pre-event positioning logic.
3. Why This Bot Matters
Markets not linked to calendar events
Strategies cannot distinguish between a stagnant market and one where a price-moving event is imminent, missing pre-event edge opportunities.
Stale calendar data
Rescheduled or cancelled events leave strategies positioned on a timeline that no longer exists.
No worked examples on this bot yet. Worked examples are optional but strongly recommended — they turn an abstract failure mode into something a developer can verify in a fixture.
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|
| event_lookahead_h | 72 | 24 | 6 | How many hours ahead the mapper looks for calendar events to associate with markets. |
| min_event_relevance | 0.6 | 0.4 | 0.2 | Minimum NLP match score between market question and calendar event description for a mapping to be accepted. |
7. Detailed Parameter Instructions
event_lookahead_h
What it means
How many hours ahead the mapper looks for calendar events to associate with markets.
Default
{ "event_lookahead_h": 72 }
Why this default matters
72 hours gives strategies enough lead time to build or unwind pre-event positions without excessive false positives.
Threshold logic
| Condition | Action |
|---|
| >= 72h | Standard lookahead |
| 24–72h | Narrow window — WARN |
| < 6h | Reject — too short for pre-event positioning |
Developer check
if (h < params.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');
User-facing English
Markets linked to events happening within the next few days are flagged for time-sensitive strategies.
min_event_relevance
What it means
Minimum NLP match score between market question and calendar event description for a mapping to be accepted.
Default
{ "min_event_relevance": 0.6 }
Why this default matters
A relevance floor of 0.6 prevents spurious mappings while allowing reasonable headline variations.
Threshold logic
| Condition | Action |
|---|
| >= 0.6 | Map accepted |
| 0.4–0.6 | Map accepted with LOW_RELEVANCE flag |
| < 0.2 | Map rejected — CALENDAR_MISMATCH |
Developer check
if (score < params.hard) emit(HARD_REJECT, 'CALENDAR_MISMATCH');
User-facing English
Markets are only linked to calendar events when there is a strong match between the market question and the event.
8. Default Configuration
{
"bot_id": "disc.event_calendar_mapper",
"version": "0.1.0",
"mode": "shadow_only",
"defaults": {
"event_lookahead_h": 72,
"enabled_calendars": [
"elections",
"sports",
"macro",
"earnings"
],
"timezone_default": "UTC",
"min_event_relevance": 0.6
}
}
9. Implementation Flow
- On each mapping cycle, fetch all active markets from Gamma API.
- For each market, extract resolution date and normalise to UTC.
- Filter to markets with resolution date within event_lookahead_h.
- For each candidate, run NLP title-match against each enabled calendar feed to find the best-matching event.
- Accept mappings where match_score >= min_event_relevance; reject below hard floor with CALENDAR_MISMATCH.
- Emit ObservationReport with market_id, event_id, calendar_source, match_score, event_time_utc, hours_to_event.
- Log cycle summary with total markets mapped, unmatched count, and top mapping by relevance.
10. Reference Implementation
Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. IF/THEN/ELSE = decision. Translate directly to TypeScript, Python, Go, or Rust.
FUNCTION mappingCycle():
ks = FETCH internal.killswitch.status
IF ks.active: RETURN
markets = FETCH gamma.GET('/markets?active=true&closed=false')
IF markets IS NULL:
LOG ERROR 'Gamma API unavailable — halting mapping cycle'
RETURN
now_utc = current_time_utc()
horizon_cutoff = now_utc + params.event_lookahead_h * 3600
FOR market IN markets:
res_time = parse_utc(market.resolution_date)
IF res_time > horizon_cutoff: CONTINUE // outside lookahead
best_match = NULL; best_score = 0.0
FOR calendar IN params.enabled_calendars:
events = calendarFeed(calendar, res_time)
FOR event IN events:
score = nlpMatch(market.question, event.description)
IF score > best_score:
best_match = event; best_score = score
IF best_score < params.min_event_relevance.hard:
LOG reason=CALENDAR_MISMATCH; CONTINUE
warnings = []
IF best_score < params.min_event_relevance.default:
warnings.append('LOW_RELEVANCE')
hours_to_event = (parse_utc(best_match.time) - now_utc) / 3600
EMIT ObservationReport(market_id, best_match.id, best_match.calendar,
best_score, hours_to_event, warnings)
LOG cycle summary
SDK calls used
gamma.GET('/markets?active=true&closed=false')calendarFeed('elections', resolution_date)nlpMatch(market.question, event.description)
Complexity: O(M × C × E) where M=markets, C=calendars, E=events per calendar window
11. Wire Examples
Input — what arrives on the wire
Gamma market with election resolution date — gamma_api
{
"condition_id": "0x8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b",
"question": "Will Candidate A win the 2026 midterm election?",
"active": true,
"resolution_date": "2026-11-04T00:00:00Z",
"neg_risk": false
}
Output — what the bot emits
ObservationReport — election market mapped
{
"report_id": "0xccdd2233445566778899001122334455ccdd2233445566778899001122334455",
"bot_id": "disc.event_calendar_mapper",
"market_id": "0x8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b",
"event_id": "us-election-2026-nov-03",
"calendar_source": "elections",
"match_score": 0.87,
"event_time_utc": "2026-11-03T23:00:00Z",
"hours_to_event": 48.5,
"warnings": [],
"mapped_at_ms": 1746789000000
}
Reproduce locally
curl 'https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=100'
12. Decision Logic
APPROVE
Not applicable — EventCalendarMapper emits ObservationReports, not approvals.
RESHAPE_REQUIRED
Not applicable — read-only mapping bot.
REJECT
Markets that cannot be matched to any calendar event above the hard floor receive CALENDAR_MISMATCH and are not forwarded.
WARNING_ONLY
Markets matched with relevance between warning and default receive LOW_RELEVANCE annotation.
13. Standard Decision Output
This bot returns a ObservationReport object. See ObservationReport schema.
{
"report_id": "0xccdd2233445566778899001122334455ccdd2233445566778899001122334455",
"bot_id": "disc.event_calendar_mapper",
"market_id": "0x8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b",
"event_id": "us-election-2026-nov-03",
"calendar_source": "elections",
"match_score": 0.87,
"event_time_utc": "2026-11-03T23:00:00Z",
"hours_to_event": 48.5,
"warnings": [],
"mapped_at_ms": 1746789000000
}
14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|
CALENDAR_MISMATCH | HARD_REJECT | No calendar event matched above the hard relevance floor; market not mapped. | Exclude from mapping output; log market_id and best attempt score. | This market could not be linked to any known upcoming event. |
LOW_RELEVANCE | WARN | Best calendar match is between warning and default threshold. | Include mapping with LOW_RELEVANCE warning annotation. | This market is tentatively linked to an event but the match is not high-confidence. |
STALE_MARKET_DATA | HARD_REJECT | Gamma API unavailable; mapping cycle halted. | Halt cycle; retry on next interval. | |
KILL_SWITCH_ACTIVE | HARD_REJECT | KillSwitch is active; all emissions suppressed. | Return immediately; do not emit any ObservationReports. | |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_disc_eventcalendarmapper_markets_evaluated_total | counter | count | cycle | Total markets evaluated per mapping cycle. |
polytraders_disc_eventcalendarmapper_mappings_emitted_total | counter | count | calendar_source | Successful calendar mappings emitted, broken down by calendar source. |
polytraders_disc_eventcalendarmapper_match_score | histogram | ratio | | Distribution of NLP match scores for accepted mappings. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
EventCalendarMapperNoMappings | rate(polytraders_disc_eventcalendarmapper_mappings_emitted_total[30m]) == 0 | P2 | #runbook-eventcalendarmapper-no-mappings |
EventCalendarMapperGammaAPIDown | rate(polytraders_disc_eventcalendarmapper_markets_evaluated_total[5m]) == 0 | P1 | #runbook-eventcalendarmapper-gamma-api |
Dashboards
- Grafana — Discovery / EventCalendarMapper mapping coverage
Log levels
| Level | What gets logged |
|---|
| DEBUG | Per-market best match event, score, and calendar source. |
| INFO | Cycle summary: markets_evaluated, mappings_emitted. |
| WARN | Low mapping rate (<5%); calendar feed returning empty. |
| ERROR | Gamma API unavailable; all calendar feeds unreachable. |
16. Developer Reporting
{
"bot_id": "disc.event_calendar_mapper",
"cycle": 11,
"markets_evaluated": 312,
"markets_mapped": 24,
"markets_unmatched": 288,
"top_mapping": "0x8a9b... \u2192 us-election-2026-nov-03 (score 0.87)",
"killswitch_active": false,
"mapped_at": "2026-05-09T11:30:00Z"
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| Market shown with an upcoming event tag | This market is linked to a real-world event happening soon. Strategies that are time-sensitive will consider it for pre-event positioning. |
| Market not linked to any event | No matching calendar event was found for this market within the current lookahead window. |
18. Failure-Mode Block
| main_failure_mode | NLP title-match produces a false positive mapping linking a market to the wrong event, causing strategies to apply incorrect pre-event timing logic. |
|---|
| false_positive_risk | A market with a generic title (e.g. 'Will X win?') may incorrectly match multiple calendar events, causing the bot to emit duplicate conflicting mappings. |
|---|
| false_negative_risk | Markets with non-standard question phrasing may not match any calendar event above threshold, missing genuine pre-event signals. |
|---|
| safe_fallback | If Gamma API is unavailable, halt the mapping cycle with STALE_MARKET_DATA rather than emitting stale mappings. |
|---|
| required_dependencies | Gamma API live market list, Enabled calendar feed data, KillSwitch active flag |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
GAMMA_API_DOWN | Block TCP to gamma-api.polymarket.com | | Automatic on next cycle. |
ALL_CALENDARS_EMPTY | Return empty event lists from all calendar feeds | | Automatic when calendar feeds return data. |
KILL_SWITCH_ON | Set killswitch.active=true | | Emissions resume after KillSwitch reset. |
20. State & Persistence
Cold-start recovery
On cold start, calendar feeds are re-fetched on first cycle.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | single-threaded async loop |
| Max in-flight | 1 |
| Idempotency key | mapping_cycle_id |
| Per-call timeout (ms) | 10000 |
| Backpressure strategy | drop newest |
| Locking / mutual exclusion | none |
22. Dependencies
Depends on (must run first)
Emits to (downstream consumers)
Sibling bots (same OrderIntent)
External services
| Service | Endpoint | SLA assumed | On failure |
|---|
| Gamma API | https://gamma-api.polymarket.com | 99.9% / 500ms p99 | Halt cycle; retry next interval. |
| Calendar feed APIs | Various (elections, sports, FRED, earnings) | best-effort | Skip affected calendar source; log warning. |
23. Security Surfaces
Abuse vectors considered
- External calendar feed returning crafted event data to create false market mappings
Mitigations
- NLP match score threshold prevents low-confidence mappings from propagating
- All outputs are ObservationReports (read-only recommendations); downstream bots independently validate
24. Polymarket V2 Compatibility
| Aspect | Value |
|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | no |
| Aware of negative-risk markets | yes |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | Gamma API enableNegRisk flag used to group neg-risk bundles under a shared event_id. All timing uses UTC resolution dates from Gamma V2 market metadata. |
API surfaces declared
gammadatainternal
Networks supported
polygon
25. Versioning & Migration
| Field | Value |
|---|
| spec | 2.0.0 |
| implementation | 0.1.0 |
| schema | 2 |
| released | None |
| planned_release | Q4-2026 |
Migration history
| Date | From | To | Reason | Action taken |
|---|
| 2026-04-28 | n/a | v2-spec | Spec drafted post-CLOB-V2 cutover; bot not yet implemented | Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain) |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|
| Mapping below hard floor emits CALENDAR_MISMATCH | match_score=0.15, hard=0.2 | Market excluded from mapping output with CALENDAR_MISMATCH reason |
| Mapping in warning band gets LOW_RELEVANCE flag | match_score=0.5, warning=0.4, default=0.6 | ObservationReport emitted with warnings=['LOW_RELEVANCE'] |
| KillSwitch suppresses emissions | killswitch.active=true | No ObservationReports emitted |
Integration Tests
| Test | Expected result |
|---|
| Election market correctly maps to election calendar event | ObservationReport with calendar_source='elections' and hours_to_event > 0 |
| Gamma API outage halts cycle with STALE_MARKET_DATA | No reports emitted; resumes next cycle |
Property Tests
| Property | Required behaviour |
|---|
| hours_to_event always > 0 at time of emission | Always true — past events are not mapped |
| No report emitted when KillSwitch active | Always true |
27. Operational Runbook
EventCalendarMapper incidents are typically calendar feed outages or NLP threshold misconfiguration. Bot is read-only; incidents do not affect active positions.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
EventCalendarMapperNoMappings | | | | |
EventCalendarMapperGammaAPIDown | | | | |
Manual overrides
Healthcheck
GET /internal/health/eventcalendarmapper → green if Last cycle completed within 2× mapping interval; at least one event mapped in last 24h.; red if No cycle completed in 2× interval or zero mappings in 24h during active markets.
29. Developer Checklist
Ready-to-ship score: 27/27 sections complete · 100%
| Requirement | Status |
|---|
| Purpose defined | ✓ done |
| Required inputs listed | ✓ done |
| Parameters defined | ✓ done |
| Defaults defined | ✓ done |
| Warning thresholds defined | ✓ done |
| Hard thresholds defined | ✓ done |
| Safe fallback defined | ✓ done |
| Structured output defined | ✓ done |
| Developer log defined | ✓ done |
| Plain-English explanation | ✓ done |
| Unit tests defined | ✓ done |
| Integration tests defined | ✓ done |
| Property tests defined | ✓ done |
| Failure-mode block complete | ✓ done |
| Reference implementation pseudocode | ✓ done |
| Wire examples (input + output) | ✓ done |
| Reason codes listed | ✓ done |
| Metrics & logs defined | ✓ done |
| State & persistence defined | ✓ done |
| Concurrency & idempotency defined | ✓ done |
| Dependencies declared | ✓ done |
| Security surfaces declared | ✓ done |
| Polymarket V2 compatibility declared | ✓ done |
| Version & migration history declared | ✓ done |
| Operational runbook defined | ✓ done |
| Promotion gates defined | ✓ done |
| Failure-injection recipes defined | ✓ done |