Pre-trade risk concepts
This guide explains the economics and rules behind Risk Service pre-trade checks. It matches how risk-service computes values where noted; field names align with gRPC / risk.pretrade_decision.v1 / risk.exposure_snapshot.v1.
Reduce-only orders
Section titled “Reduce-only orders”A reduce-only order may only decrease or close an existing position. It must not open a new position or add to the same side.
Rejections
Section titled “Rejections”| Situation | Reject reason | Typical message |
|---|---|---|
| No open position for that user and symbol (or empty size) | REDUCE_ONLY_NO_POSITION | No position to reduce |
| Order side would increase the position (e.g. BUY when long, SELL when short) | REDUCE_ONLY_INVALID_SIDE | e.g. Reduce-only BUY requires a short position / Reduce-only SELL requires a long position |
| Order quantity greater than absolute position size | REDUCE_ONLY_EXCEEDS_SIZE | Order size exceeds position size |
| Position size or order quantity not a valid positive decimal (parse error) | INTERNAL_ERROR | e.g. Invalid position size / Invalid order quantity |
Side rules (signed position size):
- Long position (positive size): only SELL reduce-only is valid (reduces long).
- Short position (negative size): only BUY reduce-only is valid (reduces short).
Examples
Section titled “Examples”Long +0.5 BTC
| Order | Result |
|---|---|
SELL 0.1, reduce_only=true | Approved (reduces long) |
SELL 0.5, reduce_only=true | Approved (closes long) |
SELL 0.7, reduce_only=true | Rejected — REDUCE_ONLY_EXCEEDS_SIZE |
BUY 0.1, reduce_only=true | Rejected — REDUCE_ONLY_INVALID_SIDE |
Short −0.5 BTC
| Order | Result |
|---|---|
BUY 0.1, reduce_only=true | Approved (reduces short) |
SELL 0.1, reduce_only=true | Rejected — REDUCE_ONLY_INVALID_SIDE |
No position
| Order | Result |
|---|---|
| Any reduce-only order | Rejected — REDUCE_ONLY_NO_POSITION |
Portfolio unrealized PnL
Section titled “Portfolio unrealized PnL”Portfolio unrealized PnL is the sum of unrealized PnL across all open positions for a user (all symbols). Each position has its own unrealized PnL; the portfolio figure is the total.
Formula
Section titled “Formula”For each open position:
Unrealized PnL = (Mark price − Entry price) × Position size
(Size is signed: long positive, short negative, so the formula is correct for both sides.)
Portfolio unrealized PnL = Σ (per-position unrealized PnL).
Worked example
Section titled “Worked example”| Symbol | Size | Entry | Mark | Unrealized PnL |
|---|---|---|---|---|
| BTCUSDT | +0.5 | 60,000 | 62,000 | (62,000 − 60,000) × 0.5 = +1,000 |
| ETHUSDT | −2 | 3,000 | 2,800 | (2,800 − 3,000) × (−2) = +400 |
| SOLUSDT | 10 | 100 | 95 | (95 − 100) × 10 = −50 |
Portfolio unrealized PnL = 1,000 + 400 − 50 = +1,350.
Why it matters (cross margin)
Section titled “Why it matters (cross margin)”In cross-margin setups, one wallet backs all positions. Risk uses portfolio-level PnL, not a single symbol in isolation.
In risk-service, equity used for pre-trade is:
Equity = Available wallet balance (for margin) + Portfolio unrealized PnL
That equity drives:
- Margin sufficiency checks
- Projected leverage (see below)
- Projected margin ratio vs maintenance (see below)
- Liquidation-risk style signals in pre-trade
Initial margin (required initial margin)
Section titled “Initial margin (required initial margin)”Initial margin is the minimum collateral needed to open or increase exposure: “Does the user have enough equity to support this trade?”
Formula
Section titled “Formula”Required initial margin = Projected notional ÷ Max leverage
- Projected notional — position value after the order (for the logic below; see next section for the full service definition).
- Max leverage — effective cap for that user/symbol/instrument (minimum of instrument, user profile, tier, etc., as enforced by the engine).
Example
Section titled “Example”- Order adds 0.5 BTC exposure at mark 60,000 → projected notional 30,000
- Max leverage 10×
Required initial margin = 30,000 ÷ 10 = 3,000 (user needs enough equity to cover this, per service rules).
gRPC exposes this as required_initial_margin; Kafka risk.exposure_snapshot.v1 includes required_initial_margin.
Projected notional
Section titled “Projected notional”Meaning: Notional value of exposure after the order is applied, in the context of the pre-trade calculation.
Intuition (single symbol):
Projected notional ≈ |Position size after fill| × Mark price
(plus the effect of other exposure on the same symbol the engine includes.)
Why it is used
Section titled “Why it is used”- Exposure limits
- Tier selection (maintenance % often depends on notional band)
- Required initial margin (notional ÷ max leverage)
- Published on events as
projected_notional(decimal string)
Implementation note (risk-service)
Section titled “Implementation note (risk-service)”For the symbol under check, risk-service aggregates marked position notional, new order notional, and open-order notional into one projected notional before leverage and margin math. So it is not always “only one position × mark”; open orders on the same symbol are included.
Projected leverage
Section titled “Projected leverage”Purpose: Estimate how leveraged the account is after the order, relative to equity.
Formula
Section titled “Formula”Projected leverage = Projected notional ÷ Equity
- Projected notional — as computed for the check (symbol-scoped aggregate above).
- Equity — wallet available for margin plus portfolio unrealized PnL (cross-margin style).
Example
Section titled “Example”- After the order, projected position 0.7 BTC at mark 60,000 → projected notional 42,000
- Equity 3,000
Projected leverage = 42,000 ÷ 3,000 = 14×
Pre-trade compares this to max allowed leverage and may reject with MAX_LEVERAGE_EXCEEDED.
Exposed as projected_leverage in decisions and exposure snapshots.
Margin ratio and projected_margin_ratio
Section titled “Margin ratio and projected_margin_ratio”Many venues use the words “margin ratio” in different ways. Two useful ideas:
Common alternative definition (exposure vs equity)
Section titled “Common alternative definition (exposure vs equity)”Some documents define:
Margin ratio (alternative) = Total position value ÷ Equity
That is essentially “how many times notional is covered by equity” (related to leverage). This is not the same as the projected_margin_ratio string emitted by risk-service.
TradeX pre-trade: projected_margin_ratio
Section titled “TradeX pre-trade: projected_margin_ratio”In risk-service, after applying a maintenance margin amount from the margin tier (maintenance % × projected notional):
Projected margin ratio = Equity ÷ Maintenance margin
- Higher ratio → more cushion vs maintenance requirement.
- Liquidation risk flags when this ratio falls below a configured threshold (e.g. equity no longer covers maintenance adequately).
So in APIs and Kafka, read projected_margin_ratio as equity divided by required maintenance margin for the projected state, not “notional divided by equity.”
Example (illustrative)
Section titled “Example (illustrative)”If maintenance margin for the projected state is 1,500 and equity is 3,000:
Projected margin ratio = 3,000 ÷ 1,500 = 2 (equity is 2× maintenance).
Quick reference (Kafka / gRPC)
Section titled “Quick reference (Kafka / gRPC)”| Concept | Typical field(s) |
|---|---|
| Equity after cross PnL | equity |
| Notional after order (aggregate) | projected_notional |
| Leverage vs equity | projected_leverage |
| Equity vs maintenance | projected_margin_ratio |
| Collateral to open/increase | required_initial_margin |
| Near-liquidation style flag | liquidation_risk (boolean) |
See also
Section titled “See also”- Risk Service — APIs, caching, Kafka topics
- Kafka Events —
risk.pretrade_decision.v1,risk.exposure_snapshot.v1