Uniswap v3 para Quants: Liquidez Concentrada e Matemática de Ticks a Partir dos Princípios Fundamentais
Uma posição de LP no Uniswap v3 é, disfarçadamente, uma ordem de intervalo (range order) de um livro de ofertas por limite. Deposite liquidez entre dois preços e você terá se comprometido a comprar o ativo enquanto o preço cai dentro do seu intervalo e a vendê-lo enquanto sobe — exatamente o que uma grade de ordens limitadas em espera faz em um CLOB. Mas o contrato não armazena preços, quantidades ou um livro de ofertas. Ele armazena três números: um preço em raiz quadrada em ponto fixo Q64.96, um índice de tick inteiro e um valor de liquidez agregada . Se você quer raciocinar sobre uma posição v3 da mesma forma que raciocinaria sobre cotações em um modelo de formação de mercado — inventário, preços de execução, seleção adversa —, precisa ser capaz de traduzir entre essas primitivas on-chain e os conceitos de trading que elas codificam. Este artigo constrói essa tradução a partir dos princípios fundamentais, seguindo o whitepaper do Uniswap v3 (Adams, Zinsmeister, Salem, Keefer, Robinson, 2021) e os contratos core, e termina onde toda discussão séria sobre LP começa hoje: loss-versus-rebalancing.
De x·y = k para reservas virtuais
O Uniswap v2 é o formador de mercado de produto constante: um pool mantém reservas do token0 e do token1 e impõe
em cada swap. O preço marginal do token0 em unidades de token1 é , e a liquidez é distribuída uniformemente por todo o eixo de preços . Isso é extremamente ineficiente em termos de capital: um par de stablecoins que negocia entre 0,999 e 1,001 mantém mais de 99% do seu capital posicionado em preços que nunca vão ocorrer.
A jogada do v3 é permitir que cada LP restrinja seu capital a um intervalo . Dentro do intervalo, a posição deve se comportar exatamente como um pool v2 — mesma curva de vinculação, mesma precificação marginal —, mas usando apenas as reservas necessárias para cobrir aquele intervalo. O whitepaper formaliza isso com reservas virtuais: a posição age como se mantivesse reservas no estilo v2 situadas em uma curva , enquanto as reservas reais são as virtuais menos o que a posição manteria nos limites do intervalo:
Esta é a equação 2.2 do whitepaper, e é a equação mais importante do v3. A curva traduzida toca os eixos: em a reserva real de atinge zero (a posição é 100% token1), e em a reserva real de atinge zero (100% token0). Fora do intervalo a posição fica inerte — um saldo fixo de um único token, sem rendimento algum.

