一:首先需要了解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生效的临时表,会比子查询快多了。