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.13 TailLossSimulator

1.13 TailLossSimulator

Risk Guardrail VetoReshape PLANNED Planned capital · Direct P4 · Core risk pending stub

TailLossSimulator applies scripted adverse shock scenarios to the combined portfolio (open positions plus the proposed order) and rejects or downsizes the order if the simulated tail loss exceeds the configured maximum. It acts as a pre-trade stress test, replacing post-hoc margin calls with a forward-looking gate.

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

LayerRisk  Risk
Bot classGuardrail
AuthorityVetoReshape
StatusPLANNED
ReadinessPlanned
Runs beforeExecutionPlan emit
Runs afterStrategy OrderIntent
Applies toEvery OrderIntent — stress-tests the combined portfolio (existing + proposed) against scripted shock scenarios before approving size-up orders
Default modeplanned
User-visiblesummary-only
Developer ownerPolytraders core — Risk pod

Operational profile

Modes supportedquarantine

2. Purpose

TailLossSimulator applies scripted adverse shock scenarios to the combined portfolio (open positions plus the proposed order) and rejects or downsizes the order if the simulated tail loss exceeds the configured maximum. It acts as a pre-trade stress test, replacing post-hoc margin calls with a forward-looking gate.

3. Why This Bot Matters

  • Tail loss unquantified before sizing up

    Adding a new position without a stress test can push the portfolio into a tail regime where a single adverse resolution event causes losses far beyond the user's tolerance.

  • Correlated shock undetected

    A scripted scenario that resolves all markets in the same direction reveals hidden concentration risk that individual position limits would miss.

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
Current market prices for all open positionsclob_publicYesSeed the shock scenario with current mark-to-market prices.
Outcome probabilities for open marketsdata_apiYesApply shock probability shifts to compute stressed portfolio value.

5. Required Internal Inputs

InputSourceRequired?Use
Open position list and notional valuesinternalYesDefine the current portfolio state before adding the proposed order.
Shock scenario libraryinternalYesLoad scripted adverse scenarios (probability shifts per market category).
KillSwitch active flagKillSwitchYesIf active, reject immediately.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
max_tail_loss_usd500400500Maximum acceptable simulated tail loss (in pUSD) across all shock scenarios before the order is blocked or downsized.
shock_scenarios['all_yes_resolves', 'all_no_resolves', 'macro_adverse_shift']NoneNoneList of named shock scenarios from the scenario library to run against the proposed portfolio.
tail_percentile0.05NoneNoneThe probability tail used when computing expected tail loss from a scenario distribution. 0.05 = CVaR at 5th percentile.

7. Detailed Parameter Instructions

max_tail_loss_usd

What it means

Maximum acceptable simulated tail loss (in pUSD) across all shock scenarios before the order is blocked or downsized.

Default

{ "max_tail_loss_usd": 500 }

Why this default matters

500 pUSD is a conservative default that limits single-event drawdown to a manageable fraction of a typical portfolio.

Threshold logic

ConditionAction
max_scenario_loss <= 400APPROVE
400 < max_scenario_loss <= 500WARN — TAIL_LOSS_APPROACHING
max_scenario_loss > 500RESHAPE or HARD_REJECT — TAIL_LOSS_EXCEEDED

Developer check

if (maxScenarioLoss > params.max_tail_loss_usd) return reshape_or_reject('TAIL_LOSS_EXCEEDED');

User-facing English

Your order would expose your portfolio to a potential tail loss above the configured limit.

shock_scenarios

What it means

List of named shock scenarios from the scenario library to run against the proposed portfolio.

Default

{ "shock_scenarios": ["all_yes_resolves", "all_no_resolves", "macro_adverse_shift"] }

Why this default matters

The three default scenarios cover full YES resolution, full NO resolution, and a macro probability shift, which together bound the binary tail exposure.

Threshold logic

ConditionAction
all scenarios runUse worst-case loss across all scenarios

Developer check

const scenarioLosses = params.shock_scenarios.map(s => runScenario(portfolio, s));

User-facing English

— not yet authored —

tail_percentile

What it means

The probability tail used when computing expected tail loss from a scenario distribution. 0.05 = CVaR at 5th percentile.

Default