O parâmetro , chamado liquidez, é o invariante que substitui . É definido como , e tem uma interpretação clara que o whitepaper explicita: liquidez é "reservas virtuais por unidade de raiz quadrada do preço". Em qualquer preço dentro do intervalo, as reservas virtuais são
Por que a variável de estado é √P
O core do v3 não rastreia o preço. Ele rastreia , armazenado como sqrtPriceX96, um número em ponto fixo Q64.96 não sinalizado:
onde é o preço bruto: unidades base do token1 por unidade base do token0, decimais incluídos (mais sobre isso adiante). A razão para a raiz quadrada não é economia de gas por si só, é álgebra. Diferencie as identidades de reserva virtual e você obtém as duas equações fundamentais de swap, implementadas na biblioteca SqrtPriceMath:
Ambos os deltas de token são lineares na raiz quadrada do preço (ou em seu recíproco), com como constante de proporcionalidade. Um swap dentro de um único tick, portanto, não precisa de inversão de curva, nem de iteração de Newton — apenas uma multiplicação para mover , depois duas multiplicações para calcular os montantes. Quando um swap é grande o suficiente para empurrar o preço através de um tick inicializado, o pool o cruza, adiciona ou remove a liquidez líquida referenciada ali (liquidityNet), e continua com o novo agregado. Globalmente, o pool é um AMM de produto constante por partes: constante entre ticks inicializados, com saltos nos ticks.
Para um formador de mercado, este é o modelo mental correto: o perfil de agregado do pool é o equivalente, em uma DEX, à profundidade do livro de ofertas. Onde a profundidade de um CLOB é cotada em unidades por nível de preço, a profundidade de um AMM é por tick — e a conversão é exatamente a identidade acima.
Ticks: uma grade de preços espaçada logaritmicamente
Os intervalos não podem começar e terminar em preços arbitrários; devem se alinhar a ticks. O tick corresponde ao preço
de modo que cada tick está a um ponto-base de distância de seus vizinhos — não em termos absolutos, mas relativos. Esse espaçamento logarítmico é deliberado: uma grade aditiva fixa seria absurdamente grosseira para um token negociado a $0,0001 e absurdamente fina a $100.000, enquanto uma grade geométrica fornece resolução uniforme de 1 pb em qualquer escala de preço. A biblioteca TickMath converte nos dois sentidos — getSqrtRatioAtTick e getTickAtSqrtRatio — usando manipulação de bits sobre constantes pré-computadas, em vez de chamar uma exponencial. Os índices de tick são limitados por MIN_TICK = -887272 e MAX_TICK = 887272, o que cobre a faixa de preços : ampla o suficiente para qualquer par de tokens que possa existir em aritmética uint256.
Nem todo tick é utilizável. Cada faixa de taxa impõe um espaçamento de tick, e as posições só podem usar ticks divisíveis por ele:
| Faixa de taxa | Espaçamento de tick | Largura mínima do intervalo | Uso típico |
|---|---|---|---|
| 0,01% | 1 | ~1 pb | stable/stable |
| 0,05% | 10 | ~10 pb | pares estáveis, ETH/stables |
| 0,30% | 60 | ~60 pb | principais |
| 1,00% | 200 | ~2% | exótico / volátil |
As faixas de 0,05%/0,30%/1% foram lançadas em maio de 2021; a faixa de 0,01% foi adicionada por votação de governança em novembro de 2021. Um espaçamento mais grosseiro em pools de taxa mais alta mantém baixo o número de ticks potencialmente cruzáveis e limitado o custo de gas dos swaps.
Uma armadilha de decimais que pega todo mundo pelo menos uma vez: o preço bruto on-chain é token1 por token0 em unidades base. No pool USDC/WETH da mainnet, o token0 é USDC (6 decimais) e o token1 é WETH (18 decimais), então um preço humano de $3.000 por ETH corresponde a um preço bruto de WETH-wei por unidade de USDC, que reside no tick
com . Se o seu dashboard de monitoramento mostrar um tick próximo de 196k para USDC/WETH, agora você sabe por quê — e que o preço humano é invertido conforme necessário.
Montantes de token a partir de (L, faixa de preço): as fórmulas exatas
O produto central desta seção: dada uma posição com liquidez em e preço atual , o que ela contém? Integrando as equações de swap ao longo do intervalo, obtêm-se três casos:
Preço abaixo do intervalo () — a posição é 100% token0:
Preço acima do intervalo () — a posição é 100% token1:
Preço dentro do intervalo ():
Isso é exatamente o que SqrtPriceMath.getAmount0Delta e getAmount1Delta calculam (com arredondamento direcionado — o contrato sempre arredonda contra o usuário, um detalhe que importa se você replicar isso em um backtester e ficar se perguntando sobre discrepâncias no nível de wei).
from math import sqrt
def position_amounts(L: float, pa: float, pb: float, P: float):
"""Token amounts held by a v3 position (float model; core uses Q96 ints)."""
sa, sb, sp = sqrt(pa), sqrt(pb), sqrt(P)
if P <= pa:
return L * (1/sa - 1/sb), 0.0
if P >= pb:
return 0.0, L * (sb - sa)
return L * (1/sp - 1/sb), L * (sp - sa)
Exemplo resolvido: ETH/USDC, intervalo [2500, 3500]
Suponha que o ETH negocia a USDC e você quer depositar 1 ETH no intervalo . Raízes quadradas: , , .
A perna de ETH fixa o valor de :
A perna de USDC decorre então:
Então, cunhar essa posição exige 1 ETH + 3.523,69 USDC, valor total $6.523,69, e note que as pernas não são 50/50 — a divisão depende de onde está posicionado dentro do intervalo (um intervalo assimétrico é precisamente como se expressa uma visão direcional, ou como se coloca uma "range order" pura com um único token).
Agora percorra o preço até os limites:
- Em a posição se converteu totalmente em ETH: ETH, no valor de $5.716,68. Você comprou 1,2867 ETH na queda a um preço médio ponderado por liquidez de 3523,69 / 1,2867 \approx \2.739$.
- Em ela se converteu totalmente em USDC: USDC. Você vendeu seu ETH na subida a uma média de $3.240.
Essa é a leitura de range order tornada concreta: a posição é uma escada de compras de 3000 até 2500 e de vendas de 3000 até 3500, com tamanho por tick proporcional a . E a concentração é aquilo pelo qual você é pago: uma posição no estilo v2 de faixa completa com o mesmo capital de $6.523,69 teria — a posição concentrada cota 12,4× mais profundidade por tick e (enquanto estiver dentro do intervalo) rende taxas a 12,4× a taxa por dólar de capital.
Contabilidade de taxas: feeGrowthGlobal e feeGrowthInside
As taxas do v3 não se capitalizam na posição (uma ruptura deliberada com o v2, onde as taxas eram reinvestidas em ). Elas se acumulam lado a lado, por token, e a contabilidade é uma pequena obra-prima de escrituração O(1) que vale a pena entender porque todo pipeline de analytics de LP a reimplementa.
O pool mantém dois acumuladores globais, feeGrowthGlobal0X128 e feeGrowthGlobal1X128: taxas cumulativas por unidade de liquidez desde a criação do pool, em ponto fixo Q128.128. A cada swap, o montante da taxa (retirado do token de entrada) é dividido pelo atualmente dentro do intervalo e adicionado ao acumulador. Esse é o mecanismo por trás da regra cardinal da economia do v3: as taxas se acumulam apenas para a liquidez que está dentro do intervalo no momento do swap. Posições fora do intervalo não são apenas inventário inerte — elas rendem exatamente zero enquanto inativas.
Para atribuir a fatia correta do crescimento global a um intervalo finito, cada tick inicializado armazena feeGrowthOutside0/1X128 — o crescimento de taxas que ocorreu no lado oposto do tick em relação ao preço atual (o valor inverte de interpretação cada vez que o tick é cruzado, o que é o que torna o esquema O(1)). Então, para uma posição em com tick atual :
Cada posição armazena um instantâneo feeGrowthInsideLast, e as taxas não coletadas são simplesmente
atualizado de forma preguiçosa sempre que a posição é tocada. Duas consequências práticas. Primeiro, os deltas de feeGrowthInside são a única forma honesta de medir a receita de taxas de uma posição — amostrar o volume no nível do pool e ratear pela sua participação no TVL erra sempre que o preço vagueia ao redor dos limites do seu intervalo. Segundo, como as taxas ficam registradas como tokensOwed em vez de se capitalizarem, os retornos realizados de LP têm um termo de arrasto de caixa que o v2 não tinha; os vaults de auto-compounding existem precisamente para arbitrar essa ineficiência (menos a própria taxa deles).

