- polygon 上的 usdc-weth(fee:0.05), pool address: 0x45dda9cb7c25131df268515131f647d726f50608[1], 这也是上次分析所用的池子
- ethereum 上的 usdc-eth(fee:0.05), pool address: 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640[2],由于这个池包含native token,为数据处理带来了一些麻烦
- 获取uniswap的数据
- 获取用户现金数据
- 计算价格序列, 也就是 eth 的价格.
- 获取每分钟, 每个 tick 上获取了多少手续费
- 获取统计周期内, 所有 position 的列表
- 获取地址和 position 的对应关系
- 计算每个 position 的收益率
- 基于 position 和地址的对应关系, 计算每个用户地址作为 LP 的收益率
- 将用户的现金和 LP 合并, 并计算总体收益率
- ethereum rpc: eth客户端的标准rpc接口. 获取数据效率比较低. 需要多开一些线程.
- Google BigQuery: 从BigQuery的数据集下载数据. 虽然每天更新一次, 但对于胜在使用方便, 价格便宜.
- Trueblocks chifra: Chifra服务可以scrape链上的交易, 并重新组织. 从而轻松的导出交易, 余额等信息. 但这需要自己搭建节点和服务.
- minute: 将 uniswap swap 的交易信息, 重采样为每分钟的数据. 用于回测
- tick: 记录 Pool 中每一笔交易. 包括 swap 和对流动性的操作.
而对于高级用户, 尤其是一些基金, 他们会选择绕过 proxy, 直接操作 pool 合约. 这种情况下, position 不会有 token id. 这种情况下, 我们会用address-LowerTick-UpperTick的格式, 给这个 LP position 创造一个 id.对于 burn 和 collect, 也可以用这种方式为 pool 的 event 找到对应的 position id. 但是这里有个麻烦, 有时候两个 event 的金额并不相同, 会有一点点偏差. 比如这个交易 他的 amount0 和 amount1 会有一点小的差值, 这种情况虽然很少, 但也很常见. 所以我们在匹配 burn 和 collect 的时候, 给数值留了一些容错空间. 下一个要处理的问题是, 这个交易是谁发起的. 对于撤仓来说, 我们会把 collect 事件中的 receipt 作为 position 的持有人. 而对于 mint, 只能从 pool mint event 中得到 sender(见带有 mint event 的图). 如果用户是操作 pool 合约, 这个 sender 就会是 LP provider, 但如果是普通用户, 通过 proxy 操作合约, 这个 sender 会是 proxy 的地址, 这是因为资金确实是从 proxy 转到 pool 的. 但好在 proxy 会有 nft token 的产生. 而这个 nft token, 一定会转移给 LP provider. 因此, 检测 proxy 合约(也就是 nft token 的合约)的 transfer, 就可以查找到这个 mint 所对应的 LP provider 另外如果 nft 进行了转让, 会让 position 的持有人产生变化. 我们对此进行了统计, 这种情况较少. 为了简化, 我们没有考虑 mint 之后的 nft 转移. 2. 获取地址持有的现金这个阶段的目标, 是获取一个地址在统计期间, 每个时刻所持有 token 的数量. 要实现这个目标, 需要获取两方面的数据,
- 地址在起始时刻的余额
- 地址在统计期间的转帐记录.
- 对于通过 Proxy 投资的 LP, 每个 position 都会拥有一个 nft, 也就是拥有一个 token id, 这可以作为 position 的 id.
- 而对于直接操作 pool 投资的 LP, 我们会为他编造一个 id, 格式为address_LowerTick_UpperTick. 这样, 所有的 position 都有了自己的标识.
- 将 mint 和 burn 的 liquidity 相加, 得到一个数字 L
- 如果 L>0, 也就是 mint>burn, 认为在统计开始之前, 就有了一些流动性, 此时会在统计开始的时刻(2023.1.1 0:0:0)补偿一个 mint 操作.
- 如果 L<0, 认为在统计结束的时候, 仍然持有 liquidity.
- 当 mint 交易发生, 让流动性增加
- 当 burn 交易发生, 让流动性减少. 并将流动性的价值折算到手续费字段(pool 合约的代码也是这样操作的)
- 当 collect 交易发生. 会触发计算, 计算范围是从上次 collect 到当前时间, 我们会计算每分钟的净值和手续费收入, 得到一个列表.
注意:
- 回填的时候还要根据当前小时的流动性, 对手续费的分配进行加权, 否则会出现这个小时手续费偏高的情况.
- 由于统计的数据是 2023 年全年, 而不是完整数据, 因此存在第五节中提到的沉没流动性的现象. 这会让实际手续费比理论手续费多很多. 使得收益率变得异常高.
- 这里的收益率需要细化到每一分钟,
- 由于 position 会在中途有资金的转入和转出. 单纯开始和结束的净值相除并不能体现收益情况.
- 指定当前分钟是 n, 前一分钟是 n-1
- 假定当前分钟的所有转帐操作, 都发生在第 n:0.000 秒. 那么在余下的时间, LP 的净值是不变的, 也就是说第 n:0.001 秒的净值等于 n:59.999 秒的净值.
- 手续费的累加发生在这一分钟的末尾.也就是第 n:59.999 秒.
- 上一分钟末尾(n-1:59.999)的价格和手续费, 就是这一分钟(n:0.000)开始的价格和手续费
- 对于净值列来说是瞬时值, 也就是当前分钟/小时最后的值.
- 而手续费列是累加值. 也就是当前分钟/小时期间所累计的手续费
- 当 LP 被 burn 掉, 然后 token 转移走的情况下, 在这个小时末尾净值会是 0
- 而对于手续费来说, 由于他是累加的, 在这个小时的末尾, 手续费会大于 0.