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 LayerGovernance6.12 PostTradeExplainer

6.12 PostTradeExplainer

Governance Governance Service Explain PLANNED Spec started capital · Indirect P7 · Governance & replay pending flagship stub

PostTradeExplainer turns every fill into a plain-English SettlementReport: which strategy, which signal, which guard checks passed, why this size, why this price. Retained 7 years for regulatory compliance.

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

LayerGovernance  Governance
Bot classGovernance Service
AuthorityExplain
StatusPLANNED
ReadinessSpec started
Runs beforeNothing — runs post-fill on every ExecutionReport event
Runs afterOrder fill confirmation from CTFExchangeV2
Applies toEvery filled or partially filled order
Default modeshadow_only
User-visiblesummary-only
Developer ownerPolytraders core

2. Purpose

PostTradeExplainer turns every fill into a plain-English SettlementReport: which strategy, which signal, which guard checks passed, why this size, why this price. Retained 7 years for regulatory compliance.

3. Why This Bot Matters

  • No post-trade explanation

    Users and regulators cannot understand why a trade was made; compliance audit fails.

  • Explanation not linked to fill

    Fill records and strategy intent cannot be reconciled; audit trail is broken.

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
Fill confirmation from CTFExchangeV2clob_authYesConsume fill metadata (size, price, side, builder_fee_pusd) for the explanation.
ExecutionReport envelopeinternal.report_busYesSource of the full order lifecycle context for the explanation.

5. Required Internal Inputs

InputSourceRequired?Use
DecisionReport for the originating intentinternal.report_archiveYesFetch strategy name, signal inputs, and parameter values at decision time.
RiskVote record for the originating intentinternal.report_archiveYesInclude guardrail votes in the explanation.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
retain_explanation_days255518252555Retention period in days for SettlementReport records (default 7 years = 2555 days).
min_detail_levelstandardNoneNoneMinimum level of detail in the explanation (standard / advanced).

7. Detailed Parameter Instructions

retain_explanation_days

What it means

Retention period in days for SettlementReport records (default 7 years = 2555 days).

Default

{ "retain_explanation_days": 2555 }

Why this default matters

7-year retention is required by financial regulations.

Threshold logic

ConditionAction
retain_explanation_days < 2555WARN — below regulatory minimum

Developer check

if p.retain_explanation_days < 2555: emit('RETENTION_BELOW_REGULATORY_MINIMUM')

User-facing English

Your trade explanations are kept on record for 7 years as required by regulation.

min_detail_level

What it means

Minimum level of detail in the explanation (standard / advanced).

Default

{ "min_detail_level": "standard" }

Why this default matters

Standard includes strategy name, signal summary, and guard votes. Advanced adds raw parameter values.

Threshold logic

ConditionAction
min_detail_level=standardInclude: strategy, signal summary, guard votes, size rationale, fill price

Developer check

if p.min_detail_level == 'advanced': explanation.include(raw_params=True)

User-facing English

Each trade record includes the reason it was made.

8. Default Configuration

{
  "bot_id": "gov.posttradeexplainer",
  "version": "0.1.0",
  "mode": "shadow_only",
  "defaults": {
    "retain_explanation_days": 2555,
    "min_detail_level": "standard",
    "publish_to_user": true,
    "require_for_advanced": false
  }
}

9. Implementation Flow

  1. Subscribe to ExecutionReport events on the internal report bus.
  2. For each ExecutionReport, fetch the linked DecisionReport and RiskVote from the report archive.
  3. Compose the explanation: strategy name, signal summary, guard vote outcomes, size rationale, fill price, builder_fee_pusd.
  4. Generate a plain-English summary sentence for user display.
  5. Emit SettlementReport(event_type=TRADE_EXPLAINED) with full explanation and plain-English summary.
  6. Store SettlementReport in the post-trade archive with retain_explanation_days retention.

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.

// ---- SUBSCRIBE TO EXECUTION REPORTS ----
FUNCTION onExecutionReport(execReport):
  // Fetch supporting records from archive
  decisionReport = FETCH internal.reportArchive.GET({
    trace_id: execReport.trace_id, kind: 'DecisionReport'
  })
  riskVotes = FETCH internal.reportArchive.GET({
    trace_id: execReport.trace_id, kind: 'RiskVote'
  })

  complete = decisionReport IS NOT NULL AND riskVotes IS NOT NULL

  // Compose explanation
  explanation = {
    fill_id:         execReport.fill_id,
    strategy:        decisionReport?.strategy_slug ?? 'unknown',
    signal_summary:  decisionReport?.signal_summary ?? '',
    guard_votes:     riskVotes ?? [],
    size_rationale:  buildSizeRationale(decisionReport, riskVotes),
    fill_price:      execReport.fill_price,
    fill_size_pusd:  execReport.fill_size_pusd,
    builder_fee_pusd: execReport.builder_fee_pusd,
    plain_english:   composePlainEnglish(explanation),
    explanation_complete: complete
  }

  // Emit SettlementReport
  EMIT SettlementReport(event_type='TRADE_EXPLAINED',
    ...explanation,
    report_kind='SettlementReport',
    topic='polytraders.reports.settlement',
    retained_until=now() + days(config.retain_explanation_days))

