A summary of the math involved in building Blend

The information provided by Aloe Labs, Inc. (“we,” “us” or “our”) on docs.aloe.capital (the “Site”) is for general informational purposes only. All information on the Site is provided in good faith, however we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability or completeness of any information on the Site.

Under no circumstance shall we have any liability to you for any loss or damage of any kind incurred as a result of the use of the site or reliance on any information provided on the site. Your use of the site and your reliance on any information on the site is solely at your own risk.

Portion of Funds in Uniswap

Aloe Blend is designed to mimic the payoff curve of Uniswap V2, which means matching its liquidity density from 00 \rightarrow \infty. But what exactly is that density?

To keep things simple, let's look at the no-fee scenario for a Uniswap V2 pool containing assets X and Y. At price pl=ylxlp_l=\frac{y_l}{x_l} we have the constant product formula xlyl=kx_l y_l = k. Imagine someone trades Δy\Delta y units of Y for Δx\Delta x units of X. We now have xuyu=kx_u y_u = k and a new price pu=yuxup_u=\frac{y_u}{x_u}, where xu=xlΔxx_u=x_l-\Delta x and yu=yl+Δyy_u=y_l+\Delta y. Isolating Δx\Delta x and Δy\Delta y, we get the following:

Δx=k(1pl1pu)\Delta x=\sqrt{k} \left(\frac{1}{\sqrt{p_l}}-\frac{1}{\sqrt{p_u}}\right)

Δy=k(pupl)\Delta y=\sqrt{k} \left(\sqrt{p_u}-\sqrt{p_l}\right)

Key Insight: In this context, the liquidity between plp_l and pup_u is either Δx\Delta x or Δy\Delta y, depending on which price is considered "current." If ppup \ge p_u, then liquidity is Δy\Delta y units of Y. If pplp \le p_l, then liquidity is Δx\Delta x units of X.

These results can be massaged to match equations 6.29 and 6.30 in the Uniswap V3 Whitepaper with the slight modification that ΔL=k\Delta L=\sqrt{k}. That's all it takes to mimic V2 on V3! This is all the background we need to implement the basics of Aloe Blend.

Each Blend Vault has some inventory, (rx,ry)\left( r_x,r_y \right), but only part of that inventory is deposited to Uniswap V3: (Δx,Δy)\left( \Delta x, \Delta y \right). Since we want the vault to behave like Uniswap V2, we set rxry=kr_x r_y = k and p=ryrx=1.0001ap=\frac{r_y}{r_x}=1.0001^a. The choice of plp_l and pup_u is arbitrary as long as pl<p<pup_l \lt p \lt p_u, but to make the math work out nicely we use (pl,pu)=1.0001a±b\left(p_l,p_u \right)=1.0001^{a \pm b}. Combining these expressions with the previous equations yields the following:

Δx=rx(11.0001b/2)\Delta x=r_x \left(1 - 1.0001^{-b/2} \right)

Δy=ry(11.0001b/2)\Delta y=r_ y\left(1 - 1.0001^{-b/2} \right)

In code form:

