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.10 CapitalAllocator

1.10 CapitalAllocator

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

CapitalAllocator carves the user's total risk budget across active strategies and refuses new orders that would cause any strategy slice or the aggregate portfolio to exceed its configured allocation. It emits a RESHAPE constraint when a downsize is possible, and HARD_REJECT when the budget is exhausted.

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 — enforces per-strategy and portfolio-level capital allocation budgets
Default modeplanned
User-visiblesummary-only
Developer ownerPolytraders core — Risk pod

Operational profile

Modes supportedquarantine

2. Purpose

CapitalAllocator carves the user's total risk budget across active strategies and refuses new orders that would cause any strategy slice or the aggregate portfolio to exceed its configured allocation. It emits a RESHAPE constraint when a downsize is possible, and HARD_REJECT when the budget is exhausted.

3. Why This Bot Matters

  • Over-allocation to a single strategy

    Unconstrained capital flow into one strategy can consume the user's full budget, preventing diversification and concentrating tail risk.

  • Aggregate portfolio budget exceeded

    Without a total-portfolio cap, concurrent strategies can collectively exceed the user's acceptable exposure, especially during correlated market events.

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
Open position notional per market from CLOBclob_publicYesTrack current strategy exposure across active positions.
Pending order value per strategyclob_authYesInclude pending orders in the budget calculation to prevent race conditions.

5. Required Internal Inputs

InputSourceRequired?Use
Capital allocation config per strategy and portfolio totalinternalYesLoad per-strategy budget slice and aggregate portfolio cap.
KillSwitch active flagKillSwitchYesIf active, reject all orders immediately.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
per_strategy_max_usd200016002000Maximum pUSD notional a single strategy may hold (open + pending).
portfolio_total_max_usd10000800010000Maximum total pUSD notional across all active strategies combined.
min_remaining_buffer_pct0.050.10.05Minimum fraction of the portfolio budget that must remain unallocated as a buffer. Orders bringing the budget below this fraction are reshaped to maintain the buffer.

7. Detailed Parameter Instructions

per_strategy_max_usd

What it means

Maximum pUSD notional a single strategy may hold (open + pending).

Default

{ "per_strategy_max_usd": 2000 }

Why this default matters

Caps each strategy at 2000 pUSD by default, forcing diversification across multiple strategies.

Threshold logic

ConditionAction
strategy_exposure + intent.size_usd <= 2000APPROVE
80-100% of capRESHAPE to remaining budget
> 100% of capHARD_REJECT — CAPITAL_ALLOCATOR_STRATEGY_BUDGET_EXCEEDED

Developer check

if (strategyExposure + intent.size_usd > params.per_strategy_max_usd) return reshape or reject;

User-facing English

Your order exceeds the budget allocated to this strategy.

portfolio_total_max_usd

What it means

Maximum total pUSD notional across all active strategies combined.

Default

{ "portfolio_total_max_usd": 10000 }

Why this default matters

Ensures the sum of all strategy exposures stays within the user's declared total risk budget.

Threshold logic

ConditionAction
total_exposure + intent.size_usd <= 10000APPROVE
> 10000HARD_REJECT — CAPITAL_ALLOCATOR_PORTFOLIO_BUDGET_EXCEEDED

Developer check

if (totalExposure + intent.size_usd > params.portfolio_total_max_usd) return reject('CAPITAL_ALLOCATOR_PORTFOLIO_BUDGET_EXCEEDED');

User-facing English

Your total portfolio exposure has reached its limit.

min_remaining_buffer_pct

What it means

Minimum fraction of the portfolio budget that must remain unallocated as a buffer. Orders bringing the budget below this fraction are reshaped to maintain the buffer.

Default

{ "min_remaining_buffer_pct": 0.05 }

Why this default matters

Keeps 5% of budget free to handle mark-to-market fluctuations without triggering cascade rejects.

Threshold logic

ConditionAction
remaining_pct >= 0.10APPROVE
0.05 <= remaining_pct < 0.10WARN
remaining_pct < 0.05RESHAPE — cap order to preserve buffer

Developer check

if (remainingAfterOrder / portfolioCap < params.min_remaining_buffer_pct) return reshape({ max_size_usd: remainingBudget - bufferAmount });

