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 LayerRisk1.18 StaleBookGuard

1.18 StaleBookGuard

Risk Guardrail Reject PLANNED Spec ready capital · Direct P2 · Data normalisation pending stub

Rejects any OrderIntent priced against an order book older than the configured staleness threshold. The book may look healthy, but if its last update is too old, prices have almost certainly moved. StaleBookGuard fails closed: if it cannot prove the book is fresh, it rejects.

v3 readiness

Docs27/27
donehow scored
Impl11/15
in progresshow scored
Backtest3/4
in progresshow scored
Runtime0/8
pendinghow scored

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

v3.5 · wired registry id risk.stalebookguard

Maps to spec page stale_book_guard. SEARCH_SPACE declared. Fixture pack pending.

Source: @polytraders/bots · src/risk/stalebookguard.js · Impl 11/15 · Backtest 3/4

1. Bot Identity

LayerRisk  Risk
Bot classGuardrail
AuthorityReject
StatusPLANNED
ReadinessSpec ready
Runs beforeexec.smart_router
Runs afterintel.orderflowanalyzer
Applies toPer OrderIntent
Default modeshadow
User-visibleYes
Developer ownerRisk pod

Operational profile

OwnershipRisk pod · on-call risk-oncall · #polytraders-risk · escalates to Head of Risk · P2
Latency budgetp50: 1ms · p99: 5ms
Modes supportedoffshadowadvisoryenforcedquarantine
Data freshnessmax_market_data_age_ms=2000 · max_orderbook_age_ms=2000 · on stale → REJECT — that is precisely what this bot does.
Human overrideno · by · logs · time-bound: — · scope: — · single approver

2. Purpose

Rejects any OrderIntent priced against an order book older than the configured staleness threshold. The book may look healthy, but if its last update is too old, prices have almost certainly moved. StaleBookGuard fails closed: if it cannot prove the book is fresh, it rejects.

3. Why This Bot Matters

  • Trading on stale prices

    An OrderIntent priced against a 20-second-old book during a fast-moving event is essentially a market order; it will fill at whatever price the new book is.

  • Hidden feed lag

    WebSocket latency can spike without disconnecting; a healthy-looking connection can still be delivering 30-second-old data.

  • No central freshness check

    Without one canonical staleness rule, every strategy invents its own — some too lenient, some forgotten entirely.

4. Required Polymarket Inputs

InputSourceRequired?Use
OrderBookSnapshot.ts_ms per market_idintel.orderflowanalyzerYesThe single source of truth for book freshness.

5. Required Internal Inputs

InputSourceRequired?Use
OrderIntent.market_idStrategy botYesIdentifies which book to check.
Wall-clock now_msRuntimeYesCompute age of the book at decision time.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
max_book_age_ms200010002000Maximum allowed book age (ms) at which an OrderIntent may proceed.
warn_book_age_ms1000Soft warn threshold — logged but not blocking, used for observability.

7. Detailed Parameter Instructions

max_book_age_ms

What it means

Maximum allowed book age (ms) at which an OrderIntent may proceed.

Default

{ "max_book_age_ms": 2000 }

Why this default matters

2 seconds is the longest the most active markets can drift before mid-price has demonstrably moved.

Threshold logic

ConditionAction
≤ 1000msPASS
1000–2000msWARN
> 2000msREJECT

Developer check

if (now - book.tsMs > p.max_book_age_ms) return reject('RISK_BOOK_STALE');

User-facing English

We did not place this order because the latest market data was too old to trust.

warn_book_age_ms

What it means

Soft warn threshold — logged but not blocking, used for observability.

Default

{ "warn_book_age_ms": 1000 }

Why this default matters

Lets ops see slowly degrading feed health before it becomes a hard reject.

Threshold logic

ConditionAction
≤ 1000msSilent
> 1000msWARN log only

Developer check

if (now - book.tsMs > p.warn_book_age_ms) log.warn('BOOK_AGE_HIGH');

User-facing English

(Internal — not shown to users.)

8. Default Configuration

{
  "max_book_age_ms": 2000,
  "warn_book_age_ms": 1000
}

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.

snap = latest_snapshot(intent.market_id)
if snap is None: return reject('RISK_BOOK_STALE')
age = now_ms() - snap.ts_ms
if age > p.max_book_age_ms: return reject('RISK_BOOK_STALE', measured_age_ms=age)
if age > p.warn_book_age_ms: log_warn('BOOK_AGE_HIGH', age=age)
return pass_()

11. Wire Examples

Input — what arrives on the wire

{
  "intent_id": "intent_002",
  "market_id": "0xabc"
}

Output — what the bot emits

{
  "vote": "REJECT",
  "reason_code": "RISK_BOOK_STALE",
  "measured_age_ms": 3104
}

