Risk engine
Every order intent runs through 8 checks, in a fixed order, before it can reach a venue.
Execution order
The checks run in the order below. The breaker check short-circuits the rest: if it trips, evaluation stops immediately and the intent is rejected with BREAKER_TRIPPED. Every other check runs to completion and all failures are collected into a single decision — an intent can fail multiple checks at once.
| # | Check | Rejection code | Fails when |
|---|---|---|---|
| 1 | Breaker | BREAKER_TRIPPED | The agent's circuit breaker is tripped |
| 2 | Market filters | MARKET_BLOCKED / TAG_NOT_ALLOWED | Market is block-listed, or carries a tag outside the allow-list |
| 3 | Order size | MAX_ORDER_EXCEEDED | size_usdc exceeds max_order_usdc |
| 4 | Daily volume | DAILY_VOLUME_EXCEEDED | Rolling 24h volume + this order exceeds daily_volume_limit_usdc |
| 5 | Concentration | POSITION_CONCENTRATION | Resulting position exceeds max_concentration_pct of bankroll |
| 6 | Kelly cap | KELLY_EXCEEDED / NO_EDGE | Size exceeds Kelly-optimal sizing, or expected value ≤ 0 |
| 7 | Price sanity | PRICE_BAND | Price deviates from midpoint by more than price_band_pct |
| 8 | Liquidity impact | SPREAD_TOO_WIDE / BOOK_IMPACT | Spread or estimated slippage exceeds configured maximums |
Reading a risk decision
A rejected intent returns HTTP 409 with the full decision — not just the first failure:
{
"approved": false,
"failures": [
{
"code": "DAILY_VOLUME_EXCEEDED",
"message": "Daily volume 9500 + 600 exceeds limit 10000",
"details": { "current": 9500, "limit": 10000, "requested": 600 }
},
{
"code": "POSITION_CONCENTRATION",
"message": "Position value 4200 exceeds 25% of bankroll",
"details": { "concentration_pct": 0.31, "max": 0.25 }
}
],
"adjusted_size_usdc": 850.0,
"checked_at": "2026-01-15T10:30:00Z"
}Adjusted size suggestions
When the concentration or Kelly cap check fails, Meridian computes the maximum size that would have passed. If that size is at least $5 USDC, it's returned as adjusted_size_usdc — retry the intent with that size instead of guessing.
Kelly-optimal sizing
When your intent includes a probability_estimate, Meridian computes the Kelly-optimal position size from your estimate and the market's implied odds:
kelly_size = max(0, (b·p − (1−p)) / b) × fraction × bankroll
where:
p = your probability_estimate
b = market odds implied by price (1/price − 1)
fraction = configured Kelly fraction (default 0.25, i.e. quarter-Kelly)If p·b ≤ (1−p) — no edge — the Kelly size is 0 and the intent fails with NO_EDGE regardless of size.
Circuit breaker
The breaker trips on either condition, independently:
- Portfolio drawdown from the peak recorded bankroll snapshot ≥
max_drawdown_pct - 10 consecutive risk rejections within a 10-minute sliding window, with no approved intent in between
While tripped, every intent for that agent is rejected immediately with BREAKER_TRIPPED — no other checks run. Reset it explicitly via POST /v1/breaker/reset once you've reviewed what happened.