{ "tail_percentile": 0.05 }

Why this default matters

5th-percentile tail captures extreme but plausible adverse outcomes without being overly conservative for normal market conditions.

Threshold logic

ConditionAction
alwaysUse configured percentile in scenario loss aggregation

Developer check

const tailLoss = cvar(scenarioLossDistribution, params.tail_percentile);

User-facing English

— not yet authored —

8. Default Configuration

{
  "bot_id": "risk.tail_loss_simulator",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "max_tail_loss_usd": 500,
    "shock_scenarios": [
      "all_yes_resolves",
      "all_no_resolves",
      "macro_adverse_shift"
    ],
    "tail_percentile": 0.05
  },
  "locked": {
    "max_tail_loss_usd": {
      "min": 50
    }
  }
}

9. Implementation Flow

  1. Receive OrderIntent with market_id, side, size_usd.
  2. Check KillSwitch; if active, HARD_REJECT(KILL_SWITCH_ACTIVE).
  3. Load open positions and current prices to construct current portfolio state.
  4. Append proposed order to the portfolio (as if filled).
  5. Load shock scenario library and run each configured scenario against the proposed portfolio.
  6. For each scenario, compute the portfolio value change (loss if negative).
  7. Take the maximum loss across all scenarios as the tail loss estimate.
  8. If tail_loss > max_tail_loss_usd.hard: compute safe_size_usd that would keep tail_loss within limit.
  9. If safe_size_usd > 0: RESHAPE with constraints.max_size_usd = safe_size_usd. Else HARD_REJECT(TAIL_LOSS_EXCEEDED).
  10. If tail_loss > max_tail_loss_usd.warning: attach WARN annotation; APPROVE.
  11. All checks passed — APPROVE with worst_case_loss attached.

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 evaluateTailLoss(intent):
  ks = FETCH internal.killswitch.status
  IF ks.active: EMIT RiskVote(HARD_REJECT, KILL_SWITCH_ACTIVE); RETURN

  positions = FETCH internal.open_positions(intent.user_id)
  prices = FETCH clob_public.prices(ALL position.market_ids)
  scenarios = FETCH internal.shock_scenario_library()
  IF positions IS NULL OR prices IS NULL OR scenarios IS NULL:
    EMIT RiskVote(HARD_REJECT, TAIL_LOSS_DATA_UNAVAILABLE); RETURN

  proposedPortfolio = positions + [intent as position]
  scenarioLosses = []
  FOR scenario IN params.shock_scenarios:
    shockedPrices = applyShock(prices, scenarios[scenario])
    pnl = computePortfolioPnL(proposedPortfolio, shockedPrices)
    scenarioLosses.append(-pnl if pnl < 0 else 0)

  tailLoss = max(scenarioLosses)

  IF tailLoss > params.max_tail_loss_usd:
    safeSize = binarySearchSafeSize(positions, intent, scenarios, prices, params)
    IF safeSize > 0:
      EMIT RiskVote(RESHAPE_REQUIRED, TAIL_LOSS_EXCEEDED,
                    constraints={max_size_usd: safeSize}); RETURN
    EMIT RiskVote(HARD_REJECT, TAIL_LOSS_EXCEEDED); RETURN

  IF tailLoss > params.max_tail_loss_usd * 0.8:
    annotations.append(WARN(TAIL_LOSS_APPROACHING, tail_loss=tailLoss))

  EMIT RiskVote(APPROVE, worst_case_loss=tailLoss)

SDK calls used

  • clob_public.prices(market_ids)
  • internal.open_positions(user_id)
  • internal.shock_scenario_library()
  • internal.killswitch.status()

Complexity: O(S * N) where S = scenarios (default 3), N = open positions

11. Wire Examples

Input — what arrives on the wire

OrderIntent — tail loss would be exceededinternal

{
  "intent_id": "int_e5f6a7b8c9d00005",
  "market_id": "0x3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d",
  "size_usd": 500,
  "side": "BUY",
  "generated_at_ms": 1746800000000
}

Output — what the bot emits

RiskVote — RESHAPE_REQUIRED