12. Decision Logic

APPROVE

Look up most recent book snapshot for market_id. Compute age in ms. Emit measured age as metric for every decision.

RESHAPE_REQUIRED

This bot does not reshape orders.

REJECT

Reject if age > max_book_age_ms.

WARNING_ONLY

No warn-only path defined.

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "vote": "REJECT",
  "reason_code": "RISK_BOOK_STALE",
  "measured_age_ms": 3104,
  "explain": "Book age 3104ms > 2000ms threshold."
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
RISK_BOOK_STALEP1Risk Book StaleSee decision output and developer log for context.We did not place this order because the latest market data was too old to trust.
RISK_BOOK_STALE_WARNP1Risk Book Stale WarnSee decision output and developer log for context.We did not place this order because the latest market data was too old to trust.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
book_age_ms_histogramcountereventbot_idBook age ms histogram.
rejects_with_book_stalecountereventmarket_id, reason_codeRejects with book stale.
warns_with_book_age_highcountereventbot_idWarns with book age high.

Dashboards

  • 1.18 overview dashboard

16. Developer Reporting

"Per decision: intent_id, market_id, book.ts_ms, decision_ts_ms, measured_age_ms, vote, reason_code."

17. Plain-English Reporting

SituationUser-facing explanation
When this bot actsWe did not place this order because the latest market data was too old to trust.

18. Failure-Mode Block

main_failure_modeRejecting orders when the book is fine but the freshness clock has drifted.
false_positive_riskClock skew between feed publisher and bot host; mitigation: bots use NTP-synced clocks and the data-flow layer stamps an ingest_ts_ms used as a fallback.
false_negative_riskA frozen book with a recent ts_ms (publisher bug) passes the check; mitigation: combine with MarketHaltDetector's TRADE_SILENCE rule.
safe_fallbackIf no snapshot exists at all, REJECT — never assume freshness.
required_dependencies

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
Pause the synthetic feed for 4 seconds and assert all in-flight intents are rejePause the synthetic feed for 4 seconds and assert all in-flight intents are rejected.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.
Inject a snapshot with future ts_ms and assert decision is still PASS (negative Inject a snapshot with future ts_ms and assert decision is still PASS (negative age).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

Stateless. Reads latest snapshot from the data-flow layer's in-memory cache.

State stores

NameKindKeyValue shapeTTLDurability
stale_book_guard_statein-memory + fast KV mirrorbot_idStateless. Reads latest snapshot from the data-flow layer's in-memory cache.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 modelPure function of (snapshot, now_ms). Trivially safe under concurrent calls.
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

Depends on (must run first)

Emits to (downstream consumers)

BotWhyContract
exec.smart_router

Requires (graph.requires)

intel.orderflowanalyzer

Required before (graph.required_before)

exec.smart_router

ConsumesOrderIntent OrderBookSnapshot
EmitsRiskVote
Blocks ordersyes

23. Security Surfaces

No external surface. Reads internal book cache only.

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
NotesOrderBookSnapshot.ts_ms field is a CLOB V2 standard property.

25. Versioning & Migration

FieldValue
current0.1.0
contract_version1.0.0
last_breaking_changenone
deprecation_window_days30

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Pass when age = max_book_age_ms - 1.Synthetic fixture per template.Behaviour matches the rule described in the test name.
Reject when age = max_book_age_ms + 1.Synthetic fixture per template.Behaviour matches the rule described in the test name.
Reject when no snapshot is present.Synthetic fixture per template.Behaviour matches the rule described in the test name.

Integration Tests

TestExpected result
End-to-end: synthetic CLOB stream paused for 4 seconds → all OrderIntents in the gap rejected.End-to-end behaviour matches the spec without manual intervention.

Property Tests

PropertyRequired behaviour
For any (book.ts_ms, now_ms) pair, vote is determined purely by their difference and the threshold.Always true across all generated inputs.

27. Operational Runbook

If reject rate spikes: first check feed health (ingest latency p99). If feed is healthy and rejects persist, suspect publisher clock drift.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
1.18_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.Risk pod

Manual overrides

  • polytraders bot pause 1.18 — Disables the bot's enforcement layer; downstream consumers fall back to safe defaults.

Healthcheck

GET /healthz/stale_book_guard → 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
Stub100% deterministic on fixtures.Documented threshold met for the full window.

Promote to Limited live

GateHow measuredThreshold
Shadow7 days; rejection rate < 0.5% on healthy feeds.Documented threshold met for the full window.
EnforcedRisk Lead sign-off.Documented threshold met for the full window.

Promote to General live

GateHow measuredThreshold
Owner sign-offBot owner reviews 14 days of advisory data.No P1 incidents attributable to this bot.

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