Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board
HomeBy LayerIntelligence4.17 MarketResolutionWatcher

4.17 MarketResolutionWatcher

Intelligence Signal Observe PLANNED Spec ready capital · Indirect P2 · Data normalisation pending stub

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

Docs27/27
donehow scored
Impl0/15
pendinghow scored
Backtest0/4
pendinghow scored
Runtime0/8
pendinghow scored

A bot is done when all four scores are. What does done mean?

1. Bot Identity

LayerIntelligence  Intelligence
Bot classSignal
AuthorityObserve
StatusPLANNED
ReadinessSpec ready
Runs beforerisk.pre_resolution_freeze
Runs after
Applies toContinuous
Default modeshadow
User-visibleNo
Developer ownerIntelligence pod

Operational profile

OwnershipIntelligence pod · on-call intel-oncall · #polytraders-intel · escalates to Head of Intelligence · P1
Latency budgetp50: 200ms · p99: 1500ms
Modes supportedoffshadowadvisoryenforced
Data freshnessmax_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 overrideyes · 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

InputSourceRequired?Use
Polymarket conditional resolution metadataPolymarket REST + on-chain eventsYesSource of truth for resolution time and status.
Per-market clock / now_msRuntimeYesDistance to scheduled resolution.

5. Required Internal Inputs

InputSourceRequired?Use
Open positions per marketPortfolioGuardNoDecides whether to escalate the warning severity.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
t_minus_warn_hours24246How far ahead of scheduled resolution to start emitting WARN signals.
t_minus_freeze_hours1How 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

ConditionAction
> 24hSilent
≤ 24hWARN signal
≤ 1hURGENT 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

ConditionAction
> 1hWARN only
≤ 1hFREEZE 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

CodeSeverityMeaningActionUser-facing message
INTEL_RESOLUTION_WARNinfoIntel Resolution WarnSee decision output and developer log for context.This market is close to resolving — the system stopped opening new positions on it.
INTEL_RESOLUTION_URGENTinfoIntel Resolution UrgentSee decision output and developer log for context.This market is close to resolving — the system stopped opening new positions on it.
INTEL_RESOLUTION_FREEZEinfoIntel Resolution FreezeSee decision output and developer log for context.This market is close to resolving — the system stopped opening new positions on it.
INTEL_RESOLUTION_RESOLVEDinfoIntel Resolution ResolvedSee 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

MetricTypeUnitLabelsMeaning
resolution_signals_totalcountereventbot_idResolution signals total.
active_markets_in_freeze_windowcountereventbot_idActive markets in freeze window.
resolution_metadata_fetch_failurescountereventbot_idResolution 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

SituationUser-facing explanation
When this bot actsThis market is close to resolving — the system stopped opening new positions on it.

18. Failure-Mode Block

main_failure_modeMisreading resolution time from Polymarket metadata.
false_positive_riskPolymarket metadata edits can shift scheduled_ts_ms forward; mitigation: emit the latest-known schedule rather than caching.
false_negative_riskMissed on-chain ConditionResolved event; mitigation: belt-and-braces — both REST poll and on-chain log subscription.
safe_fallbackOn metadata fetch failure, emit FREEZE for any market within 24h of its last-known schedule — fail closed.
required_dependencies

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
Inject metadata that changes scheduled_ts_ms backwards by 6 hours and assert dowInject 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 firesDrop 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

NameKindKeyValue shapeTTLDurability
market_resolution_watcher_statein-memory + fast KV mirrormarket_idPer-market last-emitted tier (latched). Persisted to durable KV.24hcrash-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

AspectSpecification
Execution modelSingle watcher process. Idempotent re-emission per tier.
Max in-flight32
Idempotency keyorder_intent_id
Replay-safeTrue
DeduplicationBy idempotency_key within a 60s window.
Ordering guaranteesPer-market_id FIFO; cross-market unordered.
Per-call timeout (ms)250
Backpressure strategyBounded queue; oldest-dropped with metric increment when full.
Locking / mutual exclusionPer-market_id mutex; no global locks.

22. Dependencies

Emits to (downstream consumers)

BotWhyContract
risk.pre_resolution_freeze

Required before (graph.required_before)

risk.pre_resolution_freeze

ConsumesPolymarketMetadata ConditionResolvedEvent
EmitsSignalEnvelope(kind=resolution_warning)
Blocks ordersno

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

AspectValue
CLOB versionV2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldyes
Aware of negative-risk marketsyes
Multi-chain readyyes
SDK usedPolymarket CLOB V2 SDK
Settlement contractCTFExchangeV2
NotesConditionResolved event ABI is identical between V1 and V2.

25. Versioning & Migration

FieldValue
current0.1.0
contract_version1.0.0
last_breaking_changenone
deprecation_window_days30

26. Acceptance Tests

Unit Tests

TestSetupExpected 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

TestExpected 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

PropertyRequired 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

AlertFirst stepDiagnosisMitigationEscalate to
4.17_anomalyOpen 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

GateHow measuredThreshold
Stubagainst fixture metadata.Documented threshold met for the full window.

Promote to Limited live

GateHow measuredThreshold
Shadow14 days; signals logged.Documented threshold met for the full window.
Advisory7 days.Documented threshold met for the full window.

Promote to General live

GateHow measuredThreshold
Enforcedsignals consumed by Risk's PreResolutionFreeze.Documented threshold met for the full window.

29. Developer Checklist

Ready-to-ship score: 27/27 sections complete · 100%

RequirementStatus
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