O payoff: um straddle vendido pelo qual você é pago para manter (talvez)
Dentro do intervalo, o valor da posição em função do preço é
Côncavo em — o termo é toda a história. Fora do intervalo, ele se torna linear: inclinação abaixo (você está comprado em um saldo fixo de ETH), inclinação 0 acima (você está totalmente em USDC). Percorra nosso exemplo resolvido ao longo do intervalo e compare com simplesmente manter (HODL) os tokens depositados:
| (USDC/ETH) | Valor LP | Valor HODL | Divergência |
|---|---|---|---|
| 2500 | 5.716,68 | 6.023,69 | −307,02 |
| 2750 | 6.200,4 | 6.273,69 | −73,3 |
| 3000 | 6.523,69 | 6.523,69 | 0 |
| 3250 | 6.706,3 | 6.773,69 | −67,4 |
| 3500 | 6.764,06 | 7.023,69 | −259,63 |
A posição de LP tem desempenho inferior ao HODL em ambas as direções e só se iguala a ele no preço de cunhagem. Alta limitada, participação amplificada na baixa, valor relativo máximo no strike... este é o payoff de um straddle vendido (mais precisamente, uma vez que as caudas lineares são compensadas contra o benchmark HODL, uma posição vendida em uma faixa de opções com strikes distribuídos em ). O fluxo de taxas é o prêmio. Concentrar o intervalo é escolher strikes mais apertados: mais prêmio por unidade de tempo enquanto dentro do intervalo, divergência mais rápida e mais profunda quando o preço se move. Todo LP do v3 é um trader vendido em volatilidade, quer tenha escolhido ser ou não — o equivalente on-chain do trade-off de risco de inventário que Avellaneda–Stoikov formaliza para formadores de mercado de livro de ofertas, com a largura do intervalo desempenhando o papel do spread cotado.

