Risk engine
A pure function. Given a proposal, account state and exchange limits, it returns allow or deny plus the clamped values. The model cannot talk its way past it.
Limits
| Field | Meaning |
|---|---|
markets | Allow-list of coins. Anything else is refused. |
maxLeverage | Your cap. Clamped further to the exchange cap read live from Hyperliquid meta (BTC 40x, ETH 25x at time of writing). A higher cap is clamped, never errors. |
marginPerTradeUsd | Margin per position, also clamped to withdrawable balance. |
maxOpenPositions | Concurrent positions across all markets. One position per market. |
maxDailyLossUsd | Optional kill switch. Once realized PnL since 00:00 UTC (fees included) reaches the negative of this, no new positions open. Closing stays allowed. |
Behavior
- Clamp: leverage and margin over the limit are reduced and the reduction is reported back to the model.
- Deny: disallowed market, duplicate position, full book, kill switch tripped, non-positive inputs, or notional under Hyperliquid's $10 minimum.
import { evaluate } from "perpad";
evaluate(
{ coin: "BTC", side: "long", leverage: 100, marginUsd: 900 },
{ openCoins: [], withdrawableUsd: 1000, dailyPnlUsd: 0, exchangeMaxLeverage: { BTC: 40 } },
{ markets: ["BTC"], maxLeverage: 50, marginPerTradeUsd: 100, maxOpenPositions: 1 },
);
// { allowed: true, adjusted: { leverage: 40, marginUsd: 100 }, reasons: [...clamped] }
Dry-run any proposal from an MCP client with the check_risk tool.