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

← All stages · stage 7 of 11

Stage 7

Decision, risk, and sizing

The single gate that turns a forecast into an approved trade. No stage downstream may bypass it.

Challenge we are solving

A profitable forecast is not enough. The trade must clear strategy caps, liquidity caps, portfolio caps, correlation limits, and the kill-switch state. If any check fails the trade does not exist.

What this stage does

Combines p_hat, q_exec, fees, and uncertainty buffer into a single net_edge number; runs every risk check independently; and produces a final approved size (or a rejection with a reason code).

Why this stage exists

This is where forecasting, execution, and portfolio risk are reconciled into one decision. Every downstream artefact (order, fill, audit) traces back to this gate.

Flow

p_hat0.67 from stage 5
q_exec0.645 from stage 6
net_edge= p_hat − q_exec − fees − buffer
Risk
checks
book fresh · within limits · correlation OK
DecisionBUY 2,000 YES

What the backend should expose

  • decision_id, correlation_id (links to stages 3-6)
  • p_hat, q_exec, fees, uncertainty_buffer, net_edge
  • risk_checks[] (each with vote, reason_code, owner)
  • approved_size (min of strategy / liquidity / portfolio caps)
  • concentration metric (market_exposure / gross_exposure)
  • final action (APPROVE · REJECT · WARN-only · paused)
  • reason_code if rejected

Maths we expect here

Every formula below is implemented in packages/polytraders-bots/ or packages/polytraders-runner/. Treat the worked example as the unit-test sanity check you should be able to reproduce locally.

1

Net edge — the gating quantity

\[net\_edge = \hat p - q\_exec - fees\_per\_share - buffer\]
SymbolMeaningUnits / range
\(\hat p\)Stage 5 forecast probability0..1
\(q_exec\)Stage 6 expected fill price0..1
\(fees_per_share\)Fees expressed per share (fee / size)0..1
\(buffer\)Stage 5 uncertainty buffer0..1
worked example\[0.670 - 0.6425 - 0.0032 - 0.045 = -0.021 \;\Rightarrow\; \text{REJECT}\;\;\;|\;\;\; 0.670-0.6425-0.0032-0.015=+0.009 \;\Rightarrow\; \text{APPROVE}\]

Same formula on both sides of the trade — net_edge for a SELL flips the sign on (p_hat − q_exec). This is the platform's single gating rule.

2

Approved size (cap-min)

\[approved\_size = \min(strategy\_cap,\; liquidity\_cap,\; portfolio\_cap)\]
SymbolMeaningUnits / range
\(strategy_cap\)Per-strategy max from policyshares
\(liquidity_cap\)Depth available within slippage budgetshares
\(portfolio_cap\)Remaining capital headroom for this marketshares
worked example\[\min(5{,}000,\;3{,}200,\;2{,}000) = 2{,}000\]

Each cap is owned by a different bot (StrategyPolicy, LiquidityGuard, PortfolioGuard). The decision gate takes the minimum; no override.

3

Concentration check

\[concentration = \frac{market\_exposure}{gross\_exposure} \le concentration\_limit\]
SymbolMeaningUnits / range
\(market_exposure\)Notional already on this marketUSD
\(gross_exposure\)Sum of |notional| across all marketsUSD
\(concentration_limit\)Cap from risk policy (e.g. 0.15 = 15%)0..1
worked example\[\tfrac{\$45{,}000}{\$300{,}000}=0.15 = \text{limit} \;\Rightarrow\; \text{at cap}\;\Rightarrow\; \text{REJECT increase}\]
4

Kelly-fraction guard (informational)

\[f^{*} = \frac{\hat p - q\_exec}{1 - q\_exec},\qquad size_{Kelly} = f^{*} \cdot bankroll\]
SymbolMeaningUnits / range
\(f^{*}\)Edge-to-pricing Kelly fraction0..1
\(bankroll\)Available risk capitalUSD
worked example\[f^{*}=\tfrac{0.670-0.6425}{1-0.6425}=0.077,\;\; bankroll=\$200{,}000 \;\Rightarrow\; size_{Kelly}\approx\$15{,}300\]

The decision gate caps actual size to a fraction of f* (typically 0.25·f*). Kelly is upper-bound informational, never the executed size.

5

Trade-or-not predicate

\[\text{TRADE} \iff (net\_edge > 0) \;\wedge\; \forall\,c\in checks: c.vote = \text{APPROVE}\]
SymbolMeaningUnits / range
\(checks\)Risk bots emitting independent votesset of (vote, reason_code)
worked example\[net\_edge=+0.009,\;\;\text{all 7 checks APPROVE} \;\Rightarrow\; \text{TRADE BUY }2{,}000\,\text{YES}\]

A single REJECT vote kills the trade — there is no override. The reason_code is logged on the decision packet.

How a developer codes this stage

Reference TypeScript implementation lives in packages/polytraders-* at the repository root. Stage owners maintain these files — read them before writing new code.

  • packages/polytraders-contracts/src/RiskVote.tsThe vote each risk check emits. Strict — any single REJECT kills the trade.
  • packages/polytraders-bots/src/riskReference risk bots: portfolioguard, liquidityguard, killswitch, stalebookguard.
  • packages/polytraders-runner/src/pipeline.jsThe decision gate in code — collects votes, applies the caps, emits the approval packet.

See it in the platform mock

The platform mock is the source of truth for what each stage's UI exposes. Open these alongside the code references.

Reason codes emitted at this stage

  • RISK_*RISK — guardrails, caps, kill-switch
  • STRAT_*STRAT — strategy, model, fair-value

Hover or tap any reason code on this page (or anywhere on the site) to see its canonical short description. Full registry: /standards/reason-codes.