永续合约 02 - 保证金管理与清算引擎

本文介绍永续合约的保证金管理和清算机制. 从全仓与逐仓保证金的区别讲起, 推导清算价格公式, 然后介绍保险基金不够赔付时 ADL (Auto-Deleveraging) 的介入流程, 附 Solidity 和 Go 的实现示例.

一、术语表

1.1 保证金

术语 英文 含义
保证金 Margin / Collateral 开仓时抵押的资金, 作为履约担保
初始保证金 Initial Margin (IM) 开仓所需的最低保证金, IM = position_size / leverage
维持保证金 Maintenance Margin (MM) 持仓所需的最低保证金, MM = position_size × MMR
维持保证金率 Maintenance Margin Rate (MMR) 协议规定的最低保证金比例, 通常 0.5%~5%
保证金率 Margin Ratio margin / position_size, 低于 MMR 时触发清算
全仓保证金 Cross Margin 账户所有可用余额共同担保所有仓位
逐仓保证金 Isolated Margin 每个仓位独立分配保证金, 亏损不影响其他仓位

1.2 清算

术语 英文 含义
清算 Liquidation 保证金率跌至 MMR 时, 协议强制平仓
清算价格 Liquidation Price 触发清算的标记价格
穿仓 Bankruptcy / Negative Equity 清算后仍然亏损, 保证金不足以覆盖损失
清算人 Liquidator / Keeper 执行清算操作的链上角色, 任何人都可以做
清算罚金 Liquidation Penalty / Fee 被清算者支付的额外费用, 作为 Keeper 奖励
部分清算 Partial Liquidation 只平掉一部分仓位, 使保证金率恢复到安全水平

1.3 ADL 与保险基金

术语 英文 含义
自动减仓 Auto-Deleveraging (ADL) 保险基金不足时, 强制减少盈利方的仓位来覆盖穿仓损失
保险基金 Insurance Fund 协议储备金, 用于覆盖穿仓损失
社会化亏损 Socialized Loss 穿仓损失由所有交易者按比例分摊 (ADL 的替代方案, 体验更差)

1.4 易混淆: “维持保证金率” vs “资金费率”

这两个名词都带 “率”, 但完全不是一回事:

维持保证金率 (MMR) 资金费率 (Funding Rate)
英文 Maintenance Margin Rate Funding Rate
是什么 协议规定的最低保证金比例 多空双方之间的周期性费用
谁定的 协议固定 (0.5%~5%), 和行情无关 市场动态计算, 随多空比例变化
作用 决定何时被清算 让合约价格锚定现货价格
触发时机 每次标记价格变动时持续检查 按固定周期结算 (CEX 8h, HL/dYdX 1h)
方向 无方向, 是一个阈值 有方向: 正=多头付空头, 负=空头付多头
对保证金的影响 跌破 → 强制清算 扣减或增加保证金余额

一句话区分: MMR 决定你 “什么时候爆仓”, Funding Rate 决定你 “持仓要付多少钱”.

资金费率的计算公式和验证代码见永续合约机制详解 §7.3, 链上数据获取见永续合约数据解析实战.


二、保证金类型

2.1 Cross Margin vs Isolated Margin

Cross Margin vs Isolated Margin Cross Margin (全仓) Account Balance: $10,000 共享保证金池 ETH Long 10x Size: $5,000 PnL: -$800 BTC Short 5x Size: $3,000 PnL: +$500 + 资金利用率高, 盈利仓保护亏损仓 + 清算价更远, 不易被清算 - 一个仓位爆仓可能拖垮整个账户 - 风险隔离差 effective_margin = balance + Σ(unrealized_pnl) = $10,000 + (-$800) + $500 = $9,700 BTC 的盈利帮 ETH 的亏损 "扛" 着 Isolated Margin (逐仓) Account Balance: $10,000 独立分配 ETH Long 10x Margin: $500 最多亏 $500 BTC Short 5x Margin: $600 最多亏 $600 剩余可用: $10,000 - $500 - $600 = $8,900 + 风险隔离, 最多亏掉该仓位的 margin + 适合高风险交易 (限制最大亏损) - 资金利用率低, 盈利仓不能帮亏损仓 - 清算价更近, 容易被清算 ETH 被清算 → 亏 $500, BTC 和剩余 $8,900 不受影响
维度 Cross Margin (全仓) Isolated Margin (逐仓)
保证金来源 账户全部可用余额 仅该仓位分配的保证金
最大亏损 整个账户余额 该仓位的 margin
清算价距离 远 (余额越多越远) 近 (margin 固定)
风险隔离 无, 一个仓爆全账户遭殃 有, 互不影响
适用场景 对冲组合, 低杠杆长持 高杠杆投机, 控制最大亏损