FUNCTION composePlainEnglish(exp):
  side = 'Bought' IF exp.fill_side == 'BUY' ELSE 'Sold'
  return f"{side} {exp.fill_size_pusd} pUSD at {exp.fill_price} — {exp.signal_summary}; all risk checks passed."

SDK calls used

  • internal.reportArchive.GET({trace_id, kind})
  • alerting.emit('EXPLANATION_ARCHIVE_UNAVAILABLE', metadata)

Complexity: O(1) per fill; O(R) for archive fetch where R = linked report count

11. Wire Examples

Input — what arrives on the wire

{
  "label": "ExecutionReport from fill",
  "source": "internal.report_bus",
  "payload": {
    "fill_id": "fill_00a1b2c3d4e5f6a7",
    "trace_id": "trc_01HX9Z",
    "fill_price": 0.621,
    "fill_size_pusd": 430.0,
    "builder_fee_pusd": 1.075,
    "fill_confirmed_at_ms": 1746792060000
  }
}

Output — what the bot emits

{
  "label": "SettlementReport — TRADE_EXPLAINED",
  "payload": {
    "report_id": "stl_01HX9Z",
    "event_type": "TRADE_EXPLAINED",
    "fill_id": "fill_00a1b2c3d4e5f6a7",
    "plain_english": "Bought 430 pUSD of YES at 0.621 — sports model edge 4.2 bps; all risk checks passed.",
    "report_kind": "SettlementReport",
    "topic": "polytraders.reports.settlement",
    "retained_until": "2033-05-09"
  }
}

12. Decision Logic

APPROVE

Not applicable — PostTradeExplainer does not approve or reject trades.

RESHAPE_REQUIRED

Not applicable.

REJECT

Not applicable.

WARNING_ONLY

Emits RETENTION_BELOW_REGULATORY_MINIMUM if retain_explanation_days < 2555.

13. Standard Decision Output

This bot returns a SettlementReport object. See SettlementReport schema.

