vault 開発 · 本番での vault 運用 · 7/7
vault のリスク管理
何がかかっているか
vault を運用するとは、他人のお金で売買することです。バグ1つ、誤ったシグナル1つ、抑えの効かないリスク1つで、預けた人の資金を失うことがあります。リスク管理はおまけではありません。keeper で最も大事な部分です。
リスク管理の枠組み
第1層: ポジションごとのリスク
# 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
第2層: ポートフォリオ全体のリスク
# 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
第3層: システムのリスク
- 緊急停止スイッチ — すべての売買をすぐに止められるようにします
- デッドマンスイッチ —
exchange.schedule_cancel(time_ms)は、先送りしないかぎり、指定した時刻にすべての未約定の注文を取り消します。時刻は5秒以上先でなければならず、発動は1日に10回までです(exchange エンドポイントの資料) - サーキットブレーカー — エラーが N 回続いたら自動で止めます
- 古いデータの確認 — 古い相場のデータで売買しないようにします
- 二重注文の防止 — 注文 ID を記録して、同じ注文を2回送らないようにします
緊急停止
どの vault の keeper にも、非常ボタンが要ります。
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")
よくある失敗
| 失敗 | 原因 | 対策 |
|---|---|---|
| keeper が落ちる | 処理していない例外 | systemd で自動で再起動する |
| 古い注文が残る | 注文中に WS が切れる | 再接続したときに突き合わせる |
| 二重注文 | 再試行のロジックのバグ | 送った注文 ID を記録する |
| 清算 | 証拠金の比率を見ていない | 証拠金に厳格な上限を設ける |
| 逆向きの売買 | シグナルが反転するバグ | すべての売買を妥当か確かめる |
| 無限ループ | ロジックの誤り | 1回の処理で回す上限回数を決める |
リスク管理をテストする
戦略のコードよりも、リスク管理のコードを多くテストしてください。悪い戦略はゆっくりお金を失います。壊れたリスク管理は、一度にすべてを失います。
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