O nome tradicional para a lacuna nessa tabela é perda impermanente (divergência), mas IL-versus-HODL é um benchmark falho: ele mistura a perda que você teve como provedor de liquidez com o risco de mercado que você teria carregado de qualquer forma. A decomposição mais precisa vem de Milionis, Moallemi, Roughgarden e Zhang (2022), "Automated Market Making and Loss-Versus-Rebalancing" (arXiv:2208.06046). Compare o LP não contra o HODL, mas contra um portfólio de rebalanceamento que mantém, a cada instante, as mesmas quantidades de token que o pool — mas negocia ao preço de mercado externo sem fricção, em vez de contra arbitradores. A diferença é o LVR ("lever"): o componente do P&L do LP que é seleção adversa pura, pago a arbitradores que exploram as cotações desatualizadas do AMM após cada movimento de preço. Sob um preço de movimento browniano geométrico com volatilidade , o LVR se acumula à taxa instantânea
— uma "fórmula de Black–Scholes para AMMs", como os autores colocam, com sendo a profundidade marginal do pool no preço atual. Para o pool de produto constante de faixa completa, isso se reduz ao famoso do valor do pool por unidade de tempo: com 5% de volatilidade diária, aproximadamente 3,1 pb do pool sangra para arbitradores todos os dias, com ou sem taxas. A concentração multiplica — nosso exemplo de profundidade 12,4× acima também é uma máquina de LVR ~12,4× enquanto dentro do intervalo. As taxas precisam superar esse sangramento para que a posição seja +EV, e o LVR (diferente do "IL") é um custo corrente previsível e hedgeável, o que é o que o torna a unidade de conta correta. O cálculo completo de rentabilidade — APR de taxas versus LVR versus volatilidade realizada, quando fazer LP supera opções vendidas com hedge delta — é o tema do próximo aprofundamento sobre perda impermanente e LVR, e a mecânica de hedge recebe seu próprio tratamento em estratégias de LP e hedge no v3.
Mais um custo reside numa camada abaixo: como as cotações de AMM só se atualizam quando alguém negocia, toda posição de LP também está exposta aos jogos de mempool disputados em torno dessas negociações — ataques sandwich contra os traders que pagam suas taxas, e bundles de arbitragem que realizam seu LVR bloco a bloco. Esse ecossistema é mapeado em o artigo sobre MEV e ataques sandwich.
Lendo o estado do pool on-chain
Tudo o que foi dito acima é observável a partir de três chamadas baratas ao contrato do pool.
slot0() empacota o estado quente em um único slot de armazenamento: sqrtPriceX96, o tick atual, índices de observação do oráculo e as flags de taxa de protocolo/bloqueio. liquidity() retorna o agregado atual dentro do intervalo — note que isso não é TVL; é o parâmetro de profundidade ativo no tick atual e salta descontinuamente quando o preço cruza um tick inicializado. ticks(int24) retorna o estado por tick: liquidityGross ( total referenciando o tick), liquidityNet ( assinado adicionado ao cruzar da esquerda para a direita) e os acumuladores feeGrowthOutside. Iterar liquidityNet sobre os ticks inicializados (o tickBitmap informa quais existem sem escanear os 1,7 milhão) reconstrói o perfil de profundidade completo — o seu instantâneo do livro de ofertas.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider(RPC_URL))
pool = w3.eth.contract(address=POOL, abi=POOL_ABI) # USDC/WETH 0.05%
sqrt_price_x96, tick, *_ = pool.functions.slot0().call()
L = pool.functions.liquidity().call()
raw_price = (sqrt_price_x96 / 2**96) ** 2 # token1/token0, base units
eth_usdc = 1 / (raw_price * 10**(18 - 6)) # human USDC per ETH
depth_1tick = L * (1.0001**0.5 - 1) * (sqrt_price_x96 / 2**96)
As posições em si vivem em dois lugares. O pool core as indexa por (owner, tickLower, tickUpper) — um slot agregado por trio owner-intervalo. Investidores de varejo e a maioria dos fundos, em vez disso, cunham através do periférico NonfungiblePositionManager (mainnet: 0xC36442b4a4522E871399CD717aBDD847Ab11FE88), que envolve cada posição em um NFT ERC-721 e expõe positions(tokenId) retornando a tupla completa: tokens, faixa de taxa, intervalo, , feeGrowthInsideLast e tokensOwed. Para monitoramento de portfólio, essa única chamada mais o feeGrowthInside atual do pool (recalculado a partir de ticks() como na seção anterior) fornece o valor a mercado e as taxas acumuladas sem precisar tocar em um indexador — embora para qualquer análise histórica você vá precisar de replay de eventos de swap ou de um subgraph, porque o crescimento de taxas depende do caminho e a chain só armazena os acumuladores atuais.
Dois detalhes operacionais que vale a pena internalizar antes de alocar capital. Primeiro, o espaçamento de tick quantiza o seu espaço de estratégia: na faixa de 0,05% você pode posicionar intervalos de 10 pb de largura e executar algo próximo de uma verdadeira ordem limitada (cunhar logo acima/abaixo do spot, coletar a taxa, queimar após o cruzamento — o whitepaper explicitamente enquadra esse caso de uso como "range order"), enquanto na faixa de 1% o seu intervalo mínimo tem ~2% de largura e a analogia com o LOB fica mais grosseira. Segundo, uma range order é uma ordem limitada sem prioridade de cancelamento: se o preço cruza o seu intervalo e volta, você faz o inventário ir e voltar (round-trip) e devolve a vantagem (ficando com as taxas). Intervalos passivos são cotações que você não pode retirar — o que é exatamente por que a questão da frequência de rebalanceamento, e a lente do LVR para respondê-la, importam tanto.
O que fica desta análise
A pilha do v3, condensada: preços são ticks em uma grade geométrica de 1,0001; profundidade é , conversível em montantes de token através de nada mais que diferenças de raízes quadradas; taxas são um acumulador por que você subtrai entre os limites do seu intervalo; e a posição resultante é uma range order vendida em volatilidade cujo custo corrente tem um nome, uma fórmula e um número de arXiv. Com as primitivas em mãos, as perguntas interessantes se tornam quantitativas: quão largo, com que frequência rebalancear, e se as taxas cobrem o LVR para um dado pool e regime — que é precisamente para onde esta série segue a seguir.
Referências
- Adams, H., Zinsmeister, N., Salem, M., Keefer, R., Robinson, D. (2021). Uniswap v3 Core (whitepaper).
- Bibliotecas core do Uniswap v3:
TickMath.sol,SqrtPriceMath.sol,Position.sol,Tick.sol. - Milionis, J., Moallemi, C., Roughgarden, T., Zhang, A. L. (2022). Automated Market Making and Loss-Versus-Rebalancing. arXiv:2208.06046.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.