User-facing English

Your order was reduced to maintain a safety buffer in your portfolio budget.

8. Default Configuration

{
  "bot_id": "risk.capital_allocator",
  "version": "0.1.0",
  "mode": "hard_guard",
  "defaults": {
    "per_strategy_max_usd": 2000,
    "portfolio_total_max_usd": 10000,
    "min_remaining_buffer_pct": 0.05
  },
  "locked": {
    "per_strategy_max_usd": {
      "min": 100
    },
    "portfolio_total_max_usd": {
      "min": 500
    }
  }
}

9. Implementation Flow

  1. Receive OrderIntent with strategy_id, size_usd, and user context.
  2. Check KillSwitch; if active, HARD_REJECT(KILL_SWITCH_ACTIVE).
  3. Load capital allocation config (per_strategy_max_usd, portfolio_total_max_usd, buffer_pct) from internal store.
  4. Fetch current strategy exposure (open + pending) for strategy_id from CLOB and position ledger.
  5. Fetch total portfolio exposure across all strategies.
  6. If strategy_exposure + intent.size_usd > per_strategy_max_usd: compute safe_size = max(0, per_strategy_max_usd - strategy_exposure). If safe_size > 0, RESHAPE; else HARD_REJECT.
  7. If total_exposure + intent.size_usd > portfolio_total_max_usd * (1 - min_remaining_buffer_pct): RESHAPE or HARD_REJECT.
  8. All checks passed — APPROVE with budget metrics 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 evaluateCapital(intent):
  ks = FETCH internal.killswitch.status
  IF ks.active: EMIT RiskVote(HARD_REJECT, KILL_SWITCH_ACTIVE); RETURN

  config = FETCH internal.capital_config(intent.user_id)
  IF config IS NULL: EMIT RiskVote(HARD_REJECT, CAPITAL_ALLOCATOR_DATA_UNAVAILABLE); RETURN

  positions = FETCH clob.get_positions(intent.user_id, intent.strategy_id)
  IF positions IS NULL: EMIT RiskVote(HARD_REJECT, CAPITAL_ALLOCATOR_DATA_UNAVAILABLE); RETURN

  strategyExposure = positions.open_usd + positions.pending_usd
  totalExposure = positions.portfolio_total_usd

  // per-strategy check
  IF strategyExposure + intent.size_usd > config.per_strategy_max_usd:
    safeSize = config.per_strategy_max_usd - strategyExposure
    IF safeSize <= 0: EMIT RiskVote(HARD_REJECT, CAPITAL_ALLOCATOR_STRATEGY_BUDGET_EXCEEDED); RETURN
    EMIT RiskVote(RESHAPE_REQUIRED, constraints={max_size_usd: safeSize}); RETURN

  // portfolio buffer check
  buffer = config.portfolio_total_max_usd * config.min_remaining_buffer_pct
  IF totalExposure + intent.size_usd > config.portfolio_total_max_usd - buffer:
    EMIT RiskVote(HARD_REJECT, CAPITAL_ALLOCATOR_PORTFOLIO_BUDGET_EXCEEDED); RETURN

  EMIT RiskVote(APPROVE, checked_at=now_ms())

SDK calls used

  • clob.get_positions(user_id, strategy_id)
  • internal.capital_config(user_id)
  • internal.killswitch.status()

Complexity: O(1) per strategy lookup

11. Wire Examples

Input — what arrives on the wire

OrderIntent — strategy budget overflowinternal

{
  "intent_id": "int_b2c3d4e5f6a70002",
  "strategy_id": "strat_001",
  "size_usd": 400,
  "generated_at_ms": 1746800000000
}

Output — what the bot emits

RiskVote — RESHAPE_REQUIRED

{
  "guard_id": "risk.capital_allocator",
  "decision": "RESHAPE_REQUIRED",
  "severity": "WARN",
  "reason_code": "CAPITAL_ALLOCATOR_STRATEGY_BUDGET_EXCEEDED",
  "message": "Strategy exposure 1800 pUSD + 400 pUSD exceeds cap 2000. Resized to 200 pUSD.",
  "constraints": {
    "max_size_usd": 200
  },
  "checked_at": "2026-05-10T09:00:00Z"
}