2.2 保证金数学

三个核心公式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Initial Margin (初始保证金):
IM = position_size (仓位名义价值) / leverage (杠杆倍数)
例: 10x 杠杆, $5000 仓位 → IM = $5000 / 10 = $500

Maintenance Margin (维持保证金):
MM = position_size × MMR (维持保证金率)
例: MMR = 0.5%, $5000 仓位 → MM = $5000 × 0.005 = $25

Maintenance Margin Rate (维持保证金率, MMR): ← 固定阈值, 协议规定
例: MMR = 0.5%, 不随价格变化

Margin Ratio (保证金率): ← 动态值, 随价格实时变化
margin_ratio = margin (当前保证金) / position_size
例: $500 margin, $5000 仓位 → ratio = 500 / 5000 = 10%

清算条件: margin <= MM
即: 当前保证金 <= 维持保证金 → 触发清算
等价于 margin_ratio <= MMR, 但工程上用绝对值比较, 避免除法精度损失

保证金率 vs 维持保证金率: 名字只差两个字, 但本质不同.
保证金率 (Margin Ratio) 是仓位的 “体温”, 实时变化;
维持保证金率 (MMR) 是 “发烧标准”, 协议固定.
体温降到标准以下 → 清算.

IM vs MM 的关系: IM 是 “开仓门槛”, MM 是 “清算门槛”.
IM 总是 > MM, 中间的差额就是你的 “安全缓冲区”.
例: 10x 杠杆, IM = 10%, MM = 0.5% → 从开仓到清算, 你有 9.5% 的缓冲.

保证金层级: IM → MM → 清算 Initial Margin = 10% (开仓时) MM 0.5% 安全缓冲区 = IM - MM = 9.5% 价格不利变动 9.5% 后触发清算 LIQUIDATION WARNING ZONE SAFE

三、清算机制详解

3.1 清算触发条件

1
2
3
4
5
6
7
8
9
10
11
12
清算触发:
margin (当前保证金) <= MM (维持保证金)

其中:
margin = initial_margin (初始保证金) + unrealized_pnl (未实现盈亏)
MM = position_size (仓位名义价值) × MMR (维持保证金率)

展开:
initial_margin + unrealized_pnl <= position_size × MMR

注: 数学上等价于 margin_ratio <= MMR (两边同除 position_size),
但实际引擎中用 margin <= MM (只有乘法, 无除法精度损失).

3.2 清算价格公式推导

Long Position (做多) 清算价格推导:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
已知:
- entry_price (开仓价格)
- margin (初始保证金)
- position_size (仓位名义价值, 以 USD 计)
- MM (维持保证金) = position_size × MMR (维持保证金率)
- quantity (持仓数量, 以 token 计) = position_size / entry_price

: 这里用 USDT 本位 (linear) 合约推导. 大多数协议的 PnL (盈亏) 公式:
PnL = (exit_price (平仓价格) - entry_price) × quantity
= (exit_price - entry_price) × (position_size / entry_price)
= (exit_price - entry_price) / entry_price × position_size
两种写法等价, 下面使用后者 (方便用 position_size 直接计算).

当 mark_price (标记价格) 跌到 liq_price (清算价格) 时:
unrealized_pnl (未实现盈亏) = (liq_price - entry_price) / entry_price × position_size
↑ 做多, 价格跌了亏钱 (负数)

触发条件: margin + unrealized_pnl = MM

代入:
margin + (liq_price - entry_price) / entry_price × position_size = MM

