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.11 CorrelationShockGuard

1.11 CorrelationShockGuard

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

CorrelationShockGuard monitors the rolling pairwise return correlation of open positions and blocks new orders when the portfolio correlation exceeds the configured ceiling. It detects sudden hidden correlation shocks where previously independent markets begin resolving together, increasing tail risk beyond what the capital model assumes.

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 — detects when the portfolio's supposedly independent positions are moving in lockstep, indicating a hidden correlation shock
Default modeplanned
User-visiblesummary-only
Developer ownerPolytraders core — Risk pod

Operational profile

Modes supportedquarantine

2. Purpose

CorrelationShockGuard monitors the rolling pairwise return correlation of open positions and blocks new orders when the portfolio correlation exceeds the configured ceiling. It detects sudden hidden correlation shocks where previously independent markets begin resolving together, increasing tail risk beyond what the capital model assumes.

3. Why This Bot Matters

  • Hidden correlation shock

    Markets that appeared independent begin resolving together (e.g. macro shock, shared underlying fact), transforming the portfolio's diversification into concentrated tail risk with no warning.

  • New order added during correlation spike

    Adding a new position while portfolio correlation is elevated amplifies the shock exposure instead of providing diversification.

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
Market price series for all open positionsdata_apiYesCompute rolling pairwise correlations across positions over a configurable lookback window.
Market category and topic tagsgammaNoGroup markets by topic to detect within-group correlation that may indicate shared resolution events.

5. Required Internal Inputs

InputSourceRequired?Use
Current open position list and notional valuesinternalYesIdentify which markets to include in the correlation calculation.
KillSwitch active flagKillSwitchYesIf active, reject immediately.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
max_portfolio_correlation0.60.450.6Maximum allowed average pairwise return correlation across open positions before new orders are blocked.
lookback_periods20NoneNoneNumber of price-update periods over which pairwise correlations are computed. Shorter windows detect shocks faster; longer windows reduce false positives.
min_positions_to_check3NoneNoneMinimum number of open positions required before the correlation check is applied. Below this threshold, the check is skipped (not enough data for meaningful correlation).

7. Detailed Parameter Instructions

max_portfolio_correlation

What it means

Maximum allowed average pairwise return correlation across open positions before new orders are blocked.

Default

{ "max_portfolio_correlation": 0.6 }

Why this default matters

A portfolio-average correlation above 0.6 indicates positions are behaving as a single concentrated bet rather than a diversified set.

Threshold logic

ConditionAction
avg_corr <= 0.45APPROVE
0.45 < avg_corr <= 0.6WARN — CORRELATION_SHOCK_APPROACHING
avg_corr > 0.6HARD_REJECT — CORRELATION_SHOCK_DETECTED

Developer check

if (avgCorr > params.max_portfolio_correlation) return reject('CORRELATION_SHOCK_DETECTED');

User-facing English

Your portfolio positions are currently highly correlated. New orders are blocked until correlation normalises.

lookback_periods

What it means

Number of price-update periods over which pairwise correlations are computed. Shorter windows detect shocks faster; longer windows reduce false positives.

Default

{ "lookback_periods": 20 }

Why this default matters

20 periods balances responsiveness (detecting a new shock within minutes) with stability (not rejecting on single-period noise).

Threshold logic

ConditionAction
alwaysUse configured lookback; no reject threshold

Developer check

corrMatrix = computeRollingCorr(priceSeries, params.lookback_periods);

User-facing English

— not yet authored —

min_positions_to_check

What it means

Minimum number of open positions required before the correlation check is applied. Below this threshold, the check is skipped (not enough data for meaningful correlation).

Default

{ "min_positions_to_check": 3 }

Why this default matters

With fewer than 3 positions, pairwise correlation is trivial and may produce misleading values.

Threshold logic

ConditionAction
open_positions < min_positions_to_checkSKIP check — APPROVE
open_positions >= min_positions_to_checkRun correlation check

Developer check

if (openPositions.length < params.min_positions_to_check) return approve('CORRELATION_SHOCK_SKIPPED');

User-facing English

— not yet authored —

8. Default Configuration

{
  "bot_id": "risk.correlation_shock_guard",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "max_portfolio_correlation": 0.6,
    "lookback_periods": 20,
    "min_positions_to_check": 3
  },
  "locked": {
    "max_portfolio_correlation": {
      "max": 0.8
    }
  }
}

