本文解释了Uniswap V3协议中如何将sqrtPriceX96转换为tick,以及如何从tick转换回sqrtPriceX96,讨论了相关的数学公式和代码实现,以及如何在Python中进行计算,最后通过练习,帮助读者理解如何在实际的Uniswap V3池中进行这些转换。
在之前的章节中,我们看到协议存储的是价格的平方根而不是价格本身。因此,有必要将 tick 与固定点 Q64.96 格式的价格平方根联系起来。
在本章中,我们将探讨如何在 sqrtPriceX96
和 tick 之间进行转换。
一个 tick 是一个离散的价格,由公式 p(i)=1.0001i 给出,其中 i 称为 tick 索引或简称为 tick。有了 tick 索引,就可以唯一地确定 tick 价格,反之亦然。由于存储 tick 索引 i 比存储 p(i) 占用的位数更少,因此协议存储 tick 索引而不是 tick 价格。
在本书中,我们将交替使用术语 tick (价格)和 tick 索引。在代码库中,名为 tick 的变量始终指 tick 索引 i。
sqrtPriceX96
首先,我们回顾价格平方根与变量 sqrtPriceX96
之间的关系:
(sqrtPriceX96296)=p
因此,价格和 sqrtPriceX96
之间的关系是通过取两边的平方来给出的。
(sqrtPriceX96296)2=p
由于价格和 tick 索引之间的关系是 p=1.0001i,我们有
(sqrtPriceX96296)2=1.0001i
为了根据 sqrtPriceX96
确定 tick 索引 i,我们取上述等式两边的对数。
log((sqrtPriceX96296)2)=log(1.0001i)2log(sqrtPriceX96296)=ilog(1.0001)used thatlog(ab)=blog(a)i=2log(sqrtPriceX96296)log(1.0001)divided both sides by log(1.0001)
对数总是相对于一个底数而言。例如,如果 ba=c,则 logbc=a(log 以 b 为底)。但是,上面等式中的对数可以取任何底数。原因将在本章的最后一节中解释。
实际上,上述公式并不完全正确,原因之一是:sqrtPriceX96
是一个连续值,而 tick 索引 i
是离散的(一个整数)。因此,该值需要向下舍入。
这可以在下面的插图中看到,我们使用了一个可以在我们关于 tick 的文章中找到的工具。请注意,虽然价格不断变化,但该价格对应的 tick 始终是紧随其后的一个。
https://img.learnblockchain.cn/2025/05/07/lower-tick-anim.mp4
因此,tick 的准确公式是:
i=⌊2log(sqrtPriceX96296)log(1.0001)⌋
其中符号 ⌊…⌋ 表示向下舍入,例如,⌊3.14⌋=3。
sqrtPriceX96
给定 tick 索引要从 tick 索引计算价格的平方根,我们从公式开始
(sqrtPriceX96296)2=1.0001i
然后,取两边的平方根并乘以 296,
sqrtPriceX96296=1.0001isqrtPriceX96=1.0001i⋅296
Solidity 中 tick 和 sqrtPriceX96
之间的转换由 TickMath 库 处理,由于在 Solidity 中实现 log 和平方根的复杂性,将在单独的章节中进行研究。
它包含两个函数:getSqrtRatioAtTick
和 getTickAtSqrtRatio
,它们执行 sqrtPriceX96
和其对应的 tick 索引之间的转换,以及相反的转换。如下所示。
我们在上一节中看到了这些公式的数学原理。在本节中,我们将使用 Python 执行此计算。
getSqrtRatioAtTick
函数Python 中的 getSqrtRatioAtTick
函数可以写成:
def getSqrtRatioAtTick(i):
return math.sqrt(1.0001 ** i) * 2**96
例如,可以使用 getSqrtRatioAtTick(887272)
计算上限价格的 sqrtPriceX96
的值,结果为 1.4614467034780703e+48
,这(近似地)对应于 TickMath
库中的 MAX_SQRT_RATIO
常量。
为了获得更准确的结果,我们可以使用 Python 中的 Decimal
库。使用此库的相同公式编码如下,以及 MAX_SQRT_RATIO
的计算值。
import math
from decimal import Decimal, getcontext
getcontext().prec = 50
def getSqrtRatioAtTick(i):
base = Decimal("1.0001")
exponent = Decimal(i)
sqrt_value = base ** (exponent / 2)
multiplier = Decimal(2) ** 96
return sqrt_value * multiplier
print(int(getSqrtRatioAtTick(887272))) # 1461446703485210103244672773810124308346321380902
getTickAtSqrtRatio
函数Python 中的 getTickAtSqrtRatio
函数可以写成:
def getTickAtSqrtRatio(sqrtPriceX96):
return math.floor(2*math.log(sqrtPriceX96/2**96)/math.log(1.0001))
例如,可以使用 getTickAtSqrtRatio(4295128739)
计算与 sqrtPriceX96
的下限 4295128739
对应的 tick,结果值为 -887272
,这是 tick 下限的预期值。
getTickAtSqrtRatio(4295128739) // -887272
当我们推导出公式
i=⌊2log(sqrtPriceX96296)log(1.0001)⌋
我们提到对数可以是任何底数。在 Python 中公式的实现中,我们使用了自然对数,但我们也可以使用以 10 为底的对数或任何其他底数,这将产生相同的结果。
原因是与不同底数的对数相关的对数的基本性质,
logb(k)=loga(k)loga(b)
其中 a 和 b 是两个不同的底数,k 是我们要计算的对数的参数。例如,将 log 从自然底数 e 转换为底数 10,我们有
log10(k)=loge(k)loge(10)
关键是除数 loge(10) 仅取决于底数,而不取决于参数。假设我们有一个分数:
log10(x)log10(y)
如果我们要将对数转换为某个任意底数 b,我们将分子和分母都除以 logb(10)。但是,将分数的分子和分母都除以相同的值对最终答案没有影响,因为它会抵消。
让我们通过将这种关系应用于我们的 tick 索引 i 公式并从自然底数转换为底数 10 来明确这一点:
i=⌊2loge(sqrtPriceX96296)loge(1.0001)⌋i=⌊2log10(sqrtPriceX96296)/log10(e)log10(1.0001)/log10(e)⌋from base e to base 10i=⌊2log10(sqrtPriceX96296)/log10(e)log10(1.0001)/log10(e)⌋i=⌊2log10(sqrtPriceX96296)log10(1.0001)⌋
正如我们所看到的,转换因子会抵消,并且计算结果与底数无关。
sqrtPriceX96
和 tick 索引之间进行转换。这可以使用 getSqrtRatioAtTick
和 getTickAtSqrtRatio
函数来完成,这些函数位于 TickMath
库中。sqrtPriceX96
转换为 tick 索引,应使用以下公式:i=⌊2log(sqrtPriceX96296)log(1.0001)⌋
浏览 Uniswap V3 Pools 查找池地址,然后在区块浏览器中打开该地址。查找公共变量 slot0
并将 tick
转换为 price
,反之亦然。
检查你的计算是否接近 Uniswap 提供的值,然后将 sqrtPriceX96 转换为美元。
对以下池执行此操作:
我们稍后将学习为什么合约直接使用来自 slot0
的 sqrtPriceX96
(可以给我们价格)和 tick
数据是不安全的,这目前仅用于练习。
- 原文链接: rareskills.io/post/unisw...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!