解 liq_price:
(liq_price - entry_price) / entry_price × position_size = MM - margin
liq_price - entry_price = (MM - margin) × entry_price / position_size
liq_price = entry_price + (MM - margin) × entry_price / position_size
liq_price = entry_price × (1 + (MM - margin) / position_size)
liq_price = entry_price × (1 - (margin - MM) / position_size)

Short Position (做空) 清算价格推导:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mark_price (标记价格) 涨到 liq_price (清算价格) 时:
unrealized_pnl (未实现盈亏) = (entry_price (开仓价格) - liq_price) / entry_price × position_size (仓位名义价值)
↑ 做空, 价格涨了亏钱 (负数)

触发条件: margin (当前保证金) + unrealized_pnl = MM (维持保证金)

代入:
margin + (entry_price - liq_price) / entry_price × position_size = MM

解 liq_price:
(entry_price - liq_price) / entry_price × position_size = MM - margin
entry_price - liq_price = (MM - margin) × entry_price / position_size
liq_price = entry_price - (MM - margin) × entry_price / position_size
liq_price = entry_price × (1 - (MM - margin) / position_size)
liq_price = entry_price × (1 + (margin - MM) / position_size)

总结:

1
2
Long:  liq_price (清算价) = entry_price (开仓价) × (1 - (margin (保证金) - MM (维持保证金)) / position_size (仓位名义价值))
Short: liq_price = entry_price × (1 + (margin - MM) / position_size)

3.3 实际数字例子

清算价格计算实例: ETH Long 10x 开仓参数 entry_price = $3,000 | leverage = 10x | margin = $3,000 | position_size = $30,000 MMR = 0.5% | MM = $30,000 × 0.005 = $150 Long 清算价 liq_price = $3,000 × (1 - ($3,000 - $150) / $30,000) = $3,000 × (1 - 0.095) = $3,000 × 0.905 = $2,715 Price → $2,715 LIQUIDATION $3,000 ENTRY $285 = 9.5% 下跌空间 同样参数, Short 清算价 liq_price = $3,000 × (1 + ($3,000 - $150) / $30,000) = $3,000 × 1.095 = $3,285 做空时, 价格涨 9.5% 到 $3,285 触发清算 注意: 9.5% ≈ (1/leverage - MMR) = (1/10 - 0.5%) = 9.5% ← 杠杆越高, 缓冲越小

不同杠杆下的清算距离 (MMR = 0.5%):

杠杆 IM Rate 缓冲 = IM - MM Long 清算价 (entry=$3000) Short 清算价
2x 50% 49.5% $1,515 $4,485
5x 20% 19.5% $2,415 $3,585
10x 10% 9.5% $2,715 $3,285
20x 5% 4.5% $2,865 $3,135
50x 2% 1.5% $2,955 $3,045
100x 1% 0.5% $2,985 $3,015

100x 杠杆: 价格只要反向波动 0.5% 就会被清算. ETH 的日内波动通常 3%~10%, 100x 基本是送钱.

3.4 部分清算 vs 全部清算

大多数 DeFi 协议采用 部分清算 (Partial Liquidation) 策略:

部分清算 vs 全部清算 Partial Liquidation (部分清算) 原始仓位: $30,000 (margin $3,000) 触发清算 → 平掉 25%~50% 剩余仓位: $15,000 (margin 恢复安全) + 减少市场冲击 (不用一次性卖出全部) + 交易者保留部分仓位 + 降低连环清算风险 代表: dYdX, GMX v2, Hyperliquid 通常分多步清算, 每次 25%~50% Full Liquidation (全部清算) 原始仓位: $30,000 (margin $3,000) 触发清算 → 全部平仓 剩余仓位: $0 (margin 全部没收) + 实现简单, 一步到位 - 市场冲击大 - 大仓位清算容易引发连锁反应 - 对交易者不友好 代表: 早期 CEX, 部分简单 DeFi 协议

3.5 清算人 (Liquidator / Keeper)

链上清算不是协议自动执行的, 需要外部参与者 (Keeper) 主动调用清算函数:

