Calculating Implied Volatility from Uniswap V2 & V3
Calculating Implied Volatility from Uniswap V2 & V3
In a previous article I showed how to calculate the expected impermanent loss from Uniswap V2 and V3. Since Uniswap is a type of options market that allows liquidity providers to enter a short volatility position, it is possible to derive an implied volatility from the formula of expected impermanent loss.
In this article, I will show how to calculate implied volatility from Uniswap liquidity pools based on the current pool yield andthe mean return of a pair.
No Arbitrage Principle
To calculate implied volatility we have to assume that providing liquidity in Uniswap does not provide any benefit nor loss to a liquidity provider. That he is indifferent between providing liquidity and not providing liquidity. If this assumption did not hold, there would be a statistical arbitrage opportunity for providing liquidity in Uniswap.
For example, suppose that the yields a pool pays are much higher than the impermanent loss a liquidity provider expects to face. In that situation, providing liquidity has a positive expected return. The yields from the pool more often than not would compensate for the impermanent losses from price variation.
If the reverse was true, that is the expected impermanent loss is higher than the yields from trading fees, then removing liquidity as a borrower from the pool would be positive EV. The borrower would have to pay the lender the yields of the pool, assuming no interest on the loan), in exchange for the returns from impermanent loss, which are expected to be higher than the yields the borrower owes. The impermanent loss normally faced by LPs become that borrower’s impermanent gain.
Therefore, if we are to assume that there is no arbitrage opportunity in Uniswap, then the volatility the Uniswap trading fee yields imply is the expected return of the contract, and in this case that the return from trading fees and impermanent loss is equivalent to zero. At this level of volatility there is no strategy, providing liquidity or borrowing liquidity, that can generate an expected positive return.
Expected Impermanent Loss after Trading Fees in Uniswap V2
For a fair comparison of the profitability of providing liquidity over HODLing, we should calculate the return of the portfolio in an Uniswap V2 pool vs the portfolio held outside of the pool when taking the yield from trading fees into account. The return of LPing is the impermanent loss plus the yield from the pool, which was covered in this previous article
The formula below is derived with the yield added to the formula of the value of the portfolio in UniV2
Therefore, all we have to do is add the yield from trading fees to the formula of the expected value of the UniV2 LP position that we derived in the previous article. Then just calculate the expected impermanent loss, which yields
This is the case because the yield from trading fees is treated as a constant, although in real world situations it is variable.
Implied Volatility in Uniswap V2
Then, in order to find the implied volatility, σ, one assumes the expected impermanent loss with trading fees of the liquidity pool position is equal to zero
Then solve for volatility, σ
Now, as long as we have an assumption for what the value of μ is, that is the expected price movement of a pair over a certain period of time, we can estimate what the UniV2 pool expected volatility for a given pair is based on that pair’s current yield from trading fees.
Another interesting thing to note is that if μ is assumed to be zero, for example in very short time frames, then the formula for implied volatility is simply the square root of eight times the pool yield, α.
This result can serve as a way to estimate implied volatility for small points in time.
The following is a chart of implied volatility (σ) as yield (α) levels change at different levels of average returns (μ)
Since the volatility is based on log returns, converting the implied volatility to volatility of non log returns yields
Expected Impermanent Loss after trading fees in Uniswap V3
Since the impermanent loss of Uniswap V3 resembles the impermanent loss of Uniswap V2 as the price range increases, for the case of choosing the entire possible price range, we could rely on the same formula for Uniswap V2 for expected impermanent loss after trading fees.
However, if we were to choose a non trivial price range, we have to remember that we will not be collecting trading fees during the portion of the time that the price is outside of the range. This is trivial when the price range is wide enough for the corresponding time period but in tighter price ranges it is a significant issue that affects the calculations.
Therefore, in the next steps I will make the assumption that one is choosing a price range that is sufficiently wide enough that the price is very unlikely to deviate from the range. We will refer to the code we used in the previous article with a small change. We will update the function for expected impermanent loss to add the pool yield.
def calcImpLoss(lowerLimit, upperLimit, px, alpha): r = math.sqrt(upperLimit/lowerLimit) a1 = (math.sqrt(r) - px) a2 = (math.sqrt(r) / (math.sqrt(r) - 1)) * (2 * math.sqrt(px) * math.exp(alpha) - (px + 1)) a3 = (math.sqrt(r) * px - 1) if(px < 1/r): return a3 elif(px > r): return a1 return a2;
The difference of this function compared to the formula above for UniV2 is the multiplication of the formula for impermanent loss in UniV2 by the range factor
Implied Volatility from Uniswap V3
Again, just as in the case of the expected impermanent loss after trading fees, if we assume a price range large enough to virtually cover the entire possible price range, we can refer to the formula for implied volatility for Uniswap V2.
However, if we choose a non trivial price range, we will also have to assume that the range is wide enough that it is unlikely that the price will step out of it. If that assumption is fair, we can find the volatility, sigma, that makes the expected impermanent loss after trading fees equal to zero, the implied volatility, through an iterative process.
To accelerate the search of this iterative process, we use binary search, since it has Big Oh notation of log(n) where log is base 2. Since we most likely will not be able to find the exact number for sigma that satisfies the condition, we will choose a small delta as the level of significance to determine if we found the number. If the number we find is within delta distance of the number we are looking for, then the search will have succeeded.
We will also define a volatility range where we will conduct our search, that extends from 0% to 1,000%, divided in discrete increments of delta size. We chose 1,000% as the max level of volatility because it’s a rare level of volatility even for cryptocurrencies.
Therefore, using binary search, if we choose delta = 0.01% it should take us on average about 17 comparisons (log(10/0.0001) ~ 16.6) to find our answer.
def calcIV(rangePerc, mu, alpha) : delta = 0.0001 loSigma = 0 hiSigma = 10 midSigma = (loSigma + hiSigma) / 2 k_ = midSigma i_ = hiSigma j_ = loSigma kRet = calcExpImpLoss(rangePerc, mu, k_, alpha) #midSigma midRet iRet = calcExpImpLoss(rangePerc, mu, i_, alpha) #hiSigma loRet jRet = calcExpImpLoss(rangePerc, mu, j_, alpha) #loSigma hiRet while iRet < jRet : if abs(kRet - 0) <= delta : break if 0 < kRet : jRet = kRet j_ = k_ elif 0 > kRet : iRet = kRet i_ = k_ k_ = (i_ + j_) / 2 kRet = calcExpImpLoss(rangePerc, mu, k_, alpha) return k_
The complete python code is below
#!/usr/bin/env python3 import math from random import random#Standard Normal variate using Box-Muller transform. def random_bm(mu, sigma): u = 0 v = 0 while(u == 0): u = random() #Converting [0,1) to (0,1) while(v == 0): v = random() mag = sigma * math.sqrt( -2.0 * math.log( u ) ) return mag * math.cos( 2.0 * math.pi * v ) + mu def calcImpLoss(lowerLimit, upperLimit, px, alpha): r = math.sqrt(upperLimit/lowerLimit) a1 = (math.sqrt(r) - px) a2 = (math.sqrt(r) / (math.sqrt(r) - 1)) * (2 * math.sqrt(px) * math.exp(alpha) - (px + 1)) a3 = (math.sqrt(r) * px - 1) if(px < 1/r): return a3 elif(px > r): return a1 return a2; def calcExpImpLoss(rangePerc, mu, sigma, alpha) : upperPx = 1 + rangePerc lowerPx = 1 / upperPx Vhsum = 0 impLossSum = 0 numTries = 10000 for i in range(numTries) : t = 1 W = random_bm(0, 1) * math.sqrt(t-0) X = (math.log(1 + mu) - 0.5 * math.pow(math.log(1 + sigma), 2)) * t + math.log(1 + sigma) * W _px = math.exp(X) Vhsum += 1 + _px impLossSum += calcImpLoss(lowerPx, upperPx, _px, alpha) return (impLossSum/numTries)/(Vhsum/numTries) def calcIV(rangePerc, mu, alpha) : delta = 0.0001 loSigma = 0 hiSigma = 10 midSigma = (loSigma + hiSigma) / 2 k_ = midSigma i_ = upperSigma j_ = lowerSigma kRet = calcExpImpLoss(rangePerc, mu, k_, alpha) #midSigma midRet iRet = calcExpImpLoss(rangePerc, mu, i_, alpha) #hiSigma loRet jRet = calcExpImpLoss(rangePerc, mu, j_, alpha) #loSigma hiRet while iRet < jRet : if abs(kRet - 0) <= delta : break if 0 < kRet : jRet = kRet j_ = k_ elif 0 > kRet : iRet = kRet i_ = k_ k_ = (i_ + j_) / 2 kRet = calcExpImpLoss(rangePerc, mu, k_, alpha) return k_
In this article I showed how to calculate the implied volatility for a pair from Uniswap V2 and V3 pools.
However, as you will see in the next article, the IV from Uniswap V2 and V3 are often below the realized volatility a pair experiences. Therefore, in most cases Uniswap does not pay liquidity providers an adequate risk adjusted return.
I will explain why this is the case and how GammaSwap is the solution for it.