Designing a constant volatility AMM
- TL;DR: Constant volatility AMMs requires the fee rate to change at every txn.
- By computing the fee on a per-trade basis in Uniswap, it is possible to target a constant volatility (eg. 100% annualized) regardless of the actual price dynamics of the underlying assets.
- Trading assets in a constant volatility AMM should feel like a delicate balancing act between slippage & fees.
How feeTier impacts trade volumes
In Uniswap v3, users deploying a new pool can specify a feeTier for each asset pair. The feeTier currently available are 1%, 0.3%, 0.05%, and 0.01%.
The conventional wisdom is that a higher fee is best for exotic pairs and the 0.01% is more suitable for the most stable of pairs.
How accurate is this “conventional wisdom”. In fact, shortly after the Uniswap governance activated the 1bps fee tier, we immediately saw a redirection of almost all stablecoin-stablecoin TVL and volume from the 0.05% pools to the 0.01% pool. And even though the 0.05% pools still hold about 1/3 of the total TVL for the DAI-USDC pair, 95% of the total volume is routed through the 1bps pool:
Interestingly, some liquidity was deployed in the ETH-USDC 1bps pool:since the creation of the 1bps fee tier, 34 LP positions have been deployed to the ETH-USDC-1bps pool (total value deployed= $15M):
What was the average return of the ETH-USDC-0.01% liquidity providers? Well, not great: the median amount of collected fees is about 0.005% of the deposited amounts. And although some positions ended up with more value in USD terms, most positions gains/losses shown in the table above were due to changes in the price of ETH when the position was burned.
This is an interesting observation: whereas the 0.05% pool is perfectly suitable for several ETH-stablecoin pairs, it seems that the yields for the 0.01% pools are insultingly low.
Why didn’t the 1bps fee tier gain traction for non-stablecoin-stablecoin pairs? Is there something special about the 1bps fee tier that makes it non-viable for ETH-based pairs?
Going even deeper, could an AMM with infinite liquidity implement a 0% trading fee? Well it turns out that, for one, a fee equal to zero is especially detrimental because of the path-dependent nature of geometric means (vs. arithmetic means). But Dave White et al showed that anything above zero is close to being optimal:
The fee should therefore be set as low as possible without being zero so that rebalances are triggered by increasingly tiny price movements. Dan Robinson calls this “picking up pennies in the quantum foam.”
However, when the fee is exactly zero, all the benefits of rebalancing disappear, and in most cases LPs are worse off than they would be if they just held the unrebalanced portfolio. source: Uniswap’s Financial Alchemy
How much of that is true? First, let’s explore how the feeTier influences the transaction volume and liquidity distribution.
How feeTier, dailyVolume and tickLiquidity impacts volatility
We’ve derived in a previous post an expression linking the feeTier, the dailyVolume, and the tickLiquidity:
In a series of tweets from last weekend, I described how this expression appears to hold across many pools in the Uniswap v3 ecosystem.
Specifically, these posts show that even though the feeTier, the daily volume, and the amount of liquidity locked in a pool do vary quite a bit between pools, it appears that the “market” has figured out a way to distribute that liquidity to maintain a constant volatility between pools.
For instance, the 1%, 0.3%, and 0.05% ETH-USDT pools all have IV equal to approximately 50% even though the volume and TVL vary greatly between pools:
I made the argument that the volatility of an asset is an invariant that must remain the same across pools. This is true in part because the Uni v3 Router contract “smartly” only direct an order to the 0.05% pool only if the slippage is less than 0.3%, to the 0.3% pool only if the slippage is less than 1%, and to the 1% pool if the slippage is greater than 1%.
As a result, the 1% pool sees a much lower number of transactions, but the slippage will be higher for each one. Similarly, the 0.05% pool is traded much more frequently but the average slippage is lower.
Ultimately, the yield will be the same across fee tiers because average returns are directly related to the volatility of an asset. “Rational” market participants will recognize this will relocate liquidity from low volatility pool (ie. pools with low volume OR too much liquidity) to high volatility pools to maximize their yields.
In the case of the 1bps ETH-USDC pool, the TVL was too low to support any kind of volume: if the 1bps and 5bps were to have the same volume, the TVL of the 1bps pool needs to be 25 times larger than the TVL of the 5bps pool.
Responsive fee: building a constant volatility AMM
What if we let the feeTier float and instead target a specific volatility σ?
In the previous section, we derived an expression for the implied volatility that depends on i) the feeTier, ii) the daily volume, and iii) the at-tick volatility. We made the argument that returns for LPs should be the same across all fee tiers as long as each pool follows the same asset.
We postulate here that we can achieve this by inverting the IV expression so that the feeTier depends on liquidity+volume while keeping the volatility constant:
Basing the fee of the daily volume is perhaps a bit arbitrary. What about computing the feeTier on a per-trade basis?
Let’s consider a user wants to swap amount0 of token0 for amount1 of token1. First, the dailyVolume would need to be transformed into tradeSize/time. If we set the “time” variable to the time since the last transaction, and the tradeSize as amount0, then the feeTier would be:
If we want to target a specific volatility, say vol=100% annualized, then we need to convert the value of σ to the same using as “time since last transaction”, which is given in seconds, to get:
This expression has very interesting consequences.
- The more time between transaction, the higher the fee
- The larger the trade size, the smaller the fee
- The higher the liquidity at the traded tick, the higher the fee
All points above seem to reduce fees in response to: large trades that happen very frequently, in regions with low liquidity. That’ll drive the volatility up for you.
Let’s consider an example, where this reactive fee is implemented in the ETH-DAI-0.3% pool. The current value locked at the current tick is 316 ETH and the implied volatility at that tick is 56%.
If one trade happens every block (blocktime=15s) and the pool targets an annualized volatility of 100%, then the purchase of 1 ETH would result in a fee equal to: √15/11231 * √(315/1) = 0.6%. About twice the “normal” fee.
Waiting another block would increase this fee to 0.86%, with a general formula for the 1 ETH transaction being 0.6% * √(no of blocks since last txn).
However, let’s say the user is a whale that instead sells 1000 ETH. Then the fee would be 0.02% (!), generating only 0.2ETH in trading fees for the liquidity providers. The slippage on that trade would also be 3 ticks for a ~0.9% slippage, so LPs would only collect 0.067ETH at each tick collectively.
This slippage may be arbitraged back if the new spot price is not aligned with other exchanges’. So in this case, another arbitrage trade may occur to bring the price back to where it was.
Because of this, non-arbitrage trades would occur more frequently because fees are lower for larger trades, and price will need to be arbed back with a correspondingly large trade to reduce gas fees. So even though the effective fee was only 0.02%, a single transaction will create a ripple effect of arbitrage trades that may effectively create more volume than if the pool had a constant fee.
All of these extra trades will contribute to increasing the volatility of the asset to the target rate, regardless of the actual realized volatility of the underlying asset on centralized exchanges.
A constant volatility AMM in Uniswap v2
What if we do not want to use Uniswap v3 with its tick-base liquidity paradigm (maybe in 1 more year we can) and instead use a vanilla x*y/k AMM like Uniswap v2?
In that case, the tickTVL is meaningless because liquidity in Uniswap v2 is deployed from 0 to infinity. However, we can assume that Uniswap v2 is simply Uniswap v3 in the limit of infinity range. Specifically, we can convert Uni v3 liquidity into Uni v2 liquidity by considering the relative capital efficiency of Uniswap v3:
In that case, we can convert between the tickTVL of Uniswap v3 with an effective “liquidity at the traded tick” for Uniswap v2 according to:
So, for an effective feeRate of 0.3%, the “tick liquidity” of a Uniswap v2 pool would be 668 times smaller than that. The effective feeTier thus becomes:
where reserve0 is the total reserve of token0 in the UniswapV2Pair smart contract and amount0 is the trade size.
As an example, the current Uni v2 ETH-Dai pool contains 13,886,496 DAI and 3,990 ETH, so a 1 ETH trade would result in a 0.08% fee and a 1000 ETH trade would result in a 0.0026% fee (only 0.026 ETH in fees!).
The reason why these fees are so low is because the effective liquidity at the “traded tick” is only ~6 ETH, which would in turn encourage traders to trade frequently enough so that LPs receive a reward commensurate with a 100% IV asset even though the “per-trade” fee is minuscule.
Forking the Uniswap v2 contract
Implementing this AMM is relatively easy: one only needs to modify the Uniswap v2 smart contracts so that the fee taken on each trade depends on the reserve amount, the traded amount, and the time since the last transaction (blockTimestampLast).
In fact, one does not even need to be a solidity dev to do that, here’s the proof (I’m surprised it actually compiles!):