撒酒狂歌

Posted on Jun 09, 2022Read on Mirror.xyz

分析dune上Charm计算Uniswap v3 LP 操作策略Alpha Vault TVL的计算逻辑

一:首先需要了解Alpha Vault是什么以及操作流程

uniswap v3集中流动性: 集中流动性意味着LP(流动性提供者)可以根据自己的意愿向任何价格范围/价格变动提供流动性,这导致流动性在价格变动中不平衡。

不同价格范围的流动性深度不同,深度不同导致获取的收益不同。

根据当前价格和目标价格范围,存在以下三种情况 (1)当前价格在目标价格区间 1、当前价格是目标价格范围的中心 当价格范围为[8,12]时,当前价格= 10。LP在相同价值的两个代币中提供流动性

2、当前价格不是目标价格范围的中心 LP仍然必须在两个代币中提供流动性,而每个代币的数量取决于当前价格和价格范围之间的距离

(2)当前价格 < 目标价格区间最小值 (3)当前价格 > 目标价格区间最大值 在情况2和3中,只需要或允许两个代币中的一个。 理解如何发现价格: x * y = k x和y代表两种代币的数量,k是常量 x相对于y的价格是 y / x,这意味着1单位X可以获得多少Y,反之亦然,y相对于x的价格是x/y。 为了X的价格上涨,y必须增加,x必须减少。

举例: 假设流LPs打算在高于当前价格x=10的区间——[15.625, 17.313]提供流动性。 (虽然没写,但是y的价格应该是1) 一个x = 1000 / 100 = 10个y

100(x) * 1000(y) = 100000(k)

当x价格上涨到15.625时,理解为:一个x可以换15.625个y了 80(x) * 1250(y) = 100000(k)

当x价格上涨到17.313时,理解为:一个x可以换17.313个y了 76(x) * 1315.789(y) = 100000(k)

X的价格进一步走高的唯一方法是进一步增加y和减少x

因此,要提供 [15.625, 17.313] 范围内的流动性,只需准备 80 - 76 = 4 个X。如果价格超过17.313,则 LP 的所有4个 X 都被交换为1315.789 - 1250 = 65.798 Y,然后 LP 与资金池不再有任何关系,因为他/她的流动性已经耗尽。

如果价格保持在该范围内: x的个数将在76~80之间摆动,y同理。只有当代币变得更有价值时,它才会被交换为价值较低的代币。

Alpha Vault: 自动为管理Uniswap V3上的流动性。集中流动性以获得更高的收益率,并定期被动再平衡以减少无常损失。 当两种资产变得不平衡且无法将其全部投入机枪池时,Vault 首先将两种资产的最大可用量投入机枪池中,然后再用有结余的一种资产投下另一个范围定单 。

被动再平衡

举例: 最初我们拥有的资产 50% 为 ETH,另外 50% 为 USDC。 一天后,随着 ETH 价格下跌,我们获得了更多的 ETH,而 USDC 减少了。

ETH 价格 = 3000 美元 : (50% ETH, 50% USDC) ETH 价格 = 2500 美元 : (70% ETH, 30% USDC) -> 资产全部取出 现在,假设我们可以在当前价格范围内输入机枪池的两者最大金额为 30% ETH 和 30% USDC。

Vault 的工作是先提取所有流动性,然后投下由 30% ETH、 30% USDC 组成的基础订单和由 40% ETH,0 USDC 组成的限价 / 重新平衡订单。

基础订单 : (30% ETH, 30% USDC) -> 按当前价格范围下单 限价 / 重新平衡订单 : (40% ETH, 0 USDC) -> 下单的价格范围 ≥ 现价 , 例如 : (2500, 2600), 这样当价格上涨时 ETH 可以被兑换成 USDC。

如果 ETH 有结余,以高于当前价格的区间值投入 ETH。

同样,如果我们有 USDC 结余,则将结余的 USDC 投入价格 ≤ 当前价格的范围内,这样当价格下跌时,可以将 USDC 兑换为 ETH。

限价 / 重新平衡订单的作用是避免直接 swapping (将 20% ETH 兑换成 20%USDC),那样做会导致被收取 0.1〜1% 的交易费,而是要通过提供流动性自然地发生 swapping,这样不花一分钱手续费,甚至还可以赚取交易费。