12. Decision Logic

APPROVE

Strategy slice and portfolio total are both within budget after including the intent size.

RESHAPE_REQUIRED

Intent size would exceed the strategy or portfolio budget but a reduced size is possible within the remaining allocation.

REJECT

Budget is fully exhausted (no room even for a reduced order), or KillSwitch active.

WARNING_ONLY

— not yet authored —

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "guard_id": "risk.capital_allocator",
  "decision": "RESHAPE_REQUIRED",
  "severity": "WARN",
  "reason_code": "CAPITAL_ALLOCATOR_STRATEGY_BUDGET_EXCEEDED",
  "message": "Strategy exposure 1800 pUSD + intent 400 pUSD exceeds cap 2000 pUSD. Resized to 200 pUSD.",
  "constraints": {
    "max_size_usd": 200
  },
  "inputs_used": [
    "internal.capital_config",
    "clob_public.positions"
  ],
  "checked_at": "2026-05-10T09: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.
CAPITAL_ALLOCATOR_STRATEGY_BUDGET_EXCEEDEDRESHAPEStrategy slice is at or above per_strategy_max_usd.RESHAPE if room remains; else HARD_REJECT.Your order exceeds the budget for this strategy.
CAPITAL_ALLOCATOR_PORTFOLIO_BUDGET_EXCEEDEDHARD_REJECTPortfolio total is at or above the cap including buffer.HARD_REJECT; no reshape possible at portfolio level.Your total portfolio exposure has reached its limit.
CAPITAL_ALLOCATOR_BUFFER_WARNWARNRemaining portfolio buffer is below the warning threshold.Attach WARN annotation; allow order if still above hard floor.
CAPITAL_ALLOCATOR_DATA_UNAVAILABLEHARD_REJECTCapital config or position data unavailable.HARD_REJECT (fail-closed).We could not verify your position data. Please try again.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_risk_capitalallocator_decisions_totalcountercountdecision, reason_codeTotal RiskVote decisions by type and reason.
polytraders_risk_capitalallocator_strategy_exposure_usdgaugeusdstrategy_idCurrent strategy exposure in pUSD at evaluation time.
polytraders_risk_capitalallocator_portfolio_utilisation_pctgaugeratioPortfolio budget utilisation fraction (0–1) at last evaluation.
polytraders_risk_capitalallocator_eval_latency_mshistogrammillisecondsLatency from intent receipt to RiskVote emit.

Alerts

AlertConditionSeverityRunbook
CapitalAllocatorPortfolioNearCappolytraders_risk_capitalallocator_portfolio_utilisation_pct > 0.9P2#runbook-capitalallocator-near-cap
CapitalAllocatorDataUnavailablerate(polytraders_risk_capitalallocator_decisions_total{reason_code='CAPITAL_ALLOCATOR_DATA_UNAVAILABLE'}[5m]) > 0P1#runbook-capitalallocator-data

16. Developer Reporting

