4.17 MarketResolutionWatcher
Watches the on-chain resolution state of every active Polymarket market. As a market approaches resolution, it emits typed warnings (T-24h, T-1h, finalising, resolved) so downstream bots can stop opening new positions and start unwinding. Pure observer — never blocks; the actual blocking is done by Risk's PreResolutionFreeze guardrail downstream.
v3 readiness
A bot is done when all four scores are. What does done mean?
1. Bot Identity
| Layer | Intelligence Intelligence |
|---|---|
| Bot class | Signal |
| Authority | Observe |
| Status | PLANNED |
| Readiness | Spec ready |
| Runs before | risk.pre_resolution_freeze |
| Runs after | — |
| Applies to | Continuous |
| Default mode | shadow |
| User-visible | No |
| Developer owner | Intelligence pod |
Operational profile
| Ownership | Intelligence pod · on-call intel-oncall · #polytraders-intel · escalates to Head of Intelligence · P1 |
|---|---|
| Latency budget | p50: 200ms · p99: 1500ms |
| Modes supported | offshadowadvisoryenforced |
| Data freshness | max_market_data_age_ms=60000 · max_orderbook_age_ms=60000 · max_external_feed_age_ms=60000 · on stale → Emit FREEZE if any market is within 24h of last-known scheduled_ts_ms. |
| Human override | yes · by Intel on-call · logs INTEL_RESOLUTION_OVERRIDE · time-bound: Until next poll · scope: Single market_id · single approver |
2. Purpose
Watches the on-chain resolution state of every active Polymarket market. As a market approaches resolution, it emits typed warnings (T-24h, T-1h, finalising, resolved) so downstream bots can stop opening new positions and start unwinding. Pure observer — never blocks; the actual blocking is done by Risk's PreResolutionFreeze guardrail downstream.
3. Why This Bot Matters
Trading into resolution
Once a market is in its finalising window, prices can become extremely sticky and orders can sit unfilled until expiry; opening fresh exposure here is high regret.
Missing the unwind window
Without an explicit signal, strategies do not know when to switch from open-mode to unwind-mode.
Resolution-driven losses
Bots that hold a position through a resolution event with the wrong outcome lose 100% of that position; the unwind window must be respected.
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.
4. Required Polymarket Inputs
| Input | Source | Required? | Use |
|---|---|---|---|
| Polymarket conditional resolution metadata | Polymarket REST + on-chain events | Yes | Source of truth for resolution time and status. |
| Per-market clock / now_ms | Runtime | Yes | Distance to scheduled resolution. |
5. Required Internal Inputs
| Input | Source | Required? | Use |
|---|---|---|---|
| Open positions per market | PortfolioGuard | No | Decides whether to escalate the warning severity. |
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|---|---|---|---|
| t_minus_warn_hours | 24 | 24 | 6 | How far ahead of scheduled resolution to start emitting WARN signals. |
| t_minus_freeze_hours | 1 | — | — | How close to resolution before the FREEZE signal is emitted (consumed by Risk's PreResolutionFreeze guardrail). |
7. Detailed Parameter Instructions
t_minus_warn_hours
What it means
How far ahead of scheduled resolution to start emitting WARN signals.
Default
{ "t_minus_warn_hours": 24 }
Why this default matters
24 hours gives strategies a full trading day to plan an unwind.
Threshold logic
| Condition | Action |
|---|---|
| > 24h | Silent |
| ≤ 24h | WARN signal |
| ≤ 1h | URGENT signal |
Developer check
if (hoursToResolve <= p.t_minus_warn_hours) emit('WARN', hoursToResolve);
User-facing English
(Internal — not shown to users.)
t_minus_freeze_hours
What it means
How close to resolution before the FREEZE signal is emitted (consumed by Risk's PreResolutionFreeze guardrail).
Default
{ "t_minus_freeze_hours": 1 }
Why this default matters
1 hour is short enough to allow last-minute unwind but long enough to avoid spurious finalising-state flapping.
Threshold logic
| Condition | Action |
|---|---|
| > 1h | WARN only |
| ≤ 1h | FREEZE signal |
Developer check
if (hoursToResolve <= p.t_minus_freeze_hours) emit('FREEZE', hoursToResolve);
User-facing English
(Internal — not shown to users.)
8. Default Configuration
{
"t_minus_warn_hours": 24,
"t_minus_freeze_hours": 1
}9. Implementation Flow
— not yet authored —
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.
for each market in active_markets():
meta = polymarket.metadata(market.id)
hours = (meta.scheduled_ts_ms - now_ms())/3_600_000
tier = pick_tier(hours, p)
emit('resolution_warning', market.id, tier, hours)
subscribe('ConditionResolved', lambda e: emit('resolution_warning', e.market_id, 'RESOLVED', 0))11. Wire Examples
Input — what arrives on the wire
{
"market_id": "0xabc",
"scheduled_ts_ms": 1715263600000,
"now_ms": 1715260000000
}
Output — what the bot emits
{
"kind": "resolution_warning",
"tier": "FREEZE",
"hours_to_resolve": 0.42
}12. Decision Logic
APPROVE
Compute hours_to_resolve = max(0, scheduled_ts_ms - now_ms) / 3.6e6. Emit at most one signal per (market_id, tier) per polling cycle. Latch RESOLVED forever once the on-chain event arrives.
RESHAPE_REQUIRED
This bot does not reshape orders.
REJECT
No reject path defined for this bot — it is observe-only.
WARNING_ONLY
No warn-only path defined.
13. Standard Decision Output
This bot returns a RiskVote object. See RiskVote schema.
{
"kind": "resolution_warning",
"market_id": "0xabc",
"tier": "FREEZE",
"hours_to_resolve": 0.42,
"ts_ms": 1715260000000
}14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|---|---|---|---|
INTEL_RESOLUTION_WARN | info | Intel Resolution Warn | See decision output and developer log for context. | This market is close to resolving — the system stopped opening new positions on it. |
INTEL_RESOLUTION_URGENT | info | Intel Resolution Urgent | See decision output and developer log for context. | This market is close to resolving — the system stopped opening new positions on it. |
INTEL_RESOLUTION_FREEZE | info | Intel Resolution Freeze | See decision output and developer log for context. | This market is close to resolving — the system stopped opening new positions on it. |
INTEL_RESOLUTION_RESOLVED | info | Intel Resolution Resolved | See decision output and developer log for context. | This market is close to resolving — the system stopped opening new positions on it. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|---|---|---|---|
resolution_signals_total | counter | event | bot_id | Resolution signals total. |
active_markets_in_freeze_window | counter | event | bot_id | Active markets in freeze window. |
resolution_metadata_fetch_failures | counter | event | bot_id | Resolution metadata fetch failures. |
Dashboards
- 4.17 overview dashboard
16. Developer Reporting
"Per emission: market_id, tier, hours_to_resolve, scheduled_ts_ms, on_chain_event (if any)."17. Plain-English Reporting
| Situation | User-facing explanation |
|---|---|
| When this bot acts | This market is close to resolving — the system stopped opening new positions on it. |
18. Failure-Mode Block
| main_failure_mode | Misreading resolution time from Polymarket metadata. |
|---|---|
| false_positive_risk | Polymarket metadata edits can shift scheduled_ts_ms forward; mitigation: emit the latest-known schedule rather than caching. |
| false_negative_risk | Missed on-chain ConditionResolved event; mitigation: belt-and-braces — both REST poll and on-chain log subscription. |
| safe_fallback | On metadata fetch failure, emit FREEZE for any market within 24h of its last-known schedule — fail closed. |
| required_dependencies | — |
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|---|---|---|
Inject metadata that changes scheduled_ts_ms backwards by 6 hours and assert dow | Inject metadata that changes scheduled_ts_ms backwards by 6 hours and assert downstream tier shifts. | Bot detects within its latency budget and emits the corresponding reason code. | Remove the injected fault; bot returns to healthy state within one debounce window. |
Drop the REST call for 5 minutes and assert FREEZE-on-stale fallback fires | Drop the REST call for 5 minutes and assert FREEZE-on-stale fallback fires. | Bot detects within its latency budget and emits the corresponding reason code. | Remove the injected fault; bot returns to healthy state within one debounce window. |
20. State & Persistence
Per-market last-emitted tier (latched). Persisted to durable KV.
State stores
| Name | Kind | Key | Value shape | TTL | Durability |
|---|---|---|---|---|---|
market_resolution_watcher_state | in-memory + fast KV mirror | market_id | Per-market last-emitted tier (latched). Persisted to durable KV. | 24h | crash-safe via KV mirror |
Cold-start recovery
Cold-start hydrates from fast KV; missing keys default to safe fallback.
On restart
All in-flight decisions are re-evaluated; no bot decision is trusted across restart without re-emit.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|---|
| Execution model | Single watcher process. Idempotent re-emission per tier. |
| Max in-flight | 32 |
| Idempotency key | order_intent_id |
| Replay-safe | True |
| Deduplication | By idempotency_key within a 60s window. |
| Ordering guarantees | Per-market_id FIFO; cross-market unordered. |
| Per-call timeout (ms) | 250 |
| Backpressure strategy | Bounded queue; oldest-dropped with metric increment when full. |
| Locking / mutual exclusion | Per-market_id mutex; no global locks. |
22. Dependencies
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|---|---|
risk.pre_resolution_freeze |
Required before (graph.required_before)
risk.pre_resolution_freeze
| Consumes | PolymarketMetadata ConditionResolvedEvent |
|---|---|
| Emits | SignalEnvelope(kind=resolution_warning) |
| Blocks orders | no |
23. Security Surfaces
Reads Polymarket REST + Ethereum logs. No write surface.
Signing surface
None — bot does not sign or submit.
Mitigations
- Rate-limit per source
- Audit-log every override
- Require role-based authz on admin paths
24. Polymarket V2 Compatibility
| Aspect | Value |
|---|---|
| CLOB version | V2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | yes |
| Aware of negative-risk markets | yes |
| Multi-chain ready | yes |
| SDK used | Polymarket CLOB V2 SDK |
| Settlement contract | CTFExchangeV2 |
| Notes | ConditionResolved event ABI is identical between V1 and V2. |
25. Versioning & Migration
| Field | Value |
|---|---|
| current | 0.1.0 |
| contract_version | 1.0.0 |
| last_breaking_change | none |
| deprecation_window_days | 30 |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|---|---|
| At T-23h emits WARN. | Synthetic fixture per template. | Behaviour matches the rule described in the test name. |
| At T-30m emits FREEZE. | Synthetic fixture per template. | Behaviour matches the rule described in the test name. |
| On ConditionResolved emits RESOLVED. | Synthetic fixture per template. | Behaviour matches the rule described in the test name. |
Integration Tests
| Test | Expected result |
|---|---|
| Replay a Polymarket fixture market through its 48-hour run-up to resolution; assert tiered signals are emitted in order. | End-to-end behaviour matches the spec without manual intervention. |
Property Tests
| Property | Required behaviour |
|---|---|
| Tier transitions are monotonic per market: SILENT → WARN → URGENT → FREEZE → RESOLVED. | Always true across all generated inputs. |
27. Operational Runbook
If signals stop emitting: check Polymarket REST credentials and chain RPC health.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|---|---|---|---|
4.17_anomaly | Open the bot's reporting page and confirm the alert is real (not a metric hiccup). | Inspect developer log entries for the affected market_id over the last 30 minutes. | Force-clear via Admin UI if the rule is clearly stale; otherwise leave engaged and notify owner. | Intelligence pod |
Manual overrides
polytraders bot pause 4.17— Disables the bot's enforcement layer; downstream consumers fall back to safe defaults.
Healthcheck
GET /healthz/market_resolution_watcher → 200 if last successful evaluation < 60s ago.28. Promotion Gates
A bot does not advance to the next readiness state until every gate below is green. Gates are observable from production data — no subjective sign-off.
Promote to Shadow
| Gate | How measured | Threshold |
|---|---|---|
| Stub | against fixture metadata. | Documented threshold met for the full window. |
Promote to Limited live
| Gate | How measured | Threshold |
|---|---|---|
| Shadow | 14 days; signals logged. | Documented threshold met for the full window. |
| Advisory | 7 days. | Documented threshold met for the full window. |
Promote to General live
| Gate | How measured | Threshold |
|---|---|---|
| Enforced | signals consumed by Risk's PreResolutionFreeze. | Documented threshold met for the full window. |
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 |