9. Implementation Flow

  1. Receive OrderIntent with market_id and strategy context.
  2. Check KillSwitch; if active, HARD_REJECT(KILL_SWITCH_ACTIVE).
  3. Load open position list and price series for each from data_api.
  4. If open positions < min_positions_to_check, APPROVE immediately (skip).
  5. Compute rolling pairwise return correlations over lookback_periods.
  6. Compute average pairwise correlation across the matrix.
  7. If avg_corr > max_portfolio_correlation.hard, HARD_REJECT(CORRELATION_SHOCK_DETECTED).
  8. If avg_corr > max_portfolio_correlation.warning, attach WARN annotation; APPROVE.
  9. All checks passed — APPROVE with correlation_score 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 evaluateCorrelation(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)
  IF len(positions) < params.min_positions_to_check:
    EMIT RiskVote(APPROVE); RETURN

  priceSeries = {}
  FOR pos IN positions:
    series = FETCH data_api.price_series(pos.market_id, params.lookback_periods)
    IF series IS NULL:
      EMIT RiskVote(HARD_REJECT, CORRELATION_SHOCK_DATA_UNAVAILABLE); RETURN
    priceSeries[pos.market_id] = series

  returns = {m: diff(priceSeries[m]) FOR m IN priceSeries}
  corrMatrix = pairwise_corr(returns)
  avgCorr = mean(upper_triangle(corrMatrix))

  IF avgCorr > params.max_portfolio_correlation:
    EMIT RiskVote(HARD_REJECT, CORRELATION_SHOCK_DETECTED,
                  avg_corr=avgCorr); RETURN
  IF avgCorr > params.max_portfolio_correlation * 0.75:
    annotations.append(WARN(CORRELATION_SHOCK_APPROACHING))

  EMIT RiskVote(APPROVE, correlation_score=avgCorr)

SDK calls used

  • data_api.price_series(market_id, lookback)
  • internal.open_positions(user_id)
  • internal.killswitch.status()

Complexity: O(N^2) where N = number of open positions (max ~20)

11. Wire Examples

Input — what arrives on the wire

OrderIntent — correlation shock activeinternal

{
  "intent_id": "int_c3d4e5f6a7b80003",
  "market_id": "0x2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c",
  "size_usd": 300,
  "generated_at_ms": 1746800000000
}

Output — what the bot emits

RiskVote — HARD_REJECT

{
  "guard_id": "risk.correlation_shock_guard",
  "decision": "HARD_REJECT",
  "severity": "HARD",
  "reason_code": "CORRELATION_SHOCK_DETECTED",
  "message": "Avg pairwise correlation 0.72 exceeds ceiling 0.60.",
  "constraints": {},
  "checked_at": "2026-05-10T10:00:00Z"
}

12. Decision Logic

APPROVE

Portfolio average correlation is at or below the warning threshold, or fewer than min_positions_to_check are open.

RESHAPE_REQUIRED

Not used; correlation is a portfolio-level metric that cannot be partially accommodated by resizing a single order.

REJECT

Average pairwise correlation exceeds the hard ceiling, indicating a correlation shock in the open portfolio.

WARNING_ONLY

— not yet authored —

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "guard_id": "risk.correlation_shock_guard",
  "decision": "HARD_REJECT",
  "severity": "HARD",
  "reason_code": "CORRELATION_SHOCK_DETECTED",
  "message": "Portfolio avg pairwise correlation 0.72 exceeds hard ceiling 0.60. New orders blocked.",
  "constraints": {},
  "inputs_used": [
    "data_api.price_series",
    "internal.positions",
    "internal.killswitch.status"
  ],
  "checked_at": "2026-05-10T10: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.
CORRELATION_SHOCK_DETECTEDHARD_REJECTPortfolio average pairwise correlation exceeds the hard ceiling.HARD_REJECT; log avg_corr, num_positions, and lookback_periods.Your portfolio positions are highly correlated. New orders are blocked.
CORRELATION_SHOCK_APPROACHINGWARNPortfolio correlation is between the warning and hard thresholds.Attach WARN annotation to APPROVE; do not block.
CORRELATION_SHOCK_DATA_UNAVAILABLEHARD_REJECTPrice series data unavailable for one or more positions.HARD_REJECT (fail-closed).We could not retrieve market data needed to check portfolio correlation. Please try again.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_risk_correlationshockguard_decisions_totalcountercountdecision, reason_codeTotal RiskVote decisions by type.
polytraders_risk_correlationshockguard_avg_correlationgaugeratioCurrent portfolio average pairwise correlation (0–1) at last evaluation.
polytraders_risk_correlationshockguard_eval_latency_mshistogrammillisecondsLatency from intent receipt to RiskVote emit.

Alerts

AlertConditionSeverityRunbook
CorrelationShockGuardTriggeredrate(polytraders_risk_correlationshockguard_decisions_total{reason_code='CORRELATION_SHOCK_DETECTED'}[5m]) > 0P2#runbook-correlationshock-triggered
CorrelationShockGuardDataUnavailablerate(polytraders_risk_correlationshockguard_decisions_total{reason_code='CORRELATION_SHOCK_DATA_UNAVAILABLE'}[5m]) > 0P1#runbook-correlationshock-data

16. Developer Reporting