1
2
3
4
5
6
7
8
清算流程:
1. Keeper (清算人) 监控所有 position (仓位) 的保证金水平
2. 发现 margin (当前保证金) <= MM (维持保证金) 的仓位
3. 调用协议的 liquidate() 函数
4. 协议验证该仓位确实满足清算条件
5. 强制平仓, 扣除 liquidation fee (清算罚金)
6. Keeper 获得 liquidation reward (清算奖励, 一部分 fee)
7. 剩余保证金 (如果有) 归入 insurance fund (保险基金)

为什么用 Keeper 而不是协议自动清算?
链上合约不能 “主动” 执行, 需要有人发交易触发. Keeper 网络是 “anyone can liquidate” 的设计, 经济激励驱动.


四、ADL (Auto-Deleveraging, 自动减仓)

4.1 什么时候需要 ADL?

穿仓 → 保险基金 → ADL 触发链 正常清算 margin_ratio <= MMR Keeper 清算, 保证金足够 剩余 margin → Insurance Fund 穿仓 (Bankruptcy) 价格剧烈波动, 清算不及时 保证金不够覆盖亏损 negative equity (负资产) 保险基金介入 Insurance Fund 补亏 基金充足 → 问题解决 ✓ 基金不足 → 触发 ADL ↓ 替代方案: 社会化亏损 穿仓损失由所有持仓者按比例分摊 - 所有人都受损, 体验极差 - 大户可以故意穿仓薅散户 - 几乎没有协议还在用 ADL 比社会化亏损更公平: 只减仓 "赚得最多+杠杆最高" 的人 ADL (自动减仓) 1. 按 "盈利率 × 杠杆" 排序所有对手方 2. 盈利最多 + 杠杆最高的先被减仓 3. 减仓量 = 穿仓仓位的 size 4. 对手方按 mark price 强制减仓 已实现盈利部分得以保留 ADL 是 "最后手段", 对盈利方不友好, 但好过社会化亏损

4.2 ADL 排序算法

1
2
3
4
5
6
7
8
9
10
11
ADL Priority Score (ADL 优先级分数) = PnL_percentage (盈亏百分比) × leverage (杠杆倍数)

其中:
PnL_percentage = unrealized_pnl (未实现盈亏) / margin (保证金)
leverage = position_size (仓位名义价值) / margin

例: 两个 long 对手方
A: margin=$1000, PnL=+$500, size=$10000 → score = (500/1000) × (10000/1000) = 0.5 × 10 = 5.0
B: margin=$2000, PnL=+$300, size=$6000 → score = (300/2000) × (6000/2000) = 0.15 × 3 = 0.45

A 的 priority score = 5.0 >> B 的 0.45, 所以 A 先被 ADL.

为什么 ADL 瞄准高盈利+高杠杆?
高盈利说明这个人赚了很多 (有 “余粮” 可以减), 高杠杆说明风险偏好高.
直觉上: 你赚得多且冒的险大, 在极端行情下 “让” 一部分利润给系统是合理的.


五、保险基金 (Insurance Fund)

5.1 资金来源

1
2
3
4
5
6
7
8
9
10
11
12
Insurance Fund (保险基金) 资金流入:
1. 清算剩余保证金
→ 被清算仓位的 margin (保证金) 扣除亏损和 liquidation fee (清算罚金) 后, 剩余部分归入 fund
→ 例: margin=$500, loss=$400, liq_fee=$50 → fund += $50

2. 协议手续费注入
→ 部分交易手续费 (如 10%~50%) 定期注入 insurance fund
→ 例: daily trading fee = $100k, 20% → $20k/day 注入

3. 初始注资
→ 协议上线时, 团队/DAO 注入初始资金
→ 例: GMX v1 insurance fund 初始注入 $2M

5.2 资金用途

1
2
3
4
5
6
7
8
Insurance Fund (保险基金) 资金流出:
1. 覆盖穿仓损失
→ 被清算仓位的保证金不足以覆盖亏损
→ deficit (亏空) = abs(remaining_margin (剩余保证金)) // negative = 穿仓
→ fund -= deficit