又产生了一个问题:如果ETH一直跌,不上涨。按上面的限价单就无法成交了(下单的价格范围 ≥ ETH现价)

二:SQL分析

-- Uniswap v3 LP 操作策略——Alpha Vault
-- postgresql 在一条sql语句中使用多个with。就是这个写法,不用纠结。

-- 交易对临时表
-- 1、用户将资产存入到vault里面,所以关联交易对mint事件表的时候,资产的所有者相当于vault的合约地址
-- 2、交易对mint事件表中contract_address字段的地址是Uniswap V3: USDT的合约。
-- 3、工厂创建池事件表中contract_address字段的地址其实指的是提供流动性后nft的合约。pool地址才是交易对的合约地址
-- 4、串联下:用户存入到Vault,Vault作为owner和sender将资产发送到Uniswap V3: USDT的合约,通过合约地址即可拿到交易对两个token的地址。
WITH pairs AS (
	SELECT
		charm.contract_address AS vault, 
		fac_pool.token0,
		fac_pool.token1 
	FROM
		charm."AlphaVault_evt_Deposit" charm       -- Alpha Vault 存入事件表
		INNER JOIN uniswap_v3."Pair_evt_Mint" AS uni  -- 交易对mint事件表
			ON charm.contract_address = uni.owner
		INNER JOIN uniswap_v3."Factory_evt_PoolCreated" AS fac_pool  -- 工厂创建池事件表
			ON uni.contract_address = pool 
	GROUP BY  -- 等于去重效果
		1,
		2,
		3 
	),
-- 	存入与提取临时表
-- 用了union all,相当于将几个查询的结果合并到一起(用union all的时候,列数需要相同)。只要读懂一个select的逻辑,就都明白了。
	deposits_and_withdrawals AS (
-- 以下开始是计算token存入的逻辑。
	SELECT
		evt_block_time,
		amount0 / 10 ^ decimals * price AS amount   -- 10^6表示的是10的6次方。prices库usd表有decimal字段,申明了小数点精度
	FROM
		charm."AlphaVault_evt_Deposit" charm
		INNER JOIN pairs ON vault = charm.contract_address      -- 通过合约地址关联上面的pairs表,从pairs表得到token地址。
		INNER JOIN prices.usd p ON token0 = p.contract_address  -- 用tonken地址关联,从prices库中的usd表取出token的价格
		AND date_trunc ( 'minute', evt_block_time ) = MINUTE 		-- MINUTE是prices库中的usd表字段。相当于交易发生时候的token价格
		
	UNION ALL
	
	SELECT
		evt_block_time,
		amount1 / 10 ^ decimals * price 
	FROM
		charm."AlphaVault_evt_Deposit" charm
		INNER JOIN pairs ON vault = charm.contract_address
		INNER JOIN prices.usd p ON token1 = p.contract_address 
		AND date_trunc ( 'minute', evt_block_time ) = MINUTE 
		
	UNION ALL
	
-- 	以下开始是计算token提现的逻辑。所以amount为负数
	SELECT
		evt_block_time,
		- amount0 / 10 ^ decimals * price 
	FROM
		charm."AlphaVault_evt_Withdraw" charm
		INNER JOIN pairs ON vault = charm.contract_address
		INNER JOIN prices.usd p ON token0 = p.contract_address 
		AND date_trunc ( 'minute', evt_block_time ) = MINUTE 
		
	UNION ALL
	
	SELECT
		evt_block_time,
		- amount1 / 10 ^ decimals * price 
	FROM
		charm."AlphaVault_evt_Withdraw" charm
		INNER JOIN pairs ON vault = charm.contract_address
		INNER JOIN prices.usd p ON token1 = p.contract_address 
		AND date_trunc ( 'minute', evt_block_time ) = MINUTE 
	) 
	
-- 根据上面的”临时表“求和
SELECT
	evt_block_time,
	-- 这个over跟hive里面应该是一样的。属于开窗函数。相当于根据时间排序。hive里面经常搭配partition by使用。比如一个班的同学考试成绩排名
	sum( amount ) OVER ( ORDER BY evt_block_time )  
FROM
	deposits_and_withdrawals

deposits_and_withdrawals 里面用了多次前面with as创建的表pairs。相当于子查询,非常影响效率,我想是因为dune不给用户创建临时表的权限吧。要不然创建只在当前session生效的临时表,会比子查询快多了。

Uniswap