{
  "guard_id": "risk.tail_loss_simulator",
  "decision": "RESHAPE_REQUIRED",
  "severity": "WARN",
  "reason_code": "TAIL_LOSS_EXCEEDED",
  "message": "Tail loss 620 pUSD under 'all_yes_resolves' exceeds 500. Resized to 320.",
  "constraints": {
    "max_size_usd": 320
  },
  "checked_at": "2026-05-10T12:00:00Z"
}

12. Decision Logic

APPROVE

Maximum simulated tail loss across all shock scenarios is at or below the warning threshold.

RESHAPE_REQUIRED

Tail loss exceeds the hard ceiling but can be reduced by downsizing the order.

REJECT

Tail loss exceeds the hard ceiling even at minimum order size, or data is unavailable.

WARNING_ONLY

— not yet authored —

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "guard_id": "risk.tail_loss_simulator",
  "decision": "RESHAPE_REQUIRED",
  "severity": "WARN",
  "reason_code": "TAIL_LOSS_EXCEEDED",
  "message": "Proposed order produces tail loss 620 pUSD under scenario 'all_yes_resolves'. Resized to keep tail loss <= 500 pUSD.",
  "constraints": {
    "max_size_usd": 320
  },
  "inputs_used": [
    "internal.positions",
    "internal.shock_scenarios",
    "clob_public.prices"
  ],
  "checked_at": "2026-05-10T12:00:00Z"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch active.Immediate HARD_REJECT.Trading is paused. Please try again later.
TAIL_LOSS_EXCEEDEDRESHAPESimulated tail loss exceeds max_tail_loss_usd; order resized if possible.RESHAPE if safe_size > 0; else HARD_REJECT.Your order was adjusted due to portfolio tail risk limits.
TAIL_LOSS_APPROACHINGWARNTail loss is between warning and hard threshold.Attach WARN annotation; APPROVE.
TAIL_LOSS_DATA_UNAVAILABLEHARD_REJECTPosition data or scenario library unavailable.HARD_REJECT (fail-closed).We could not complete the portfolio stress test. Please try again.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_risk_taillosssimulator_decisions_totalcountercountdecision, reason_codeTotal decisions by type.
polytraders_risk_taillosssimulator_worst_case_loss_usdgaugeusdWorst-case tail loss across scenarios at last evaluation.
polytraders_risk_taillosssimulator_eval_latency_mshistogrammillisecondsLatency from intent to RiskVote emit.

Alerts

AlertConditionSeverityRunbook
TailLossSimulatorHighTailLosspolytraders_risk_taillosssimulator_worst_case_loss_usd > 400P2#runbook-tailloss-high
TailLossSimulatorDataUnavailablerate(polytraders_risk_taillosssimulator_decisions_total{reason_code='TAIL_LOSS_DATA_UNAVAILABLE'}[5m]) > 0P1#runbook-tailloss-data

16. Developer Reporting

