---
# System prepended metadata

title: MARGIN

---

# MARGIN

https://pacific-tuck-372.notion.site/Dex-FundingFee-Design-dc43fa7e22db4162b2a45681148ede51
https://pacific-tuck-372.notion.site/Dex-Margin-Design-3f3d004ad37744558eaaf18b219c77ec
https://pacific-tuck-372.notion.site/margin-156450e57e1e44bdac1cdfc1e67a49b9

https://pacific-tuck-372.notion.site/Lite-Paper-34aa321bf48a48b4b5d2c86e12420a34 litepaper

https://pacific-tuck-372.notion.site/4fff7f429b0e4e8589806b95e8d74081 whitehat

https://pacific-tuck-372.notion.site/SSoT-4e68f85e4ffa44fda68bef6b9ce5c936

https://pacific-tuck-372.notion.site/249b552daa1d42819c8e791a1d5ef1fc?v=24f9973fade84b489a90c9bb64c10e66

https://www.notion.so/BANA-Tokenomics-Design-06e8745f492a4294ae2bd689ea1e87fc

https://pacific-tuck-372.notion.site/ApeX-c3dcfd14e8434468897ce5c8d4501cfd

# **margin合约的设计和实现**

### **1. 架构**

作为apeX交易站的一个重要模块，margin在整个协议的角色如图所示，负责管理所有用户的仓位，包括加减保证金、开平仓和清算、更新全局CPF等，它的前端是router（目前开平仓操作仅对config合约中的白名单router开放调用），后端是具有一一绑定关系的Amm。margin的正常运行依赖于Amm中有足够的流动性，以及bot可以对可清算仓位进行及时清算等。

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1227555e-ac9f-4b22-82fc-d9994e5bebaf/Untitled.png)

### **2. 设计**

### **2.1 仓位管理**

用户预先存入保证金后，就可以依据这笔保证金进行杠杆交易。以下简要描述margin的仓位管理过程，以一次eth-vUSD开平仓为例，假设最初amm中流动性对应的token数量分别是：5000eth，10000000vUSD，场内价格2000vUSD/eth。

- trader添加2eth的保证金，然后在2000块的价格开了20000vUSD的多仓，开完仓后margin中的该trader的仓位信息是：eth有2+9.98个，vUSD有-20000个；amm经过swap，剩下4990.02个eth和10020000个vUSD。
- 平仓的过程就是把vUSD绝对值变小：平多仓时，需要卖掉足量eth，填补vUSD负债；平空仓时，需要把vUSD卖掉，填补eth负债。
- 当考虑了fundingFee和风控参数beta的仓位负债率达到100%时，仓位清空且清算后仓位的剩余资产由amm承担

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2791aebe-87e5-433f-bfea-04dae17c9795/Untitled.png)

### **2.2 资金费收支**

为了让AMM中的场内价格紧跟场外的指数价格，设置了资金费用的机制，根据场内和场外的溢价在多空双方进行资金费用的结算，必要时AMM充当仓位少的那一方。考虑到链上无法集中结算所有仓位的资金费用，所以在用户操作自己仓位时，利用累计溢价率（CPF，cumulative premium fraction）的差值进行实时资金费用的结算。

- Premium Fraction (PF) 计算公式
    
    $PF=\dfrac{MarkPrice-IndexPrice}{IndexPrice} * \dfrac{1}{FundingDuration}$
    
- 资金费收支示例

每当有用户操作自己仓位时，会先触发全局CPF的更新，这个更新会影响全部持仓账户的资金费用，然后通过全局CPF和上一次自己操作仓位时用户CPF的差值以及当前持仓大小进行资金费的收支。

以下是资金费用结算的一个简单示例：trader1在全局CPF为0时开10个单位的多仓，在全局CPF为10时平掉所有仓位，需要支付100；trader2在全局CPF为0时开10个单位的多仓，在全局CPF为10时平掉一半仓位，需要支付100（此时需要记录用户CPF为10），然后在全局CPF为-5时平掉剩余仓位，赚了75；trader3在全局CPF为5时开20个单位的空仓，在全局CPF为10时平掉所有仓位，赚了100。

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/481a8426-dec7-4c63-8e95-ec6e3b88acd1/Untitled.png)

真实场景中，为了减少AMM因被动参与资金费结算带来的损失，会对收支系数进行调整（乘以或除以Long/Short），以实现AMM在资金费结算时赚的更多一点、亏的更少一点。

![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/07704985-7d0c-41bc-b941-08a38feda902/Untitled.png)

