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 LayerExecution2.7 FillQualityAnalyzer

2.7 FillQualityAnalyzer

Execution Execution Utility Reshape PLANNED Spec started capital · Direct P5 · Execution rails pending stub

FillQualityAnalyzer scores every completed fill on price slippage, execution latency, and post-fill toxicity relative to the original intent. Low-scoring fills generate WARN annotations; systematic poor scores feed back to strategy tuning.

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

LayerExecution  Execution
Bot classExecution Utility
AuthorityReshape
StatusPLANNED
ReadinessSpec started
Runs beforePost-trade governance pipeline
Runs afterOrderLifecycleManager (FILLED event received)
Applies toEvery completed fill event emitted by OrderLifecycleManager
Default modeshadow_only
User-visiblesummary-only
Developer ownerPolytraders core — Execution pod

Operational profile

Modes supportedquarantine

2. Purpose

FillQualityAnalyzer scores every completed fill on price slippage, execution latency, and post-fill toxicity relative to the original intent. Low-scoring fills generate WARN annotations; systematic poor scores feed back to strategy tuning.

3. Why This Bot Matters

  • Slippage not tracked per fill

    Execution costs compound silently; strategy returns eroded without visibility into the cause.

  • Post-fill toxicity not measured

    The system continues submitting orders on markets with chronic adverse selection, accumulating losses.

  • Fill latency not profiled

    Slow fills on fast-moving markets indicate queue-position issues that smarter order type selection could fix.

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
CLOB V2 trade tape — last 100 fills on marketws_marketYesCompute post-fill mark drift over toxicity_horizon_s for toxicity score.
Market mid at time of fill (top-of-book snapshot)clob_publicYesCompute arrival mid for slippage calculation.

5. Required Internal Inputs

InputSourceRequired?Use
ExecutionReport (FILLED) from OrderLifecycleManagerexec.orderlifecyclemanagerYesSource fill price, size, intent price, and submit_ms for all score dimensions.
Original DecisionReport (intent price)strat layer via internal busNoCompare fill price against decision-time price to compute decision-to-fill slippage.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
toxicity_horizon_s3060120Seconds after fill to measure post-fill mark drift as a toxicity proxy.
min_score_for_repeat0.60.40.2Minimum fill quality score (0–1) for the strategy to be permitted to repeat on the same market without a WARN annotation.
warn_threshold_bps2040100Slippage in bps relative to arrival mid above which a WARN annotation is added to the ExecutionReport.
publish_to['polytraders.reports.execution']List of bus topics to which the fill quality ExecutionReport is published.

7. Detailed Parameter Instructions

toxicity_horizon_s

What it means

Seconds after fill to measure post-fill mark drift as a toxicity proxy.

Default

{ "toxicity_horizon_s": 30 }

Why this default matters

30s captures the immediate adverse-selection window without confounding long-term price moves.

Threshold logic

ConditionAction
horizon <= 30sNormal toxicity measurement
30 < horizon <= 60sWARN — extended horizon may include non-toxic drift
horizon > 120sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

assert params.toxicity_horizon_s <= params.hard

User-facing English

Fill quality is assessed over a short window after your order is executed.

min_score_for_repeat

What it means

Minimum fill quality score (0–1) for the strategy to be permitted to repeat on the same market without a WARN annotation.

Default

{ "min_score_for_repeat": 0.6 }

Why this default matters

A score below 0.6 indicates the fill underperformed the intent on at least two of three dimensions.

Threshold logic

ConditionAction
score >= 0.6PASS — no annotation
0.4 <= score < 0.6WARN — FILL_QUALITY_BELOW_THRESHOLD
score < 0.2 (hard)HARD_REJECT — FILL_QUALITY_CRITICAL; forward to risk pipeline

Developer check

if score < params.min_score_for_repeat: emit(FILL_QUALITY_BELOW_THRESHOLD)

User-facing English

Your order was executed, but the fill quality was lower than expected.