2. 如果 fund 耗尽
→ 触发 ADL (详见上节)

5.3 链上实现

保险基金通常是一个独立的合约地址, 持有 USDC/USDT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/// @title InsuranceFund - 保险基金合约
/// @notice 接收清算剩余保证金, 覆盖穿仓损失
contract InsuranceFund is Ownable {
IERC20 public immutable usdc;

// 授权的清算引擎合约
mapping(address => bool) public isLiquidationEngine;

event Deposit(address indexed from, uint256 amount, string reason);
event Withdraw(address indexed to, uint256 amount, string reason);

constructor(address _usdc) Ownable(msg.sender) {
usdc = IERC20(_usdc);
}

modifier onlyLiquidationEngine() {
require(isLiquidationEngine[msg.sender], "not authorized");
_;
}

/// @notice 清算引擎存入剩余保证金
function depositFromLiquidation(uint256 amount) external onlyLiquidationEngine {
usdc.transferFrom(msg.sender, address(this), amount);
emit Deposit(msg.sender, amount, "liquidation_surplus");
}

/// @notice 清算引擎提取资金覆盖穿仓
function coverBankruptcy(uint256 deficit) external onlyLiquidationEngine returns (bool covered) {
uint256 balance = usdc.balanceOf(address(this));
if (balance >= deficit) {
usdc.transfer(msg.sender, deficit);
emit Withdraw(msg.sender, deficit, "bankruptcy_coverage");
return true; // 完全覆盖
}
// 基金不足, 全部转出, 剩余需要 ADL
if (balance > 0) {
usdc.transfer(msg.sender, balance);
emit Withdraw(msg.sender, balance, "partial_bankruptcy_coverage");
}
return false; // 未完全覆盖 → 触发 ADL
}

function setLiquidationEngine(address engine, bool authorized) external onlyOwner {
isLiquidationEngine[engine] = authorized;
}
}

六、链上清算的实现

