Vault Development · Production Vault Operations · 7/7
Risk Management for Vaults
The Stakes
When you run a vault, you're trading other people's money. A bug, a bad signal, or uncontrolled risk can lose depositors' funds. Risk management isn't optional — it's the most important part of your keeper.
Risk Framework
Layer 1: Position-Level Risk
# Maximum size for any single position
MAX_POSITION_PCT = 0.20 # 20% of vault equity
# Maximum leverage per position
MAX_LEVERAGE = 5
# Stop-loss for every position
STOP_LOSS_PCT = 0.05 # 5% loss triggers close
def check_position_risk(position_value: float, vault_equity: float) -> bool:
"""Return True if position is within risk limits."""
return position_value / vault_equity <= MAX_POSITION_PCT
Layer 2: Portfolio-Level Risk
# Maximum total margin usage
MAX_MARGIN_RATIO = 0.60 # Use at most 60% of equity as margin
# Maximum drawdown before emergency shutdown
MAX_DRAWDOWN_PCT = 0.15 # 15% drawdown → close everything
# Maximum number of simultaneous positions
MAX_POSITIONS = 5
def check_portfolio_risk(margin_ratio: float, drawdown: float,
num_positions: int) -> bool:
"""Return True if portfolio is within risk limits."""
if margin_ratio > MAX_MARGIN_RATIO:
return False
if drawdown > MAX_DRAWDOWN_PCT:
return False
if num_positions > MAX_POSITIONS:
return False
return True
Layer 3: System-Level Risk
- Kill switch — Ability to stop all trading immediately
- Dead man's switch —
exchange.schedule_cancel(time_ms)cancels all open orders at a future time unless you push it back; the time must be at least 5 seconds ahead, and it can trigger at most 10 times per day (exchange endpoint docs) - Circuit breaker — Auto-stop after N consecutive errors
- Stale data check — Don't trade on old market data
- Duplicate order prevention — Track order IDs to prevent double-submission
Emergency Shutdown
Every vault keeper needs a panic button:
import logging
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
def emergency_shutdown(exchange: Exchange, info: Info, vault_address: str):
"""Close all positions and cancel all orders immediately.
`exchange` must be created with vault_address=vault_address.
"""
# Cancel all open orders
open_orders = info.open_orders(vault_address)
for order in open_orders:
try:
exchange.cancel(order["coin"], order["oid"])
except Exception as e:
logging.error(f"Failed to cancel {order['oid']}: {e}")
# Close all positions
state = info.user_state(vault_address)
for ap in state["assetPositions"]:
p = ap["position"]
size = float(p["szi"])
if abs(size) > 0:
try:
# Reduce-only IOC order on the closing side
exchange.market_close(p["coin"], sz=abs(size))
except Exception as e:
logging.error(f"Failed to close {p['coin']}: {e}")
logging.critical("EMERGENCY SHUTDOWN COMPLETE")
Common Failure Modes
| Failure | Cause | Mitigation |
|---|---|---|
| Keeper crash | Unhandled exception | Auto-restart via systemd |
| Stale orders | WS disconnect during order | Reconcile on reconnect |
| Double orders | Retry logic bug | Track submitted order IDs |
| Liquidation | Ignoring margin ratio | Hard margin limits |
| Wrong direction | Signal inversion bug | Sanity check all trades |
| Infinite loop | Logic error | Maximum iterations per cycle |
Testing Risk Management
Test your risk management code more than your strategy code. A bad strategy loses money slowly; broken risk management loses everything at once.
def test_emergency_shutdown():
"""Test that emergency shutdown closes all positions."""
# Set up mock positions
# Call emergency_shutdown
# Assert all positions are closed
# Assert all orders are cancelled