warn_threshold_bps

What it means

Slippage in bps relative to arrival mid above which a WARN annotation is added to the ExecutionReport.

Default

{ "warn_threshold_bps": 20 }

Why this default matters

20 bps represents approximately one spread width on typical Polymarket markets; beyond this, slippage is notable.

Threshold logic

ConditionAction
slippage_bps <= 20No annotation
20 < slippage_bps <= 40WARN — FILL_SLIPPAGE_ELEVATED
slippage_bps > 100HARD_REJECT — FILL_SLIPPAGE_CRITICAL

Developer check

if slippage_bps > params.warn_threshold_bps: emit(FILL_SLIPPAGE_ELEVATED)

User-facing English

Your order filled at a price slightly different from what was expected.

publish_to

What it means

List of bus topics to which the fill quality ExecutionReport is published.

Default

{ "publish_to": ["polytraders.reports.execution"] }

Why this default matters

Publishing only to the execution topic keeps fill quality data within the exec layer; adding post-trade topics extends visibility to governance.

Threshold logic

ConditionAction
includes polytraders.reports.executionNormal — exec layer receives score
empty listWARN — fill quality scores not published; governance cannot audit

Developer check

assert len(params.publish_to) > 0

User-facing English

Your fill details are recorded and available in your trade summary.

8. Default Configuration

{
  "bot_id": "exec.fillqualityanalyzer",
  "version": "0.1.0",
  "mode": "shadow_only",
  "defaults": {
    "toxicity_horizon_s": 30,
    "min_score_for_repeat": 0.6,
    "warn_threshold_bps": 20,
    "publish_to": [
      "polytraders.reports.execution"
    ]
  },
  "locked": {
    "toxicity_horizon_s": {
      "max": 120
    },
    "warn_threshold_bps": {
      "max": 100
    }
  }
}

9. Implementation Flow

  1. Receive ExecutionReport(FILLED) from OrderLifecycleManager.
  2. Fetch CLOB V2 top-of-book snapshot from clob_public at fill_ts_ms to get arrival_mid.
  3. Compute slippage_bps = abs(fill_price - arrival_mid) / arrival_mid * 10000.
  4. Compute latency_ms = fill_ts_ms - submit_ms from the ExecutionReport.
  5. Wait toxicity_horizon_s; fetch trade tape from ws_market to compute post-fill drift_bps.
  6. Score dimensions: price_score = max(0, 1 - slippage_bps/100); latency_score = max(0, 1 - latency_ms/500); toxicity_score = max(0, 1 - drift_bps/100).
  7. Overall score = mean(price_score, latency_score, toxicity_score).
  8. Annotate ExecutionReport with scores and reason codes (FILL_QUALITY_BELOW_THRESHOLD if score < min_score_for_repeat).
  9. Emit annotated ExecutionReport to publish_to topics.

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 analyzeFill(executionReport):
  // 1. Fetch arrival mid at fill time
  book = FETCH clob_public.GET('/book?market=' + executionReport.market_id)
  arrivalMid = (book.best_bid + book.best_ask) / 2

  // 2. Slippage score
  slippageBps = abs(executionReport.fill_price - arrivalMid) / arrivalMid * 10000
  priceScore = max(0, 1 - slippageBps / 100)
  IF slippageBps > params.warn_threshold_bps:
    EMIT reason(FILL_SLIPPAGE_ELEVATED)

  // 3. Latency score
  latencyMs = executionReport.fill_ts_ms - executionReport.submit_ms
  latencyScore = max(0, 1 - latencyMs / 500)

  // 4. Toxicity score (deferred by toxicity_horizon_s)
  WAIT params.toxicity_horizon_s * 1000
  tape = FETCH ws_market.recent_fills(executionReport.market_id, last_n=100)
  IF tape IS NULL:
    toxicityScore = None
    EMIT reason(FILL_QUALITY_TOXICITY_UNAVAILABLE, WARN)
  ELSE:
    driftBps = computeDrift(tape, executionReport.fill_price, params.toxicity_horizon_s)
    toxicityScore = max(0, 1 - driftBps / 100)

  // 5. Overall score and verdict
  scores = [s FOR s IN [priceScore, latencyScore, toxicityScore] IF s IS NOT None]
  overallScore = mean(scores)
  IF overallScore < 0.2:
    verdict = 'FILL_QUALITY_CRITICAL'
  ELIF overallScore < params.min_score_for_repeat:
    verdict = 'FILL_QUALITY_BELOW_THRESHOLD'
  ELSE:
    verdict = 'FILL_QUALITY_PASS'

  // 6. Annotate and emit
  executionReport.fill_quality = {slippageBps, latencyMs, driftBps, overallScore, verdict}
  EMIT ExecutionReport(executionReport) TO params.publish_to

