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 ![](https://chart.googleapis.com/chart?cht=tx&chl=r_%7Ba0%7D)
reserve_b_initial
be ![](https://chart.googleapis.com/chart?cht=tx&chl=r_%7Bb0%7D)
reserve_b_after_execution
be ![](https://chart.googleapis.com/chart?cht=tx&chl=r_%7Bb1%7D)
amount_traded
be ![](https://chart.googleapis.com/chart?cht=tx&chl=a_%7Btraded%7D)
fee
be ![](https://chart.googleapis.com/chart?cht=tx&chl=f)
amountInWithFee
be ![](https://chart.googleapis.com/chart?cht=tx&chl=a_%7Bin%7D)
constant_product
be ![](https://chart.googleapis.com/chart?cht=tx&chl=k)
amountOut
be ![](https://chart.googleapis.com/chart?cht=tx&chl=a_%7Bout%7D)
market_price
be ![](https://chart.googleapis.com/chart?cht=tx&chl=p_%7Bmarket%7D)
mid_price
be ![](https://chart.googleapis.com/chart?cht=tx&chl=p_%7Bmid%7D)
price_impact
be ![](https://chart.googleapis.com/chart?cht=tx&chl=price_%7Bimpact%7D)
Now for the math:
![](https://chart.googleapis.com/chart?cht=tx&chl=a_%7Bin%7D%20%3D%20a_%7Btraded%7D%281-f%29)
![](https://chart.googleapis.com/chart?cht=tx&chl=k%20%3D%20r_%7Ba0%7D%20%5Ccdot%20r_%7Bb0%7D)
![](https://chart.googleapis.com/chart?cht=tx&chl=r_%7Bb1%7D%20%3D%20%5Cfrac%7Bk%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D%20%3D%20%5Cfrac%7Br_%7Ba0%7D%20%5Ccdot%20r_%7Bb0%7D%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D)
![](https://chart.googleapis.com/chart?cht=tx&chl=a_%7Bout%7D%20%3D%20r_%7Bb0%7D%20-%20r_%7Bb1%7D%20%3D%20r_%7Bb0%7D%20-%20%5Cfrac%7Br_%7Ba0%7D%20%5Ccdot%20r_%7Bb0%7D%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D%20%3D%20%5Cfrac%7Br_%7Bb0%7D%28r_%7Ba0%7D%20%2b%20a_%7Bin%7D%29%20-%20r_%7Ba0%7D%20%5Ccdot%20r_%7Bb0%7D%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D%20%3D%20%5Cfrac%7Br_%7Bb0%7D%20%5Ccdot%20a_%7Bin%7D%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D)
![](https://chart.googleapis.com/chart?cht=tx&chl=p_%7Bmarket%7D%20%3D%20%5Cfrac%7Ba_%7Bin%7D%7D%7Ba_%7Bout%7D%7D%20%3D%20a_%7Bin%7D%20%5Cdiv%20%5Cfrac%7Br_%7Bb0%7D%20%5Ccdot%20a_%7Bin%7D%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D%20%3D%20%5Cfrac%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D%7Br_%7Bb0%7D%7D)
![](https://chart.googleapis.com/chart?cht=tx&chl=p_%7Bmid%7D%20%3D%20%5Cfrac%7Br_%7Ba0%7D%7D%7Br_%7Bb0%7D)
![](https://chart.googleapis.com/chart?cht=tx&chl=price_%7Bimpact%7D%20%3D%201%20-%20%5Cfrac%7Bp_%7Bmid%7D%7D%7Bp_%7Bmarket%7D%7D%20%3D%201%20-%20%5Cfrac%7Br_%7Ba0%7D%7D%7Br_%7Bb0%7D%7D%20%5Cdiv%20%5Cfrac%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D%7Br_%7Bb0%7D%7D%20%3D%201%20-%20%5Cfrac%7Br_%7Ba0%7D%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D%20%3D%20%5Cfrac%7Ba_%7Bin%7D%7D%7Br_%7Ba0%7D%20%2b%20a_%7Bin%7D%7D)
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
:
![](https://chart.googleapis.com/chart?cht=tx&chl=a_%7Btraded%7D%20%3D%20%5Cfrac%7Br_%7Ba0%7D%20%5Ccdot%20price_%7Bimpact%7D%7D%7B%281%20-%20price_%7Bimpact%7D%29%281-f%29%7D)
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%](https://i.stack.imgur.com/T4tGs.png)
Please note that the price shown on the PancakeSwap UI is the execution_price
:
execution_price = amount_traded / amountOut;
I think what's going is that you are confused about the number of decimals. One ETH is 10^18 wei. But the USDC and USDT tokens only have 6 decimals: one USDC or USDT is only 10^6 of the basic unit.
Your code is converting the USDT amount to "wei" by multiplying one with 10^18, instead of 10^6 as should be correct. The results are incorrect by a factor of 10^12. In other words you're trying to swap 10^12 USDT for USDC. This is a huge number, so you get a huge price impact and the results is completely not what you expect.
If you change your function like this, the resulting USDT to USDC exchange rate should be close to 1.0:
DECIMALS = 6
def calcSell(contract,from_token,to_token):
oneToken = 10**DECIMALS
price = contract.functions.getAmountsOut(oneToken, [from_token, to_token]).call()
normalizedPrice = price[1] / 10**DECIMALS
normalizedPrice = f'{normalizedPrice:.30f}'
return normalizedPrice
Best Answer
There is a decimal mistake in the TokenAmount. It is important to check how many decimals any specific token has. While 1000000000000000000 wei is 1 ether, 1000000000000000000 specified for USDT, which has 6 decimal places, would equal 1000000000000, which is much greater than the current liquidity supply on Uniswap. 1000000 specified for USDT would equal 1 USDT and return reasonable results for