- 其他说明
    
    > 多头trader资金费用的对手方是空头trader，不足或多出的部分由Amm承担。CPF更新包含两种情况：用户操作仓位时会被动触发全局CPF和该用户的CPF、bot主动触发全局CPF。每次操作仓位时，都需要对总Long或Short进行更新部分平仓时，资金费率是全部结算的，对照收支示例的trader2。
    > 

### **2.3 数据结构**

对应上面的仓位管理过程，需要相应的数据结构，以下是几个关键的变量及其数据结构：traderPositionMap用来记录每个用户的持仓信息和保证金信息；traderCPFMap记录每个用户对应的最后一次结算时CPF，用于计算仓位fundingFee的收支，CPF是一个可正可负的溢价率累积指数。

- traderPositionMap（trader -> position）
    
    > position(包含quoteSize、baseSize、tradeSize）
    > 
    > - quoteSize是quote的数量
    > - baseSize是base的总数量
    > - tradeSize是quoteSize对应的成本，base计价
    > - quoteSize、baseSize都是可正可负。如果quoteSize为正，净持有short仓位；quoteSize为负，净持有long仓位
    > - quoteSize/tradeSize就是当前仓位的平均入场价
    
    1个eth开10倍仓位，现价500，basesize指的是11eth、quotesize指的是550vusd、tradesize指的是10 eth(By arwen)
    
- traderCPFMap（trader -> CPF）

### **3. 实现**

### **3.1 配置项**

设置参数（都是通过config合约来设置，然后在margin合约读取）

- 初始保证金率（跟apeX的最大开仓杠杆有关）
- 清算负债率阈值（仓位的负债率高于此值时，可以被清算，如100%）
    
    > （quote资产或负债的总市值用priceOracle中的AccountSpecificMarkPrice来计价，这样不管做空做多，都通过beta考虑到了quote数量对滑点的影响，为安全清算留出buffer）
    > 
    > 
    > 资产 = 余额为正的资产总市值
    > 负债 = 余额为负的资产总市值
    > 负债率 = 负债 / 资产
    > 
- 清算费率（支付给清算机器人的盈利，清算剩余资产的百分比）
- CPF boost（防止多空严重不平衡时，fundingFee收支太大，设置的边界值）
- beta（风控参数）
- routerMap（可以写margin状态的router列表）

### **3.2 仓位写函数**

- 增加保证金addMargin, 提高保证金率
    
    需要指定增加的base保证金数量以及保证金充值到的账户，简要过程：
    
    - 需要trader先通过router合约将baseToken转入margin合约
    - 检查上一步转入的base数量不少于depositAmount
    - 更新holder的position中baseSize
    - 更新margin合约的reserve值，reserve = _reserve + depositAmount;
    
- 减少保证金removeMargin
    
    需要指定从哪个账户withdraw保证金、需要withdraw到哪个账户以及withdraw的base数量，简要过程：
    
    - 更新fundingFee的全局CPF，用于结算fundingFee.
    - 结算fundingFee。
    - 计算未结盈亏PNL，检查调用者可以withdraw的保证金数量_getWithdrawable（基于用户当前仓位、风控参数beta和初始保证金率）。
    
            1 用markprice计算未结盈亏。
    
            2 计算负债率公式
    
    ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/14afe8f4-4a83-4de4-8ae3-b088ec4317a8/Untitled.png)
    
    - 从trader position的baseSize减少保证金
        - 按照fundingFee、unrealizedPnl、margin的先后顺序进行withdraw（疑问，funding fee， margin提即可， unpnl必须关仓才能提， 感觉这样逻辑会简单点）
            - 如果fundingFee有盈余且盈余超过withdraw数量，就直接提取withdraw，并把盈余剩下部分加到baseSize，作为已实现fundingFee
            - 如果fundingFee无法覆盖withdraw数量且未实现盈亏可以覆盖(withdraw数量-fundingFee)，就将这部分未实现盈亏变为已实现盈亏
            - 如果fundingFee无法覆盖withdraw数量且未实现盈亏无法覆盖(withdraw数量-fundingFee)，就将未实现盈亏变为已实现盈亏，(withdraw数量-fundingFee-unrealizedPnl)由原始保证金承担
        - 提取未实现盈亏时，仓位quoteSize不变，tradeSize变化，最终体现在平均入场价的修改
    - 更新position
    - 更新用户CPF
    - 将保证金从margin合约直接转给to
    