SDK calls used

  • clob_public.GET('/book?market=' + market_id)
  • ws_market.recent_fills(market_id, last_n=100)

Complexity: O(F) where F = tape size (capped at 100)

11. Wire Examples

Input — what arrives on the wire

ExecutionReport (FILLED) from OrderLifecycleManagerexec.orderlifecyclemanager

{
  "order_id": "0xbbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888cccc9999",
  "status": "FILLED",
  "fill_price": 0.621,
  "fill_usd": 450,
  "submit_ms": 1746770000000,
  "fill_ts_ms": 1746770000085,
  "collateral": "pUSD"
}

Output — what the bot emits

Annotated ExecutionReport with fill quality scores

{
  "report_id": "rep_2b3c4d5e6f7a8b9c",
  "bot_id": "exec.fillqualityanalyzer",
  "verdict": "FILL_QUALITY_PASS",
  "slippage_bps": 1.6,
  "latency_ms": 85,
  "drift_bps": 4,
  "overall_score": 0.92,
  "collateral": "pUSD",
  "evaluated_at_ms": 1746770030000
}

12. Decision Logic

APPROVE

Fill quality score >= min_score_for_repeat; ExecutionReport annotated with FILL_QUALITY_PASS.

RESHAPE_REQUIRED

Not applicable — FillQualityAnalyzer is post-fill; it annotates but cannot reshape a completed fill.

REJECT

Score below hard threshold (0.2); FILL_QUALITY_CRITICAL emitted; alert forwarded to risk pipeline.

WARNING_ONLY

Score between warn threshold and hard threshold; FILL_QUALITY_BELOW_THRESHOLD annotation added.

13. Standard Decision Output

This bot returns a ExecutionReport object. See ExecutionReport schema.