6.1 Solidity: 清算函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title PerpLiquidation - 清算引擎伪代码
/// @notice 演示链上清算的核心逻辑
contract PerpLiquidation {
struct Position {
address trader;
bool isLong;
uint256 size; // position size (仓位名义价值) in USD (6 decimals)
uint256 entryPrice; // entry price (开仓价格) (6 decimals)
uint256 margin; // collateral amount (保证金) (6 decimals)
}

uint256 public constant MMR = 50; // 0.5% = 50 basis points (维持保证金率)
uint256 public constant BPS = 10_000; // basis points denominator (基点分母)
uint256 public constant LIQ_FEE_BPS = 100; // 1% liquidation fee (清算罚金率)
uint256 public constant PRICE_DECIMALS = 1e6;

mapping(bytes32 => Position) public positions;
address public insuranceFund; // 保险基金合约地址
address public oracle; // 预言机合约地址

event PositionLiquidated(
bytes32 indexed positionId,
address indexed trader,
address indexed liquidator, // 清算人
uint256 markPrice, // 标记价格
uint256 marginRemaining, // 剩余保证金
uint256 liquidatorReward // 清算人奖励
);

/// @notice 任何人都可以调用清算函数 (Keeper)
/// @param positionId 被清算仓位的 ID
function liquidate(bytes32 positionId) external {
Position storage pos = positions[positionId];
require(pos.size > 0, "position does not exist");

uint256 markPrice = getMarkPrice(); // 标记价格

// 1. 计算未实现盈亏 (unrealized PnL)
int256 pnl = _calcUnrealizedPnL(pos, markPrice);

// 2. 判断是否可清算: margin <= MM (用乘法避免除法精度损失)
int256 currentMargin = int256(pos.margin) + pnl; // 当前保证金 = 初始保证金 + 未实现盈亏
uint256 maintenanceMargin = (pos.size * MMR) / BPS; // MM = size × MMR
require(currentMargin <= 0 || uint256(currentMargin) <= maintenanceMargin,
"position is healthy");

// 3. 计算清算费用 (liquidation fee)
uint256 liqFee = (pos.size * LIQ_FEE_BPS) / BPS; // 清算罚金
uint256 liquidatorReward = liqFee / 2; // 清算人奖励, 一半给 Keeper
uint256 protocolFee = liqFee - liquidatorReward; // 协议收入

// 4. 结算 (settlement)
uint256 marginRemaining = 0; // 剩余保证金
if (currentMargin > int256(liqFee)) {
marginRemaining = uint256(currentMargin) - liqFee;
// 剩余保证金 → 保险基金
_transferToInsuranceFund(marginRemaining);
} else if (currentMargin < 0) {
// 穿仓 → 从保险基金提取
_coverFromInsuranceFund(uint256(-currentMargin));
} else {
// 0 <= currentMargin <= liqFee: margin 不足以支付全部罚金
// 剩余 margin 全部作为清算奖励, 保险基金不收不付
}

// 5. 支付 Keeper 奖励
_transferToLiquidator(msg.sender, liquidatorReward);

// 6. 删除仓位
emit PositionLiquidated(positionId, pos.trader, msg.sender, markPrice, marginRemaining, liquidatorReward);
delete positions[positionId];
}

function _calcUnrealizedPnL(Position storage pos, uint256 markPrice) internal view returns (int256) {
if (pos.isLong) {
// Long PnL = (markPrice - entryPrice) / entryPrice * size
return int256(pos.size) * (int256(markPrice) - int256(pos.entryPrice)) / int256(pos.entryPrice);
} else {
// Short PnL = (entryPrice - markPrice) / entryPrice * size
return int256(pos.size) * (int256(pos.entryPrice) - int256(markPrice)) / int256(pos.entryPrice);
}
}

function _maintenanceMargin(uint256 size) internal pure returns (uint256) {
return (size * MMR) / BPS; // MM = size × MMR, 只有乘法和一次整除
}

function getMarkPrice() public view returns (uint256) {
// 从 Oracle 获取价格 (简化)
// 实际使用 Chainlink / Pyth / 自建 Oracle
return 0; // placeholder
}

function _transferToInsuranceFund(uint256 amount) internal { /* 转入保险基金 */ }
function _coverFromInsuranceFund(uint256 deficit) internal { /* 从保险基金覆盖亏空 */ }
function _transferToLiquidator(address liquidator, uint256 reward) internal { /* 支付清算人奖励 */ }
}

6.2 Go: 清算监控 Bot (Keeper)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package main

import (
"fmt"
"math"
)

// Position represents an on-chain perpetual position (链上永续合约仓位).
type Position struct {
ID string
Trader string
IsLong bool
Size float64 // position size in USD (仓位名义价值)
EntryPrice float64 // entry price (开仓价格)
Margin float64 // collateral (保证金)
}

// LiquidationParams holds protocol-level liquidation parameters (清算参数).
type LiquidationParams struct {
MMR float64 // maintenance margin rate (维持保证金率, e.g. 0.005 = 0.5%)
LiqFeeRate float64 // liquidation fee rate (清算罚金率, e.g. 0.01 = 1%)
KeeperReward float64 // keeper's share of liq fee (清算人奖励比例, e.g. 0.5 = 50%)
}

// CalcUnrealizedPnL computes unrealized PnL (未实现盈亏) at the given mark price (标记价格).
func CalcUnrealizedPnL(pos Position, markPrice float64) float64 {
if pos.IsLong {
return pos.Size * (markPrice - pos.EntryPrice) / pos.EntryPrice
}
return pos.Size * (pos.EntryPrice - markPrice) / pos.EntryPrice
}

// CalcMarginRatio returns current margin ratio (当前保证金率).
func CalcMarginRatio(pos Position, markPrice float64) float64 {
pnl := CalcUnrealizedPnL(pos, markPrice)
currentMargin := pos.Margin + pnl // 当前保证金 = 初始保证金 + 未实现盈亏
if currentMargin <= 0 {
return 0
}
return currentMargin / pos.Size
}