{
  "report_id": "stl_posttradeexplainer_01HX9Z",
  "bot_id": "gov.posttradeexplainer",
  "event_type": "TRADE_EXPLAINED",
  "fill_id": "fill_00a1b2c3d4e5f6a7",
  "strategy": "sports-model",
  "signal_summary": "Model edge 4.2 bps on YES leg of market 0x9b0c",
  "guard_votes": [
    {
      "bot": "liquidityguard",
      "vote": "pass"
    },
    {
      "bot": "portfolioguard",
      "vote": "pass"
    }
  ],
  "size_rationale": "Position sized at 80% of max_size_pusd due to LiquidityGuard spread signal",
  "fill_price": 0.621,
  "fill_size_pusd": 430.0,
  "builder_fee_pusd": 1.075,
  "plain_english": "Bought 430 pUSD of YES on market 0x9b0c at 0.621 \u2014 sports model detected 4.2 bps edge; all risk checks passed.",
  "report_kind": "SettlementReport",
  "topic": "polytraders.reports.settlement",
  "retained_until": "2033-05-09"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
TRADE_EXPLAINEDINFOA fill was successfully explained and a SettlementReport emitted.Log and store.Your trade record is available.
EXPLANATION_ARCHIVE_UNAVAILABLEWARNDecisionReport or RiskVote could not be fetched; partial explanation emitted.Emit partial SettlementReport with explanation_complete=false.Detailed trade explanation is temporarily unavailable.
RETENTION_BELOW_REGULATORY_MINIMUMWARNretain_explanation_days is below 2555 (7 years).Emit WARN; do not reduce retention.
EXPLANATION_MISSING_GUARD_VOTESWARNRiskVote records not found for the fill; explanation is incomplete.Emit partial explanation; flag explanation_complete=false.
KILL_SWITCH_ACTIVEWARNKillSwitch was active at fill time; noted in explanation.Include kill-switch note in explanation.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_gov_posttradeexplainer_explanations_totalcountercountcompleteTotal explanations emitted, labelled by completeness.
polytraders_gov_posttradeexplainer_archive_fetch_latency_mshistogrammsLatency of archive fetch per explanation.
polytraders_gov_posttradeexplainer_partial_explanations_totalcountercountTotal partial explanations (archive unavailable).
polytraders_gov_posttradeexplainer_retention_daysgaugedaysConfigured retention in days; should always be >= 2555.

Alerts

AlertConditionSeverityRunbook
PostTradeExplainerArchiveUnavailablerate(polytraders_gov_posttradeexplainer_partial_explanations_total[10m]) > 0P2#runbook-posttradeexplainer-archive
PostTradeExplainerRetentionBreachpolytraders_gov_posttradeexplainer_retention_days < 2555P1#runbook-posttradeexplainer-retention

16. Developer Reporting

{
  "bot_id": "gov.posttradeexplainer",
  "event_type": "EXPLANATION_COMPOSED",
  "fill_id": "fill_00a1b2c3d4e5f6a7",
  "archive_fetch_ms": 12,
  "explanation_length": 280
}

17. Plain-English Reporting

SituationUser-facing explanation
Trade explanation availableYour trade was made because the strategy detected a pricing edge. All risk checks passed. Full details are in the trade record.
Explanation archive unavailableThe detailed trade explanation is temporarily unavailable. The trade itself was executed correctly.

18. Failure-Mode Block

main_failure_modeReport archive is unavailable; DecisionReport or RiskVote cannot be fetched, resulting in an incomplete explanation.
false_positive_riskA stale DecisionReport is linked to the wrong fill, producing a misleading explanation.
false_negative_riskMissing guard vote record causes the explanation to omit a guardrail outcome.
safe_fallbackIf archive is unavailable, emit a partial explanation with fill metadata only and set explanation_complete=false.
required_dependenciesinternal.report_archive (DecisionReport, RiskVote), internal.report_bus (ExecutionReport), clob_auth (fill confirmation)

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
ARCHIVE_UNAVAILABLEBlock reads from internal.reportArchive during fill processingAutomatic retry when archive recovers.
MISSING_RISK_VOTESDelete RiskVote records for a specific trace_idNo recovery needed — partial explanation is acceptable.
RETENTION_CONFIG_BREACHSet retain_explanation_days=30 in configRestore retain_explanation_days to >= 2555.

20. State & Persistence

Cold-start recovery

On restart, resume consuming ExecutionReport events from the last processed offset.

21. Concurrency & Idempotency

AspectSpecification
Execution modelevent-driven; one goroutine per ExecutionReport
Max in-flight100
Idempotency keyfill_id
Per-call timeout (ms)2000
Backpressure strategyqueue
Locking / mutual exclusionnone (append-only writes)

22. Dependencies

Depends on (must run first)

BotWhyContract
internal.report_archiveDecisionReport and RiskVote fetched for each fill.Records available by trace_id within 500ms of fill.

Emits to (downstream consumers)

BotWhyContract
internal.post_trade_archive

Sibling bots (same OrderIntent)

BotWhyContract
gov.attributionrevenuereporterAttributionRevenueReporter references SettlementReport records for fee reconciliation.SettlementReport includes builder_fee_pusd.

External services

ServiceEndpointSLA assumedOn failure
Internal report archivehttps://archive.internal99.9%Emit partial explanation with explanation_complete=false.

23. Security Surfaces

Abuse vectors considered

  • Requesting explanations for fills not belonging to the requesting user

Mitigations

  • SettlementReport records are scoped to wallet_address; read access requires authenticated user context

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldyes
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesPostTradeExplainer consumes ExecutionReport records from V2 fills; all amounts in pUSD. Reads builder_fee_pusd from fill metadata.

API surfaces declared

internalclob_auth

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
Explanation composed with all required fieldsFull DecisionReport and RiskVote availableSettlementReport with non-null plain_english and all guard_votes
Partial explanation emitted when archive unavailablereport_archive returns 503SettlementReport with explanation_complete=false; fill metadata present

Integration Tests

TestExpected result
End-to-end: fill confirmed → ExecutionReport → SettlementReport with explanation emittedSettlementReport on polytraders.reports.settlement; retained_until = now+7y

Property Tests

PropertyRequired behaviour
Every SettlementReport includes a non-empty plain_english fieldAlways true — even partial explanations include a fallback message

27. Operational Runbook

PostTradeExplainer incidents are usually archive unavailability producing partial explanations. These are low-urgency unless retention is also breached.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
PostTradeExplainerArchiveUnavailable
PostTradeExplainerRetentionBreach

Manual overrides

Healthcheck

/internal/health/posttradeexplainer → green if Consuming ExecutionReport events; retention_days >= 2555; partial_explanations rate == 0; red if retention_days < 2555 or archive unreachable

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
Explanation composition unit tests pass; all required fields presentCIPass

Promote to Limited live

GateHow measuredThreshold
End-to-end: fill → ExecutionReport → SettlementReport verified in stagingIntegration testPass

Promote to General live

GateHow measuredThreshold
7-year retention enforced in Postgres schema migration; compliance review passedCompliance team sign-offPass

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