{
  "report_id": "rep_2b3c4d5e6f7a8b9c",
  "trace_id": "trc_1a2b3c4d5e6f7a8b",
  "bot_id": "exec.fillqualityanalyzer",
  "order_id": "0xbbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888cccc9999",
  "market_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
  "fill_price": 0.621,
  "arrival_mid": 0.62,
  "slippage_bps": 1.6,
  "latency_ms": 85,
  "drift_bps": 4,
  "price_score": 0.98,
  "latency_score": 0.83,
  "toxicity_score": 0.96,
  "overall_score": 0.92,
  "verdict": "FILL_QUALITY_PASS",
  "collateral": "pUSD",
  "evaluated_at_ms": 1746770030000
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
FILL_QUALITY_PASSINFOFill quality score >= min_score_for_repeat on all dimensions.Emit annotated ExecutionReport; no further action.
FILL_QUALITY_BELOW_THRESHOLDWARNOverall fill score below min_score_for_repeat.Annotate ExecutionReport; emit WARN to strategy layer.Your order was filled with below-average execution quality.
FILL_QUALITY_CRITICALHARD_REJECTOverall fill score below 0.2 — severe slippage, latency, or toxicity.Emit HARD_REJECT; alert risk pipeline.Your order filled with significant adverse conditions.
FILL_SLIPPAGE_ELEVATEDWARNSlippage exceeded warn_threshold_bps.Annotate ExecutionReport.Your fill price differed from the market mid more than expected.
FILL_QUALITY_TOXICITY_UNAVAILABLEWARNPost-fill trade tape unavailable; toxicity_score not computed.Omit toxicity_score from overall; note in ExecutionReport.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_exec_fillqualityanalyzer_scoreshistogramscoreverdictDistribution of overall fill quality scores per verdict.
polytraders_exec_fillqualityanalyzer_slippage_bpshistogrambpsmarket_idDistribution of slippage in bps across all scored fills.
polytraders_exec_fillqualityanalyzer_drift_bpshistogrambpsDistribution of post-fill drift bps over toxicity_horizon_s.
polytraders_exec_fillqualityanalyzer_critical_totalcountercountTotal fills scoring below 0.2 (FILL_QUALITY_CRITICAL).

Alerts

AlertConditionSeverityRunbook
FQACriticalFillRaterate(polytraders_exec_fillqualityanalyzer_critical_total[10m]) > 0.05P1#runbook-fqa-critical
FQAHighSlippagehistogram_quantile(0.95, rate(polytraders_exec_fillqualityanalyzer_slippage_bps_bucket[5m])) > 40P2#runbook-fqa-slippage

16. Developer Reporting

{
  "report_id": "rep_2b3c4d5e6f7a8b9c",
  "slippage_bps": 1.6,
  "latency_ms": 85,
  "drift_bps": 4,
  "price_score": 0.98,
  "latency_score": 0.83,
  "toxicity_score": 0.96,
  "overall_score": 0.92,
  "min_score_for_repeat": 0.6,
  "warn_threshold_bps": 20
}

17. Plain-English Reporting

SituationUser-facing explanation
Fill quality passYour order was filled at a good price with low slippage and no signs of adverse market conditions.
Fill quality below thresholdYour order was filled, but the execution quality was lower than expected — the price moved slightly against you near the time of the fill.
Fill quality criticalYour order filled with significant slippage or post-fill adverse movement. This market may have been experiencing unusual conditions at the time.

18. Failure-Mode Block

main_failure_modePost-fill trade tape unavailable because ws_market feed is lagged, causing toxicity_score to default to 1.0 (optimistic), masking genuine adverse selection.
false_positive_riskBrief price spike at fill time causes high slippage_bps reading, triggering FILL_QUALITY_BELOW_THRESHOLD when the fill was actually at a reasonable price.
false_negative_risktoxicity_horizon_s too short to capture slow-moving adverse drift, producing a high toxicity_score on a genuinely toxic fill.
safe_fallbackIf ws_market tape is unavailable, set toxicity_score=None and emit WARN with FILL_QUALITY_TOXICITY_UNAVAILABLE; do not block ExecutionReport publication.
required_dependenciesws_market trade tape, clob_public top-of-book snapshot at fill time, ExecutionReport(FILLED) from OrderLifecycleManager

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
WS_MARKET_TAPE_UNAVAILABLEDisconnect ws_market feed before toxicity_horizon_s elapsesFeed reconnects; next fill scored normally
HIGH_SLIPPAGE_FILLInject fill_price=0.70 when arrival_mid=0.62Automatic on next fill
CLOB_PUBLIC_UNAVAILABLEBlock clob_public at fill timeAutomatic on next fill

20. State & Persistence

Cold-start recovery

WAL replayed on restart; in-memory cache rebuilt from last 1000 fills.

21. Concurrency & Idempotency

AspectSpecification
Execution modelasync per-fill coroutine (deferred by toxicity_horizon_s)
Max in-flight100
Idempotency keyorder_id + fill_ts_ms
Per-call timeout (ms)35000
Backpressure strategyDrop oldest pending if > 100 fills awaiting toxicity measurement
Locking / mutual exclusionnone — read-only annotation on immutable ExecutionReport

22. Dependencies

Depends on (must run first)

BotWhyContract
exec.orderlifecyclemanagerProvides FILLED ExecutionReport with fill_price, size, submit_ms.FillQualityAnalyzer activates only on FILLED status events.

Emits to (downstream consumers)

BotWhyContract
gov.posttradeexplainerAnnotated fill quality scores feed the post-trade explanation layer.ExecutionReport with quality annotations published to polytraders.reports.execution.

External services

ServiceEndpointSLA assumedOn failure
CLOB V2 public APIhttps://clob.polymarket.com99.9% / 200ms p99arrivalMid set to None; price_score omitted from overall.
WS market feedwss://ws-subscriptions-clob.polymarket.com/ws/marketbest-efforttoxicity_score omitted; FILL_QUALITY_TOXICITY_UNAVAILABLE emitted.

23. Security Surfaces

Abuse vectors considered

  • Injecting a fabricated FILLED event with a favourable fill_price to suppress a poor-quality fill from scoring
  • Replaying old ExecutionReports to inflate fill quality metrics

Mitigations

  • ExecutionReport messages validated against originating bot HMAC before scoring
  • Idempotency key (order_id + fill_ts_ms) prevents duplicate scoring

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesAll fill prices and slippage amounts are denominated in pUSD; no fee fields on signed orders — fees are operator-set at match time by CTFExchangeV2.

API surfaces declared

clob_publicws_marketinternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ4-2026

Migration history

DateFromToReasonAction taken
2026-04-28n/av2-specSpec drafted post-CLOB-V2 cutover; bot not yet implementedDesigned against V2 schema (pUSD, builder codes, V2 EIP-712 domain)

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Slippage score computed correctlyfill_price=0.63, arrival_mid=0.62, warn_threshold_bps=20slippage_bps=161 > 20 → FILL_SLIPPAGE_ELEVATED, price_score low
Overall score passes when all dimensions goodslippage_bps=2, latency_ms=50, drift_bps=3overall_score > 0.9; FILL_QUALITY_PASS
Critical alert when score < 0.2slippage_bps=90, latency_ms=480, drift_bps=95overall_score < 0.2; FILL_QUALITY_CRITICAL emitted

Integration Tests

TestExpected result
End-to-end: FILLED event → score computed → annotated ExecutionReport publishedExecutionReport includes all score fields; published to polytraders.reports.execution
Toxicity measurement: wait toxicity_horizon_s, fetch tape, compute drift_bpsdrift_bps populated; toxicity_score reflects post-fill price move

Property Tests

PropertyRequired behaviour
Overall score always in [0, 1]Always true
FillQualityAnalyzer never modifies fill_price or order_id in the ExecutionReportAlways true — read-only annotation

27. Operational Runbook

FillQualityAnalyzer incidents are typically high critical fill rates (systematic adverse selection) or stale market tape causing toxicity measurement failures.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
FQACriticalFillRateCheck which markets have high critical rates in Grafana; cross-reference with AntiToxicFill cooldown history.Strategy pod lead for market selection review
FQAHighSlippageCheck arrival_mid availability and ws_market feed freshness.Exec pod lead if p95 slippage > 40 bps sustained > 10 min

Manual overrides

  • polytraders bot rescore exec.fillqualityanalyzer --order <order_id> — Rescore a fill after feed availability is restored to get accurate toxicity_score.

Healthcheck

GET /internal/health/fillqualityanalyzer → green if ws_market feed connected, clob_public reachable, critical_total rate < 0.05/min, pending toxicity evals < 100; red if ws_market disconnected, critical rate > 0.2/min, pending evals > 100

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
Score computation unit tests pass with known inputsCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Mean slippage_bps < 20 over 48h shadow runpolytraders_exec_fillqualityanalyzer_slippage_bps histogramp50 < 20 bps

Promote to General live

GateHow measuredThreshold
Critical fill rate < 0.5% over 7-day limited-live periodpolytraders_exec_fillqualityanalyzer_critical_total< 0.5%

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