// CalcLiquidationPrice derives the liquidation price (清算价格) for a position.
func CalcLiquidationPrice(pos Position, mmr float64) float64 {
mm := pos.Size * mmr // mm: 维持保证金
buffer := (pos.Margin - mm) / pos.Size // buffer: 安全缓冲比例
if pos.IsLong {
return pos.EntryPrice * (1 - buffer)
}
return pos.EntryPrice * (1 + buffer)
}

// IsLiquidatable checks if a position should be liquidated (是否应被清算).
// 用 margin <= MM (乘法) 而非 margin_ratio <= MMR (除法), 避免精度损失.
func IsLiquidatable(pos Position, markPrice float64, params LiquidationParams) bool {
currentMargin := pos.Margin + CalcUnrealizedPnL(pos, markPrice) // 当前保证金
mm := pos.Size * params.MMR // 维持保证金
return currentMargin <= mm
}

// CalcADLPriority computes the ADL priority score (ADL 优先级分数).
// Higher score = gets ADL'd first (分数越高, 越先被自动减仓).
func CalcADLPriority(pos Position, markPrice float64) float64 {
pnl := CalcUnrealizedPnL(pos, markPrice)
if pnl <= 0 {
return 0 // 亏损仓位不会被 ADL
}
pnlPct := pnl / pos.Margin // 盈亏百分比
leverage := pos.Size / pos.Margin // 杠杆倍数
return pnlPct * leverage
}

func main() {
params := LiquidationParams{
MMR: 0.005, // 0.5%
LiqFeeRate: 0.01, // 1%
KeeperReward: 0.5, // 50%
}

pos := Position{
ID: "pos-001",
Trader: "0xAlice",
IsLong: true,
Size: 30_000, // $30,000 position
EntryPrice: 3_000, // ETH @ $3,000
Margin: 3_000, // $3,000 margin (10x leverage)
}

// 计算清算价格
liqPrice := CalcLiquidationPrice(pos, params.MMR)
fmt.Printf("=== Position: %s ===\n", pos.ID)
fmt.Printf("Direction: %s\n", map[bool]string{true: "Long", false: "Short"}[pos.IsLong])
fmt.Printf("Size: $%.0f\n", pos.Size)
fmt.Printf("Entry Price: $%.0f\n", pos.EntryPrice)
fmt.Printf("Margin: $%.0f (%.0fx leverage)\n", pos.Margin, pos.Size/pos.Margin)
fmt.Printf("Liquidation Price: $%.2f\n", liqPrice)
fmt.Printf("Buffer: %.2f%%\n\n", math.Abs(liqPrice-pos.EntryPrice)/pos.EntryPrice*100)

// 模拟不同价格下的 margin ratio
testPrices := []float64{3000, 2900, 2800, 2750, 2715, 2700}
fmt.Println("Mark Price | Margin Ratio | Liquidatable?")
fmt.Println("-----------|-------------|---------------")
for _, price := range testPrices {
ratio := CalcMarginRatio(pos, price)
liquidatable := IsLiquidatable(pos, price, params)
status := "Safe"
if liquidatable {
status = "** LIQUIDATE **"
}
fmt.Printf("$%-9.0f | %10.2f%% | %s\n", price, ratio*100, status)
}

// ADL priority 示例
fmt.Println("\n=== ADL Priority Ranking ===")
counterparties := []Position{
{ID: "cp-A", IsLong: false, Size: 10_000, EntryPrice: 2_800, Margin: 1_000},
{ID: "cp-B", IsLong: false, Size: 6_000, EntryPrice: 2_900, Margin: 2_000},
{ID: "cp-C", IsLong: false, Size: 20_000, EntryPrice: 2_700, Margin: 5_000},
}
adlPrice := 2_600.0 // 价格跌了, short 方盈利
fmt.Printf("Mark Price: $%.0f\n", adlPrice)
fmt.Println("ID | PnL | Leverage | ADL Score")
fmt.Println("------|-----------|----------|----------")
for _, cp := range counterparties {
pnl := CalcUnrealizedPnL(cp, adlPrice)
lev := cp.Size / cp.Margin
score := CalcADLPriority(cp, adlPrice)
fmt.Printf("%-5s | $%7.0f | %6.1fx | %.2f\n", cp.ID, pnl, lev, score)
}
}

