2

I am trying to understand how the price impact and liquidity was calculated in uniswap or pancakeswap through the router contract?

UNI Router: https://etherscan.io/address/0x7a250d5630b4cf539739df2c5dacb4c659f2488d

Pancake Router: https://bscscan.com/address/0x05ff2b0db69458a0750badebc4f9e13add608c7f

Is there is any direct method or what calculations need to do in my code? Pl. Guide me. Thanks in Advance

1

4 Answers 4

19

Disclaimer: I don't think this is the correct method to calculate the price impact but it's almost 100% accurate for price impacts of less than 1%. This calculation method assumes that multi-hop is disabled i.e. swapping is restricted to direct pairs. I've only tested this calculation method on PancakeSwap and it is less accurate for larger trade sizes compared to what is shown on their website. Attempting to calculate trade sizes for a 2% price impact, showed 1.99% on the PancakeSwap UI, 5% showed 4.99%, and 10% showed 9.97%. While these discrepancies could be attributed to the user interface lagging, I'm not confident that's the only reason. If anyone can better explain what's happening, I'd be very eager to know.

Short answer

Given a liquidity pool with token A and token B, let:

  • reserve_a_initial be the amount of token A in the liquidity pool before the trade
  • reserve_b_initial be the amount of token B in the liquidity pool before the trade (not required to calculate the price impact)
  • fee be the trading fee. For PancakeSwap, this is 0.0025
  • amount_traded be the amount of token A traded

To calculate the price impact:

amountInWithFee = amount_traded * (1 - fee);
price_impact = amountInWithFee / (reserve_a_initial + amountInWithFee);

Explanation

This article explains how to calculate the price impact. However, please note that what the article refers to as the "current market price" or "market rate", I refer to as the mid_price. Now, let's calculate the price_impact:

constant_product = reserve_a_initial * reserve_b_initial;
reserve_b_after_execution = constant_product / (reserve_a_initial + amountInWithFee);
amountOut = reserve_b_initial - reserve_b_after_execution;
market_price = amountInWithFee / amountOut;
mid_price = reserve_a_initial / reserve_b_initial;
price_impact = 1 - (mid_price / market_price);

Before substituting and simplifying, let:

  • reserve_a_initial be
  • reserve_b_initial be
  • reserve_b_after_execution be
  • amount_traded be
  • fee be
  • amountInWithFee be
  • constant_product be
  • amountOut be
  • market_price be
  • mid_price be
  • price_impact be

Now for the math:

I found this mind-blowing, but the amount of token B in the pool doesn't affect the price_impact. However, reserve_b_initial will affect amount_out.

Calculating amount_traded given the price_impact

Rearranging the price_impact equation, it is possible to calculate amount_traded for a given price_impact:

Slippage and minimum amount received

To account for slippage, s, and calculate the minimum received, amount_out_min:

amount_out_min = amount_out * (1 - s);

Slippage does not affect price_impact.

Sample code

I don't know if there's a function in a PancakeSwap contract that will directly return the price_impact, but you can get the reserves of a pool and then calculate it. Let's take CAKE-USDT as an example:

import { Contract, utils, providers } from 'ethers';
const poolAddress = '0xa39af17ce4a8eb807e076805da1e2b8ea7d0755b'; // CAKE-USDT
const poolContract = new Contract(
    poolAddress,
    ['function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast)'],
    new providers.WebSocketProvider(PROVIDER_URL)
);

poolContract.getReserves()
    .then((reserves) => {
        let reserve_a_initial = parseFloat(utils.formatUnits(reserves._reserve0));
        let reserve_b_initial = parseFloat(utils.formatUnits(reserves._reserve1));
        console.log(`CAKE in pool: ${reserve_a_initial}`);
        console.log(`USDT in pool: ${reserve_b_initial}`);

        const fee = 0.0025;
        let max_price_impact = 0.01;
        let amount_traded_cake = reserve_a_initial * max_price_impact / ((1 - max_price_impact)*(1 - fee));
        let amount_traded_usdt = reserve_b_initial * max_price_impact / ((1 - max_price_impact)*(1 - fee));
        console.log(`Given a max price impact of ${max_price_impact*100}%, the max amount of CAKE tradeable is ${amount_traded_cake}`);
        console.log(`Given a max price impact of ${max_price_impact*100}%, the max amount of USDT tradeable is ${amount_traded_usdt}`);

        let amountInCAKE = amount_traded_cake * (1 - fee);
        let amountInUSDT = amount_traded_usdt * (1 - fee);
        let price_impact_trade_cake = amountInCAKE / (reserve_a_initial + amountInCAKE);
        let price_impact_trade_usdt = amountInUSDT / (reserve_b_initial + amountInUSDT);
        console.log(`Price impact when trading ${amount_traded_cake} CAKE: ${price_impact_trade_cake*100}%`);
        console.log(`Price impact when trading ${amount_traded_usdt} USDT: ${price_impact_trade_usdt*100}%`);
    }).catch(console.error);

Sample results:

CAKE in pool: 1030240.4016832297
USDT in pool: 19974605.474162016
Given a max price impact of 1%, the max amount of CAKE tradeable is 10432.550079068678
Given a max price impact of 1%, the max amount of USDT tradeable is 202269.36507087937
Price impact when trading 10432.550079068678 CAKE: 1%
Price impact when trading 202269.36507087937 USDT: 1%

price impact at 1%

Please note that the price shown on the PancakeSwap UI is the execution_price:

execution_price = amount_traded / amountOut;
5
  • 1
    One of the best written answers on stackexchange! clap clap. Commented Oct 16, 2021 at 13:40
  • hi, somehow for me the 2 different ways of calculating price impact are not giving the same numbers. If we take first example of the article you linked we should get the same number if $10000 / (2000000 + 10000) = 1 - (2000 / 2009.64)$ which is not correct?
    – Chris
    Commented Apr 22, 2022 at 4:46
  • @Chris the article has some rounding errors. reserve_b_after_execution should be approx. 995.024876. amountOut (ETH received) should be approx. 4.975124. market_price (Price paid per ETH) should be exactly 2010. Thus, 10000 / (2000000 + 10000) = 1 - (2000 / 2010)
    – vlj614
    Commented Apr 22, 2022 at 14:25
  • 1
    Thanks for this clear solution! really saved me a ton of time.
    – Amir5000
    Commented May 12, 2022 at 23:36
  • 1
    The only thing missing in this answer is that the fee actually goes into the pool, so the constant product does change a little bit. That is probably why it is ever so slightly off in some cases. Excellent answer btw. I would love to see how this could be tweaked to find the optimal order quantity for an arbitrage trade.
    – Chev_603
    Commented Feb 6, 2023 at 5:49
1

[Adding this as a separate answer, due to lack of reputation for comments]

The answer by vlj614 is great. There is just one minor thought error and that also explains why your price impact is slightly off, specifically for large trades.

To calculate the impact, you are taking p_market as reference point (=denominator), i.e. you calculate the % difference from the realized price to the initial mid-price (p_mid). Instead, your formula should be the other way around and use p_mid as the reference, because the user is interested in how far (in %) the swap moves the price away from p_mid.

The correct formula would then be: p_mid / p_market - 1

To illustrate with a numerical example, assume the mid-price before the swap is 2000 USDC per ETH. Given the other parameters (pool reserves etc.), let's say the swap execution realizes at p_market = 2200. The intuitive answer to the price impact is = 10%, because my execution happened at 10% above the mid-price. Or expressed with the suggested new formula: impact = 2200/2000 - 1 = 10%.

According to your formula however, the price impact would instead be = 1 - 2000/2200 = 9.09%.

1
  • You substituted the wrong numbers (it says p_market is denominator but in the example p_market is the numerator). But the formula still can't be right. Using my formula to get 10%, PancakeSwap UI showed 9.97% (see disclaimer). But using your formula with the same p_mid and p_market, it should be 11.1% which is quite far from 9.97% compared with 10%.
    – vlj614
    Commented Apr 2 at 0:59
0

.17% is added back to the pool, so we need to figure out how to split that up in order to get the exact rate of exchange. It's confusing me, because the rate is used in the calculation of rate.

0

Based on the previous answers,I changed something. In UniswapV2 Module,this is a demo

function calculate() {
    let reserve_a_initial = 9266.898548529131779247;
    let reserve_b_initial = 0.3837704022282708;
    console.log(`TokenA in pool: ${reserve_a_initial}`);
    console.log(`TokenB in pool: ${reserve_b_initial}`);
    const amount_traded_cake = 100;
    const realInput = amount_traded_cake * 0.997;
    let amount_traded_usdt =
        (realInput * reserve_b_initial) / (reserve_a_initial + realInput);
    console.log(`the max amount of TokenA tradeable is ${amount_traded_cake}`);
    console.log(` the max amount of TokenB tradeable is ${amount_traded_usdt}`);
    let price_impact_trade_cake = (realInput * 100) / (reserve_a_initial + realInput);
    let price_impact_trade_usdt = amount_traded_usdt * 0.997 / (reserve_b_initial + (amount_traded_usdt * 0.997));
    console.log(`Price impact when trading ${amount_traded_cake} TokenA: ${price_impact_trade_cake}%`);
    console.log(`Price impact when trading ${amount_traded_usdt} TokenB: ${price_impact_trade_usdt * 100}%`);
}

and solidity demo

function getDeep(
    uint256 inTokenANum,
    uint256 tokenAReserve,
    uint256 tokenBReserve,
    uint8 decimals
) public view returns (uint256) {
    uint256 maxOutAmount = router02.getAmountOut(
        inTokenANum,
        tokenAReserve,
        tokenBReserve
    );
    uint256 realInput = (inTokenANum * 997) / 1000;
    // accuracy mul 1000000
    uint256 slippage = (realInput * 1000000) / (tokenAReserve + realInput);
    return slippage;
}

This may be slightly different from the webpage, but what I can think of may be caused by a sliding point issue

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.