{
  "bot_id": "risk.correlation_shock_guard",
  "decision": "HARD_REJECT",
  "reason_code": "CORRELATION_SHOCK_DETECTED",
  "inputs_used": [
    "data_api.price_series",
    "internal.positions"
  ],
  "metrics": {
    "avg_pairwise_corr": 0.72,
    "num_positions": 5,
    "lookback_periods": 20,
    "hard_ceiling": 0.6
  },
  "checked_at": "2026-05-10T10:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Order blocked — correlation shockYour open positions are currently moving together more than expected, indicating a correlated market event. New orders are blocked until correlation normalises.
Warning — correlation approaching ceilingYour portfolio correlation is elevated. Consider reviewing your open positions before adding new ones.

18. Failure-Mode Block

main_failure_modeMissing price data for one or more positions causes the correlation matrix to be computed on a subset, potentially underestimating the true correlation.
false_positive_riskA transient data feed spike for one market artificially inflates its correlation with all others, triggering a spurious block.
false_negative_riskCorrelation develops slowly over many periods and avg_corr approaches but never crosses the hard ceiling while the portfolio remains concentrated.
safe_fallbackIf price series data is unavailable for any open position, HARD_REJECT with CORRELATION_SHOCK_DATA_UNAVAILABLE. Never approve on incomplete correlation data.
required_dependenciesData API price series, Open position ledger, KillSwitch active flag

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
PRICE_DATA_UNAVAILABLEBlock data_api endpoint for one market in the portfolioReturns to normal within one evaluation cycle after data_api is reachable.
CORRELATION_SPIKEInject price series with near-identical returns for all positions (corr=0.9)Returns to APPROVE once price series diverge below the ceiling.
INSUFFICIENT_POSITIONSSet open positions to 2 when min_positions_to_check=3Check activates once a 3rd position is opened.

20. State & Persistence

Cold-start recovery

On cold start, price series is populated from data_api before first evaluation. Insufficient data causes check to be skipped.

21. Concurrency & Idempotency

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

22. Dependencies

Depends on (must run first)

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

Emits to (downstream consumers)

BotWhyContract
exec.smart_routerApproved RiskVote passes to SmartRouter.APPROVE passes; HARD_REJECT discards intent.

External services

ServiceEndpointSLA assumedOn failure
Data API (price series)https://data-api.polymarket.com99.9% / 500ms p99HARD_REJECT(CORRELATION_SHOCK_DATA_UNAVAILABLE) if data unavailable.

23. Security Surfaces

Abuse vectors considered

  • Injecting manipulated price data to artificially lower apparent correlation
  • Submitting orders rapidly before correlation spike is detected

Mitigations

  • Price series sourced exclusively from data_api with provenance timestamp validation
  • Idempotency deduplication prevents rapid concurrent submissions from the same user

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
NotesPosition prices are denominated in pUSD. Uses data_api V2 price series; no order signing.

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 correlation below warning thresholdavg_corr=0.30, positions=4APPROVE
Warn when correlation between warning and hardavg_corr=0.50, positions=4APPROVE with WARN annotation
Reject when correlation above hard ceilingavg_corr=0.72, positions=4HARD_REJECT(CORRELATION_SHOCK_DETECTED)
Skip when fewer than min_positions_to_checkopen_positions=2, min=3APPROVE (check skipped)

Integration Tests

TestExpected result
Correlation spike detected from live price dataHARD_REJECT(CORRELATION_SHOCK_DETECTED) within one evaluation cycle of avg_corr exceeding ceiling
KillSwitch bypasses all correlation checksHARD_REJECT(KILL_SWITCH_ACTIVE) without reading price series

Property Tests

PropertyRequired behaviour
Avg correlation above hard ceiling never results in APPROVEAlways true
Fewer than min_positions always results in APPROVE (skip)Always true

27. Operational Runbook

CorrelationShockGuard incidents are typically caused by a macro market event driving correlated resolution across multiple markets. Verify the correlation is genuine before pausing the guard.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
CorrelationShockGuardTriggeredInspect the avg_corr metric and identify which markets are correlated. Check if a macro event is driving the correlation.Risk pod lead; consider pausing affected strategies if shock is genuine.
CorrelationShockGuardDataUnavailableCheck data_api connectivity; confirm price series endpoint is responding.Infra on-call if data_api is down > 5 minutes.

Manual overrides

  • polytraders risk disable-check risk.correlation_shock_guard --duration 300s — During a known data feed issue causing false data unavailability rejections; requires risk pod lead approval.

Healthcheck

GET /internal/health/correlationshockguard → green: data_api reachable, price series populated for all tracked positions, avg_corr below warning threshold; red: data_api unreachable, price series stale > lookback_periods, or avg_corr above hard ceiling

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 correlation spike and skip scenariosCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Shadow reject rate from genuine correlation events within 5% of expected over 48hGrafana shadow vs live< 5% divergence

Promote to General live

GateHow measuredThreshold
Zero DATA_UNAVAILABLE rejections during normal hours over 7 daysCorrelationShockGuardDataUnavailable 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