运行输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
=== Position: pos-001 ===
Direction: Long
Size: $30000
Entry Price: $3000
Margin: $3000 (10x leverage)
Liquidation Price: $2715.00
Buffer: 9.50%

Mark Price | Margin Ratio | Liquidatable?
-----------|-------------|---------------
$3000 | 10.00% | Safe
$2900 | 6.67% | Safe
$2800 | 3.33% | Safe
$2750 | 1.67% | Safe
$2715 | 0.50% | ** LIQUIDATE **
$2700 | 0.00% | ** LIQUIDATE **

=== ADL Priority Ranking ===
Mark Price: $2600
ID | PnL | Leverage | ADL Score
------|-----------|----------|----------
cp-A | $ 714 | 10.0x | 7.14
cp-B | $ 621 | 3.0x | 0.93
cp-C | $ 741 | 4.0x | 0.59

6.3 MEV 与清算

清算在链上是公开的, 存在 MEV 问题:

1
2
3
4
5
6
7
8
9
10
清算 MEV 攻击路径:
1. Keeper A 发现可清算仓位, 提交 liquidate() tx
2. MEV searcher 在 mempool 看到这笔 tx
3. Searcher 提交更高 gas 的 liquidate() tx (front-running)
4. Searcher 获得 liquidator reward (清算人奖励), 原始 Keeper A 被抢

对策:
- 私有 mempool (Flashbots Protect): tx 不经过公共 mempool
- 链上分配机制: 多个 Keeper 按轮次/随机分配清算权
- 时间窗口: 给第一个提交者优先权 (first-come-first-served + 时间锁)

详见永续合约中的 MEV: 清算 MEV 是永续协议 MEV 的主要来源之一, 与 sandwich attack, oracle extraction 并列.


七、各协议保证金对比

维度 GMX (v2) dYdX (v4) Hyperliquid
保证金模式 Isolated only Cross margin Cross + Isolated
清算类型 全部清算 部分清算 部分清算
Mark Price Chainlink oracle 自建 oracle (验证者共识) 验证者中位数 oracle
MMR 1% (大部分币对) 3%~5% (按层级) 动态, 按仓位大小递增
清算费 ~1% position size ~1% position size ~0.5%~1%
保险基金 GLP 池兜底 + protocol fee 独立 insurance fund 独立 insurance fund
ADL 有, GLP 池亏损时触发 有, insurance fund 耗尽时触发 有, 按 priority score 排序
Keeper Chainlink Keepers 验证者内置 验证者内置
特点 简单直观, oracle 依赖强 专业级, 支持组合保证金 速度快, 支持两种模式切换

GMX 的特殊之处: GMX 没有传统的保险基金, 而是用 GLP/GM 流动性池作为对手方.
当交易者亏损, 钱流入池子 (LP 赚钱); 当交易者盈利, 钱从池子流出 (LP 亏钱).
极端情况下, 池子亏损过大 = 变相的 “社会化亏损” 给 LP.


八、小结

概念 一句话
Initial Margin (IM) 开仓门槛, IM = size / leverage
Maintenance Margin (MM) 清算门槛, MM = size × MMR
Cross Margin 全仓共享余额, 资金效率高但风险不隔离
Isolated Margin 逐仓独立保证金, 最大亏损可控
清算 margin <= MM 时 Keeper 强制平仓
清算价 Long: entry × (1 - (margin - MM) / size), Short 反过来
ADL 保险基金不足时, 减仓盈利最多+杠杆最高的对手方
Insurance Fund 清算剩余 + 手续费注入, 覆盖穿仓损失
清算 MEV front-running 清算交易, 抢夺 Keeper 奖励

8.1 下一步

  • GMX 协议深度解析: Oracle 型永续的具体实现, GM 池运作, LP 的风险与收益
  • 永续合约中的 MEV: 清算 MEV, oracle extraction, 公平排序

九、参考


永续合约 02 - 保证金管理与清算引擎
https://mritd.com/2025/07/28/perp-margin-and-liquidation/
作者
Kovacs
发布于
2025年7月28日
许可协议