/// @dev Computes amounts that should be placed in primary Uniswap position
function _computeMagicAmounts(
    uint256 _inventory0,
    uint256 _inventory1,
    int24 _halfWidth
) internal pure returns (uint256 amount0, uint256 amount1) {
    // the fraction of total inventory (X96) that should be put into primary Uniswap order to mimic Uniswap v2
    uint96 magic = uint96(Q96 - TickMath.getSqrtRatioAtTick(-_halfWidth));
    amount0 = FullMath.mulDiv(_inventory0, magic, Q96);
    amount1 = FullMath.mulDiv(_inventory1, magic, Q96);

These equations allow Blend to compute the portion of vault inventory that should be deposited to Uniswap V3. The symmetry and 1.0001 exponentials are quite convenient here, as they reduce on-chain computation and allow existing TickMath library functions to be reused.

Position Width

To use the equations above and figure out what percentage of funds to deposit to Uniswap, Blend must first choose how wide its position will be. This is tough because it has to optimize over the following criteria:

  • Maximizing silo deposits to earn more yield (smaller Uniswap position is better)

  • Keeping the Uniswap position in-range (larger Uniswap position is better)

  • Keeping rebalances as infrequent as possible so that maintenance fees paid to bots are kept to a minimum (larger Uniswap position is better)

These trade-offs are made even more complicated by the fact that Blend should work for all sorts of trading pairs, and stable-stable pairs behave very differently from others. Blend addresses this by measuring implied volatility on-chain and scaling position width accordingly.

Implied Volatility

Implied volatility (IV) differs from historical volatility (HV) in that it's forward-looking rather than backward-looking. In general, high IV values mean that the market thinks a given price is going to swing around a lot. This is perfect information for Blend, because if it observes high IV, it can preemptively increase its Uniswap position width to keep it in-range.

Surprisingly, computing IV on-chain is not only possible, but also more precise and gas-efficient than computing HV. Guillaume Lambert was the first to discover that you can estimate IV from Uniswap V3 data. What follows is a summary of Guillaume's results, plus some modifications to make everything run in the EVM.

IV=σ=2γDailyVolumeTickLiquidityIV = \sigma = 2 \gamma \sqrt{\frac{Daily Volume}{Tick Liquidity}}

This equation operates on data from a single Uniswap pool and determines the implied volatility between the two pool assets. γ\gamma is the fee tier, daily volume is self-explanatory, and tick liquidity is the amount of liquidity available at the current tick. Since IV is a dimensionless quantity, daily volume and tick liquidity must be denominated in the same asset.

There are a few problems here:

  • γ\gamma isn't as simple as it seems. Uniswap governance can excise a protocol fee on one or both assets independently.

  • There's no direct way to get daily volume on-chain. It must be estimated from daily LP fee revenue.

With this in mind, let's rework the equation such that it's actually implementable:

IV=σ=2γ0γ1fees0pmeanγ0+fees11γ1TickLiquidity=2γ1fees0pmean+γ0fees1TickLiquidityIV=\sigma=2 \sqrt{\gamma_0 \gamma_1} \sqrt{\frac{{fees}_0 \frac{p_{mean}}{\gamma_0} + {fees}_1 \frac{1}{\gamma_1}}{TickLiquidity}}=2\sqrt{\frac{\gamma_1 fees_0 p_{mean} + \gamma_0 fees_1}{TickLiquidity}}

There are a few approximations here. First, we've replaced γ\gamma with the geometric mean of γ0\gamma_0 and γ1\gamma_1. This may or may not be the "correct" mean to use, but it helps the math work out cleanly. We're also assuming that volume0=pmeanγ0fees0,ivolume_0=\frac{p_{mean}}{\gamma_0} \sum fees_{0, i} when in reality it's 1γ0pfees0\frac{1}{\gamma_0} \cdot \vec{p} \cdot \overrightarrow{fees_0} where pp and fees0fees_0 are vectors containing one element for each trade.

The only other tricky thing is making sure that these calculations don't overflow. You can find most of the code here and the oracle documentation here.

The Aloe Labs team did a brief analysis of the accuracy of this volume approximation and found that it was good enough for Blend. Prices were modeled with GBM, and simulations were run for both uniformly-distributed and normally-distributed trade sizes.

The approximation error seems to be proportional to μ2(logpnp0)2\mu^2 \propto \left( log \frac{p_n}{p_0} \right) ^ 2 with negligible dependence on GBM's σ\sigma. For reasonable daily price movements (±30\pm 30%) the error is just 1%.

Mapping IV to Ticks

The last step is to map IV values to position widths, measured in ticks. To encompass 95% of trading activity, Blend's Uniswap position should cover 2 standard deviations of price movement: p(1±2σ)p (1\pm2\sigma). Unfortunately, this would require an uneven number of ticks above the current price (log1.0001(1+2σ)\log_{1.0001}(1+2\sigma)) and below the current price (log1.0001(112σ)\log_{1.0001}(\frac{1}{1-2\sigma})). To make other math simpler, the larger value was chosen.

In code form:

/// @dev Computes position width based on volatility
function _computeNextPositionWidth(uint256 _sigma) internal pure returns (int24) {
    if (_sigma <= 9.9491783619e15) return MIN_WIDTH; // \frac{1e18}{B} (1 - \frac{1}{1.0001^(MIN_WIDTH / 2)})
    if (_sigma >= 3.7500454036e17) return MAX_WIDTH; // \frac{1e18}{B} (1 - \frac{1}{1.0001^(MAX_WIDTH / 2)})
    _sigma *= B; // scale by a constant factor to increase confidence

    unchecked {
        uint160 ratio = uint160((Q96 * 1e18) / (1e18 - _sigma));
        return TickMath.getTickAtSqrtRatio(ratio);


Since 95% of trading activity should stay in-range of Blend's Uniswap positions, most rebalances can be done by just shuffling liquidity between Uniswap and the silos. No swaps or limit orders necessary.

If/when a primary Uniswap position does slide out of range (or when interest income piles up in one silo faster than the other), Blend is able to place limit orders to get back to a 50/50 inventory ratio. These limit orders are carefully placed to avoid locking in impermanent loss.

One question that arises is how large a limit order should be such that, when it's pushed through, the vault's inventory ratio will be exactly 50/50. As long as one really can create a limit order (a sufficiently thin range order on Uniswap V3), the answer is simple:

error=inventory0pinventory1error=|inventory_0 \cdot p - inventory_1|

value1,limitorder=error2value_{1,limit order}=\frac{error}{2}

This approximation is good enough for Blend, but we've provided more exact formulas here.

Last updated