Skip to Content
HyperQuote is live on HyperEVM — Start trading →
MakersRisk Management

Risk Management

The maker SDK includes a built-in risk framework that tracks open exposure and enforces configurable limits before any quote is submitted. This prevents makers from accumulating unbounded positions.

RiskState

The RiskState class tracks the maker’s current exposure across two dimensions:

class RiskState { expiryBuckets: Map<bigint, ExpiryBucket>; // delta by expiry notionalByCollateral: Map<string, bigint>; // notional by collateral token }

ExpiryBucket

Each unique option expiry has its own bucket tracking aggregate delta exposure and notional:

interface ExpiryBucket { deltaExposure: number; // signed: positive for calls, negative for puts notional: bigint; // total notional across all positions for this expiry }

Per-Collateral Notional

Total notional is tracked separately for each collateral token (USDC, USDH, USDT0). This allows the maker to set different exposure limits per stablecoin.

Risk Configuration

Risk limits are defined in the RiskConfig interface:

interface RiskConfig { maxNotionalPerCollateral: Record<string, bigint>; // per collateral token maxTenorSecs: number; // max time to expiry in seconds maxStrikeDeviationPct: number; // max strike deviation from spot (fraction) maxDeltaPerExpiry: number; // max absolute delta per expiry bucket minPremium: Record<string, bigint>; // minimum premium per collateral token }

Default Limits

The SDK ships with these defaults:

LimitDefault ValueDescription
maxTenorSecs7,776,000 (90 days)Maximum time to option expiry
maxStrikeDeviationPct0.5 (50%)Maximum strike distance from spot
maxNotionalPerCollateral1,000,000 USDC (1e12 units)Maximum notional per collateral
maxDeltaPerExpiry100Maximum absolute delta per expiry bucket
minPremium1000 units (0.001 USDC)Minimum premium to quote

Risk Checks

The checkRisk function runs five checks in sequence. If any check fails, the RFQ is skipped:

import { checkRisk, RiskState } from "@hyperquote/sdk-maker"; const riskState = new RiskState(); const result = checkRisk(rfq, market, config.risk, riskState, cDec, delta); if (!result.passed) { console.log(`Risk check failed: ${result.reason}`); return; // skip this RFQ }

1. Max Tenor Check

Rejects RFQs with expiry too far in the future:

const tenor = Number(rfq.expiry - now); if (tenor > config.maxTenorSecs) { return { passed: false, reason: `Tenor ${tenor}s exceeds max` }; }

The on-chain contract also enforces a 90-day maximum expiry (MAX_EXPIRY_DURATION = 90 days), but the maker can set a tighter limit.

2. Max Strike Deviation

Rejects strikes that are too far from the current spot price:

const deviation = Math.abs(strike - spot) / spot; if (deviation > config.maxStrikeDeviationPct) { return { passed: false, reason: `Strike deviation ${deviation * 100}% exceeds max` }; }

A maxStrikeDeviationPct of 0.5 means the strike must be within 50% of spot. For a $25 spot, this allows strikes from $12.50 to $37.50.

3. Per-Collateral Notional Limit

Prevents total notional exposure from exceeding the configured maximum for each collateral token:

const notional = computeNotional(rfq.strike, rfq.quantity, 18, cDec); const currentNotional = state.notionalByCollateral.get(collateralKey) ?? 0n; if (currentNotional + notional > maxNotional) { return { passed: false, reason: "Notional would exceed max" }; }

Notional is computed as ceilDiv(strike * quantity, 10^(18 + uDec - cDec)), matching the on-chain CollateralMath._strikeTimesQuantity formula.

4. Max Delta Per Expiry

Prevents the delta exposure for any single expiry bucket from exceeding the configured limit:

const bucket = state.expiryBuckets.get(rfq.expiry); const newDelta = bucket.deltaExposure + delta; if (Math.abs(newDelta) > config.maxDeltaPerExpiry) { return { passed: false, reason: "Delta exposure exceeds max for expiry" }; }

Delta is signed: positive for calls, negative for puts. A maxDeltaPerExpiry of 100 means the absolute net delta per expiry cannot exceed 100 in either direction. Puts and calls can offset each other.

5. Min Premium Check

The RFQ’s minPremium field is checked against the maker’s configured minimum. This is a soft check — the actual premium comparison happens after pricing.

Recording Quotes

After a quote passes risk checks and is submitted, call riskState.recordQuote() to update the exposure tracking:

import { computeNotional } from "@hyperquote/sdk-maker"; const notional = computeNotional(rfq.strike, rfq.quantity, 18, cDec); riskState.recordQuote( rfq.collateral, // collateral token address rfq.expiry, // expiry timestamp notional, // notional in collateral units pricing.delta, // signed delta from pricing engine rfq.isCall, // true for calls, false for puts );

This updates both the notionalByCollateral map and the expiryBuckets map, ensuring subsequent risk checks reflect the cumulative exposure.

Risk state is tracked locally in memory. If the maker bot restarts, the risk state resets to zero. Production systems should persist risk state or resync from on-chain position data.

Notional Computation

The computeNotional function mirrors the on-chain CollateralMath calculation:

function computeNotional( strike: bigint, // 1e18 fixed-point quantity: bigint, // underlying base units uDec: number, // underlying decimals (18 for WHYPE) cDec: number, // collateral decimals (6 for USDC) ): bigint { const product = strike * quantity; const exponent = 18n + BigInt(uDec) - BigInt(cDec); const divisor = 10n ** exponent; return (product - 1n) / divisor + 1n; // ceilDiv }

For example, a $25 strike on 1 WHYPE with USDC (6 decimals):

notional = ceilDiv(25e18 * 1e18, 10^(18+18-6)) = ceilDiv(25e36, 10^30) = 25_000_000 (= $25 in USDC base units)

Example Configuration

const riskConfig: RiskConfig = { maxNotionalPerCollateral: { [USDC.toLowerCase()]: 1_000_000n * 10n ** 6n, // $1M in USDC [USDH.toLowerCase()]: 500_000n * 10n ** 6n, // $500K in USDH }, maxTenorSecs: 30 * 24 * 3600, // 30 days (tighter than protocol max) maxStrikeDeviationPct: 0.3, // 30% from spot maxDeltaPerExpiry: 50, // tighter delta limit minPremium: { [USDC.toLowerCase()]: 10_000n, // $0.01 USDC minimum [USDH.toLowerCase()]: 10_000n, }, };
Last updated on