- 开仓openPosition
    
    需要指定为哪个账户开仓、开仓方向（0long，1short）以及开仓的quote数量（已经乘了杠杆），简要过程：
    
    - 更新fundingFee的全局CPF，用于结算fundingFee
    - 检查仓位健康状况，杠杆符合初始保证金率
        1.  如果有仓位的情况下，先模拟平调仓位，获取仓位此时的保证金；
        2. 计算当前池子市价（如果市价跟场外价格偏差10%，或者兑换后价格差10%，以场外价格计算市场价）
        3. 根据市价计算最大可开仓量（根据最大可开仓量公式）。   
        4. 调用amm swap 进行实际开仓
        5. 更新仓位的tradesize    
            - 同向加仓或新开仓，就直接加tradeSize，入场价应该会有变化
            - 反向开小仓，按比例减tradeSize，入场价不变
            - 反向开大仓，tradeSize减为0后，新开反向仓，入场价变为当前开仓价
        6. 更新仓位的basesize,quotesize，以及池子当前总开仓量
        7. 检查用户当前仓位小于最大开仓量
    - 检查负债率是否小于清算线（负债率检查过程补充todo） ，禁止开仓即被清算（todo，是否跟最大开仓量互斥）；
    - 更新position
    - 更新用户CPF
    
- 平仓closePosition
    
    需要指定为哪个账户平仓、以及平仓的quote数量，简要过程：
    
    - 更新fundingFee的全局CPF，用于结算fundingFee
    - 计算负债率，检查是否小于清算线
    - 与amm进行swap平仓进行实际关仓
    - 更新position
    - 更新用户CPF
    - 如果用户平完有坏账，由amm承担
    
- 清算liquidate
    
    需要指定清算哪个账户以及清算奖励（base）的接收地址，简要过程：
    
    - 更新fundingFee的全局CPF，用于结算fundingFee
    - 计算负债率是否小于清算线 ，检查是否可以清算
    - 根据仓位quoteSize和amm的swap函数，计算清算后对应的base资产数量
    - 如果场内和oracle价差过大，就用oracle价格进行settle，然后forceSwap剩余的资产（盈利或亏损）到amm；如果价差不大，就直接将被清算仓位forceSwap到amm
    - 如果仓位清算完，有剩余资金，就将清算bonus转给to地址，剩下的转到treasury或amm，如果没有剩余资金，则无清算奖励
    - 清除仓位
    - 更新用户CPF
    
- 更新全局CPF updateCPF
    
    任何人都可以主动更新，也可以由用户操作仓位时被动更新，简要过程：
    
    - 从预言机获取最新的PF，并根据Long和Short以及时长计算出deltaPF
    - 累积deltaPF到全局CPF，完成更新
    
- baseToken的deposit和withdraw

amm将流动性对应的baseToken转入转出margin合约

### **3.3 仓位读函数**

- 查询最新的CPF getNewLatestCPF：根据历史CPF和价差率*时长，计算实时的CPF，用于计算fundingFee
- 查询可以减少的base保证金数量 getWithdrawable：基于最大开仓杠杆和风控参数beta，算出quoteSize对应的base资产，然后用baseSize减掉
- 查询用户最新的资金费 calFundingFee：根据用户仓位和最新的CPF，计算fundingFee的收入或亏损
- 查询用户负债率 calDebtRatio
- 查询是否可以清算 canLiquidate：计算负债率是否超过清算负债率阈值，涉及到fundingFee和AccountSpecificMarkPrice
- 查询用户未实现盈亏 calUnrealizedPnl：如果场内和oracle价差过大，就用oracle价格进行模拟平仓结算；如果价差不大，就直接以场内价格进行模拟平仓结算。

       把仓位剩余的quotesize平掉得到baseAmount，然后用仓位tradeSize减去baseAmount

### **3.4 其他说明**

- 仓位有四种情形：有盈利、有亏损但未至清算、可清算且无坏账、可清算且有坏账
- margin合约可以处理base资产（如eth）和虚拟的quote资产（如vUSD）
- 仓位以全仓模式进行管理，非逐仓模式
- 清算情况下，没有部分平仓，要平就全部平掉；普通平仓可以部分平仓
- 不设立保险基金，清算导致的赔付和盈余由amm负责
- 触发清算的价格是场内twap标记价格（AccountSpecificMarkPrice），实际清算时直接forceSwap场外价格结算的剩余资金或场内价格计算的仓位
- 清算机器人通过graph监控链上仓位，对不健康的仓位进行清算