0%

AMM | 计算原理

很多博文都只是浅尝辄止,或者干脆复制黏贴,也没有考虑手续费的变量。

这里以 pancakeswapgetAmountsOut 为例,探究,如何计算 AMM 价格。


写在前面


经常看到,AMM 的乘积是一个常数,即池子的深度

$$ x * y = k $$

其中,K 是常数。然后,x 增加和 y 减少之后

$$ (x - \Delta{x}) * (y + \Delta{y}) = k $$

其里面有一个前提,所谓的 k 不变,是不存在加池子和撤池子的情况,才是恒定 k。仅仅只是交易的话,k 是恒定的,但是,如果进行加池子或者减少池子,则 K 是变化的。


代码分析


getAmountOut

这里参考的是 pancakeswap。

1
2
3
4
5
6
7
8
9
// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'PancakeLibrary: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'PancakeLibrary: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(998);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}

这里解释一下参数

假设,池子是 A , B 两个 token 组成的,这个时候,你想用 A 来换 B

  • amountIn
    • A 的数量
  • reserveIn
    • 池子中 A 的数量
  • reserveOut
    • 池子中 B 的数量

方法中 998,是因为,pancakeswap 的收取费用是 0.2%

但是,pancakeswap 现在的手续费调整为 0.25%,即上面的 998 要变成 997.5 。但是,看公开的代码,这里的手续费用是写死的,不知道 pancakeswap 是怎么变得。

这里按照代码中的 0.2% 的手续费进行计算。

公式代码如下面所示

$$ amountOut = \frac{amountIn * 998 * reserveOut}{reserveIn * 1000 + amountIn * 998} $$

公式推导

这里直接带手续费进行推导。

$$ reserveIn * reserveOut = (reserveIn + amountIn * (1- f)) * (reserveOut - amountOut) $$
$$ \frac{reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} = (reserveOut - amountOut) $$
$$ amountOut = reserveOut - \frac{reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} $$
$$ amountOut = \frac{reserveOut * (reserveIn + amountIn * (1-f)) - reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} $$
$$ amountOut = \frac{reserveOut * reserveIn + reserveOut * amountIn * (1-f)) - reserveIn * reserveOut}{(reserveIn + amountIn * (1- f))} $$
$$ amountOut = \frac{reserveOut * amountIn * (1-f)}{(reserveIn + amountIn * (1- f))} $$

这里要说一下,为什么 $ (reserveIn + amountIn * (1- f)) $ ,这里的 $ 1 - f $ 是因为,pancakeswap 要拿走一部分的币作为手续费。

getAmountIn

1
2
3
4
5
6
7
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'PancakeLibrary: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'PancakeLibrary: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(998);
amountIn = (numerator / denominator).add(1);
}

结合上面的公式,最后推导为

$$ amountIn = \frac{reserveIn * amountOut}{(reserveOut - amountOut) * (1- f)} $$

细节

这里要特别注意一个经常被忽略的点。

假设一个池子,A50 个,B100 个。

如果,你卖出 50 个 A,只能获得 50B「不考虑手续费」,而获得不了 100B。这就是恒定乘积做市商。也就是滑点。

使用

一般调用上面两个方法都是用下面的用法,下面两个方法的底层就是上面的解析。

1
2
3
4
5
6
7
function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) {
return PancakeLibrary.getAmountsOut(factory, amountIn, path);
}

function getAmountsIn(uint amountOut, address[] memory path) public view override returns (uint[] memory amounts) {
return PancakeLibrary.getAmountsIn(factory, amountOut, path);
}
  • getAmountsOut(10000,[path_A,path_B])
    • 意思是 10000A 能兑换多少个 B
  • getAmountsIn(10000,[path_A,path_B])
    • 意思是 借 10000B 需要多少个 A

自定义计算

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
import math


class AMM:
__AMM = None

def __init__(self):
if AMM.__AMM:
self.get_instance()

@classmethod
def get_instance(cls):
if not cls.__AMM:
cls.__AMM = AMM()
return cls.__AMM

def get_amounts_out(self, reserveIn, reserveOut, amountIn, f):
# reserveIN = A reserveOut = B
# amountIn 为 为池子增加 amountIn 个 A 可以拿出多少个 B
# reserveIN 和 amountIn 是同一币种
a = reserveOut * amountIn * (1 - f)
b = reserveIn + amountIn * (1 - f)
return a / b

def get_amounts_in(self, reserveIn, reserveOut, amountOut, f):
# reserveIN = A reserveOut = B
# amountOut 为借出 amountOut 个 B 需要归还多少个 A
# reserveOut 和 amountOut 是一个币种
if amountOut > reserveOut * 0.7:
return 0
a = reserveIn * amountOut
b = (reserveOut - amountOut) * (1 - f)
c = a / b
return c

经过验证,上面是正确的。

请我喝杯咖啡吧~