FABRKNT
Step 5. Liquidation:レバレッジ環境における非単調性の発見と清算エンジンの構築
レッスン 3 / 14·CONTENT25 分50 XP
コース
Step 5. Liquidation:レバレッジ環境における非単調性の発見と清算エンジンの構築
レッスンの役割
CONTENT
順序
3 / 14

レッスン2 — MarginRatio + MarginHealth — エンジンが返す分類型

問い

margin_health を表す型は何にすべきか? bool(liquidatable か否か)で足りるか? エンジンはアカウントごとに「新規リスクを取れるか / force-close すべきか / close だけで不足カバーできるか」の判断をする。これを 1 つの型でどう表すか?

原理(最小モデル)

  • MarginRatiotype alias でなく newtype。 newtype なら「bps スケールの ratio を期待する所に生の i64 を渡した」をコンパイル時に捕まえる(funding の MarkPrice(pub u64) vs u64 と同じ規律)。
  • MarginHealth はちょうど 4 variants。 Safe/AtRisk/Liquidatable/Underwater、それぞれ異なるエンジン動作を許可する。どれを潰しても他の部分が必要とする情報が失われる。
  • enum のカーディナリティ = それが許可する action のカーディナリティ。 エンジンが下流で 3 判断(新規可否 / force-close 可否 / close だけで足りるか)するから 4 variant。
  • MarginHealthPartialOrd/Ord を derive しない。 variants は worsening order を成すが、health > Safe の順序比較はコード臭。matches!(health, Liquidatable | Underwater) のほうが意図が明示的で exhaustiveness check も効く。

具体例

4 variant が許可する action のマトリクス:

                    │ (a) 新規ポジ開ける? │ (b) Force-close?  │ (c) close だけで不足カバー? │
   Safe              │ ✅ yes              │ ❌ no              │ N/A (close 不要)           │
   AtRisk            │ ❌ no               │ ❌ no              │ N/A (close 不要)           │
   Liquidatable      │ ❌ no               │ ✅ yes             │ ✅ yes (equity 残あり)      │
   Underwater        │ ❌ no               │ ✅ yes             │ ❌ no → insurance fund 吸収 │

下流挙動: Safe→運用継続 / AtRisk→警告・新規拒否 / Liquidatable→close+fee+残返却 / Underwater→close+不足を fund 補填

各 variant がそのまま「許可される action のセット」を表す。state machine の variants は、自分がトリガーする下流 action の数だけ存在する。

失敗例(誤解)

MarginHealthbool(liquidatable か否か)でいい」は誤り — エンジンは 1 でなく 3 つの下流判断を要求する。bool だと (a)「新規ポジを開けるか」と (c)「insurance fund が関与するか」を 1 ビットに潰す。LiquidatableUnderwater をマージすると「insurance fund を呼ぶべきか」の信号が型から消え、エンジンが equity を再計算する羽目に。


ここまでで「4 variant = 3 判断 + 1」は着地した。ここから 2 つの分類型を足す(受動的データ型なのでテストなし、最初の挙動テストはレッスン4)。コードは完全形。

🛑 予測。 MarginHealth は何 variants 必要か?(ヒント: エンジンは各アカウントで 3 判断 — (a) 新規リスクを取れる? (b) force-close すべき? (c) close だけで不足カバーできる?)(答え: 3 問 → 4 variants。Safe=(a)yes、AtRisk=(a)no/(b)no、Liquidatable=(a)no/(b)yes/(c)yes、Underwater=(a)no/(b)yes/(c)no。3-variant に潰すと Liquidatable/Underwater がマージされ「insurance fund が関与するか」の信号が消える。)

ステップで組み立てる

Step 1: src/types.rs に追記

LiquidationParams の impl の後に:

/// Account margin ratio = `equity / notional`, scaled by [`MARGIN_SCALE`].
///
/// Sign: usually non-negative; can be negative when the account is
/// "underwater" — accumulated losses have driven equity below zero, and
/// liquidating the position alone cannot cover the deficit. The insurance
/// fund absorbs that shortfall.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MarginRatio(pub i64);

/// Margin health classification given the account's current margin ratio
/// and the network's params. Four states, in decreasing health order.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MarginHealth {
    /// Margin ratio ≥ initial margin requirement. Healthy: the account
    /// can open new positions or increase existing ones.
    Safe,
    /// Margin ratio ∈ [maintenance, initial). Allowed to hold existing
    /// positions but not to add risk. Production UIs typically warn the
    /// user.
    AtRisk,
    /// Margin ratio < maintenance, equity still ≥ 0. The engine should
    /// liquidate the position at market; the account's remaining equity
    /// (after the liquidation fee) returns to the account.
    Liquidatable,
    /// Margin ratio < 0 (equity is negative). Closing the position at
    /// any price won't fully cover losses. The insurance fund absorbs
    /// the shortfall.
    Underwater,
}

MarginRatio(pub i64) は newtype(守るべき不変量がないので透明な pub フィールド、ゲッターで隠さない)。MarginHealthPartialOrd/Ord を derive しない(worsening order を成すが順序比較はコード臭 — matches! で明示的に書く)。variant doc は条件でなく authorization(エンジンが何をすべきか)を語る。

Step 2: src/lib.rs を更新

pub use types::{LiquidationParams, MarginHealth, MarginRatio, MARGIN_SCALE};

レッスン1 の MarginHealth rustdoc warning がここで解消する。

Step 3: コンパイル

cargo build -p openhl-liquidationwarning ゼロ

答え合わせ

cd ~/code/openhl-reference && git checkout 22eedf9
diff -u ~/code/my-openhl/crates/liquidation/src/types.rs ./crates/liquidation/src/types.rs
diff -u ~/code/my-openhl/crates/liquidation/src/lib.rs ./crates/liquidation/src/lib.rs
git checkout main

types.rs は MarginHealth::Underwater まで一致(次の AccountSnapshot/CloseOrderSpec はレッスン3)。lib.rs は compute モジュールと追加 re-export を除いて一致。

合格基準

cargo build -p openhl-liquidation が warning ゼロ。よくあるエラー: E0432(re-export 行の typo)/ ambiguous re-export(既存を拡張せず別行を追加 — すべて 1 つの pub use types::{...} に収める)。

まとめ(3行)

  • MarginRatio(pub i64) は newtype(alias でない)— ゼロコストで本物の型区別を生む。値が「整数」を超えた意味を運ぶときは newtype。
  • MarginHealth は 4 variants = エンジンの 3 下流判断(新規/force-close/insurance 関与)に対応。enum のカーディナリティは許可する action のカーディナリティに揃える。
  • PartialOrd/Ord を derive しない(health > AtRisk はどの worse か言わない)— matches! で明示的に書き、exhaustiveness check を効かせる。比較可能 enum はたいていコード臭。

次のレッスン(レッスン3)

AccountSnapshot(全 margin 関数の入力)と CloseOrderSpec(エンジンが bridge へ渡す出力)を追加し types モジュールを閉じる。funding::Position を再利用せず独自 snapshot を起こす理由を見る。