{
  "bot_id": "risk.capital_allocator",
  "decision": "RESHAPE_REQUIRED",
  "reason_code": "CAPITAL_ALLOCATOR_STRATEGY_BUDGET_EXCEEDED",
  "inputs_used": [
    "internal.capital_config",
    "clob_public.positions"
  ],
  "metrics": {
    "strategy_exposure_usd": 1800,
    "portfolio_exposure_usd": 5400,
    "intent_size_usd": 400,
    "per_strategy_cap": 2000,
    "portfolio_cap": 10000
  },
  "checked_at": "2026-05-10T09:00:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
Order downsized — strategy budgetYour order was reduced because this strategy has reached its capital allocation limit. The order was resized to fit within the remaining budget.
Order blocked — portfolio budget exhaustedYour total portfolio exposure has reached the configured limit. Please close some positions before placing new orders.

18. Failure-Mode Block

main_failure_modeApproving an order that exceeds the strategy or portfolio budget due to a stale position snapshot, causing over-allocation.
false_positive_riskRejecting an order because the position cache includes a recently cancelled pending order that has not yet been removed.
false_negative_riskApproving concurrent orders from the same strategy if idempotency deduplication is bypassed, leading to budget double-counting.
safe_fallbackIf capital allocation config or position data is unavailable, HARD_REJECT with CAPITAL_ALLOCATOR_DATA_UNAVAILABLE. Never approve on missing data.
required_dependenciesCapital allocation config store, CLOB position and pending order data, KillSwitch active flag

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
POSITION_DATA_UNAVAILABLEReturn 503 from CLOB positions endpointReturns to normal within one position-cache refresh cycle.
STRATEGY_BUDGET_EXHAUSTEDSet strategy exposure = per_strategy_max_usd in position mock, submit any intentReturns to APPROVE after strategy closes positions.
PORTFOLIO_BUFFER_BREACHSet portfolio total > portfolio_max * (1 - buffer) in position mockReturns to APPROVE after portfolio exposure decreases.

20. State & Persistence

Cold-start recovery

On cold start, position snapshot loaded from CLOB before first evaluation. If unavailable, HARD_REJECT until restored.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight200
Idempotency keyintent_id
Per-call timeout (ms)100
Backpressure strategydrop newest
Locking / mutual exclusionper-strategy_id optimistic lock to prevent concurrent over-allocation

22. Dependencies

Depends on (must run first)

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

Emits to (downstream consumers)

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

External services

ServiceEndpointSLA assumedOn failure
CLOB API (positions)https://clob.polymarket.com99.95% / 200ms p99HARD_REJECT(CAPITAL_ALLOCATOR_DATA_UNAVAILABLE) if position data unavailable.

23. Security Surfaces

Abuse vectors considered

  • Submitting concurrent intents to bypass per-strategy idempotency and double-count budget
  • Poisoning the position cache to show lower exposure than actual

Mitigations

  • Per-intent_id deduplication within 24h window prevents double-count
  • Position cache is validated against CLOB on each refresh; stale data triggers fail-closed

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 exposure values are denominated in pUSD. Uses V2 CLOB position endpoints; no order signing.

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ3-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 within strategy and portfolio budgetstrategy_exposure=500, portfolio_exposure=3000, intent.size_usd=300, caps=2000/10000APPROVE
Reshape when strategy budget partially exceededstrategy_exposure=1800, intent.size_usd=400, cap=2000RESHAPE_REQUIRED(max_size_usd=200)
Reject when strategy budget fully exhaustedstrategy_exposure=2000, intent.size_usd=100, cap=2000HARD_REJECT(CAPITAL_ALLOCATOR_STRATEGY_BUDGET_EXCEEDED)
Reject when portfolio cap exceededtotal_exposure=9800, intent.size_usd=300, portfolio_cap=10000, buffer=0.05HARD_REJECT(CAPITAL_ALLOCATOR_PORTFOLIO_BUDGET_EXCEEDED)

Integration Tests

TestExpected result
Reshape flows through to ExecutionPlan with reduced sizeExecutionPlan receives constraints.max_size_usd and does not exceed it
KillSwitch bypasses all budget checksHARD_REJECT(KILL_SWITCH_ACTIVE) without reading position data

Property Tests

PropertyRequired behaviour
Approved order never causes strategy exposure to exceed per_strategy_max_usdAlways true
Reshape size is always strictly <= requested order sizeAlways true

27. Operational Runbook

Incidents typically involve a position cache sync delay causing false budget exhaustion, or a genuine portfolio cap breach requiring position reduction.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
CapitalAllocatorPortfolioNearCapCheck current portfolio exposure in Grafana; confirm positions are accurate and no cache sync issue.Risk pod lead if genuine cap breach confirmed.
CapitalAllocatorDataUnavailableCheck CLOB API status and Redis connectivity; manually refresh position cache if possible.Risk pod lead if sustained > 2 minutes.

Manual overrides

  • polytraders risk refresh-positions --user-id <id> — After a known CLOB sync delay; forces immediate position cache refresh.

Healthcheck

GET /internal/health/capitalallocator → green: Position cache age < 30s, CLOB reachable, no DATA_UNAVAILABLE rejections in last 5m; red: Position cache age > 60s or CLOB 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
Unit tests pass including reshape and reject budget scenariosCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Shadow reshape rate within 10% of expected baseline over 48hGrafana shadow vs live dashboard< 10% divergence

Promote to General live

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