{
  "bot_id": "risk.tail_loss_simulator",
  "decision": "RESHAPE_REQUIRED",
  "reason_code": "TAIL_LOSS_EXCEEDED",
  "inputs_used": [
    "internal.positions",
    "internal.shock_scenarios",
    "clob_public.prices"
  ],
  "metrics": {
    "worst_scenario": "all_yes_resolves",
    "tail_loss_usd": 620,
    "safe_size_usd": 320,
    "max_tail_loss_usd": 500
  },
  "checked_at": "2026-05-10T12:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Order downsized — tail loss limitYour order was reduced because the full size would expose your portfolio to a potential loss above the configured stress-test limit under adverse scenarios.
Order blocked — tail loss cannot be reducedEven at a minimal size, this order would push your portfolio tail risk above the allowed limit. Please close some existing positions first.

18. Failure-Mode Block

main_failure_modeApproving an order because the shock scenarios are insufficiently adverse, underestimating the true tail risk.
false_positive_riskRejecting a legitimate order because an extreme scenario is too pessimistic relative to actual market conditions.
false_negative_riskApproving an order if the scenario library has not been updated to reflect a new market structure or correlated risk factor.
safe_fallbackIf position data or scenario library is unavailable, HARD_REJECT with TAIL_LOSS_DATA_UNAVAILABLE. Never approve on missing data.
required_dependenciesOpen position ledger, Shock scenario library, CLOB current prices, KillSwitch active flag

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
SCENARIO_LIBRARY_UNAVAILABLEDelete scenario library from RedisReturns to normal after scenario library is restored.
EXTREME_TAIL_LOSSSet open positions to all-in YES with large notional, submit a BUYReturns to APPROVE after existing positions are reduced.
PRICE_DATA_UNAVAILABLEReturn 503 from CLOB prices endpointReturns to normal within one portfolio snapshot refresh.

20. State & Persistence

Cold-start recovery

On cold start, portfolio snapshot fetched from CLOB before first evaluation. Scenario library loaded from Redis.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight50
Idempotency keyintent_id
Per-call timeout (ms)300
Backpressure strategydrop newest
Locking / mutual exclusionper-user_id mutex during scenario computation

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchGlobal brake checked first.HARD_REJECT(KILL_SWITCH_ACTIVE) short-circuits all scenario evaluation.

Emits to (downstream consumers)

BotWhyContract
exec.smart_routerAPPROVE or RESHAPE passes to SmartRouter.RESHAPE constraints respected by SmartRouter.

External services

ServiceEndpointSLA assumedOn failure
CLOB API (current prices)https://clob.polymarket.com99.95% / 200ms p99HARD_REJECT(TAIL_LOSS_DATA_UNAVAILABLE) if prices unavailable.

23. Security Surfaces

Abuse vectors considered

  • Submitting a low-notional order to establish a position then sizing up in a second order after the first passes the stress test
  • Manipulating the position cache to show a smaller portfolio, reducing apparent tail risk

Mitigations

  • Portfolio snapshot is refreshed every 10s from CLOB, limiting the staleness window
  • Idempotency deduplication prevents rapid sequential submissions from bypassing the per-intent evaluation

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsyes
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesAll loss values denominated in pUSD. NegRisk market outcomes are included in shock scenarios using the NegRiskAdapter resolution model.

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
Approve when all scenario losses within warning thresholdmax_scenario_loss=380, ceiling=500APPROVE
Reshape when tail loss exceeds ceiling but safe size existstail_loss=620, safe_size=320RESHAPE_REQUIRED(max_size_usd=320)
Reject when tail loss exceeds ceiling even at min sizetail_loss=800 even at min_size=10HARD_REJECT(TAIL_LOSS_EXCEEDED)
Warn when tail loss between warning and hardtail_loss=450, warning=400, hard=500APPROVE with WARN annotation

Integration Tests

TestExpected result
Shock scenario reshapes order flowing through to SmartRouterSmartRouter receives constraints.max_size_usd from RESHAPE; executes at reduced size
KillSwitch bypasses scenario computationHARD_REJECT(KILL_SWITCH_ACTIVE) without running any scenarios

Property Tests

PropertyRequired behaviour
Approved order never causes tail loss above max_tail_loss_usdAlways true after reshape
Reshape size is strictly <= original intent size_usdAlways true

27. Operational Runbook

TailLossSimulator incidents are typically caused by a concentrated portfolio approaching the tail loss ceiling, or by stale scenario data. Verify the scenario library is current before adjusting parameters.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
TailLossSimulatorHighTailLossInspect worst_case_loss_usd gauge; identify which scenario is driving the worst case. Check if portfolio concentration has increased.Risk pod lead if tail loss is genuinely at the ceiling.
TailLossSimulatorDataUnavailableCheck CLOB prices endpoint and scenario library Redis key.Infra on-call if CLOB or Redis unavailable > 2 minutes.

Manual overrides

  • polytraders risk update-scenarios --scenario-file <path> — After a market structure change that requires new or revised shock scenarios.

Healthcheck

GET /internal/health/taillosssimulator → green: Scenario library loaded, CLOB prices reachable, portfolio snapshot age < 30s; red: Scenario library missing, CLOB unreachable, or snapshot age > 60s

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
Unit tests pass for reshape and reject scenariosCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Shadow reshape rate aligns with expected over 48hGrafana shadow vs liveNo spurious HARD_REJECTs in shadow run

Promote to General live

GateHow measuredThreshold
Zero DATA_UNAVAILABLE rejections during normal hours over 7 daysTailLossSimulatorDataUnavailable alert history0 firings

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