Skip to content

Commit

Permalink
Adding slippage and price impact tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
miohtama committed Aug 1, 2023
1 parent 21ac716 commit 8476e3a
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 105 deletions.
5 changes: 5 additions & 0 deletions docs/source/tutorials/index.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.. meta::
:description: Python and Pandas examples for blockchain data research


Tutorials
=========

Expand Down Expand Up @@ -43,6 +47,7 @@ Example tutorials
pancakeswap-live-minimal
live-swap
aave-v3-interest-analysis
slippage-and-price-impact

`For more examples, browse tests folder on Github <https://github.com/tradingstrategy-ai/web3-ethereum-defi/tree/master/tests>`__.
You can also search function names in `the repository <https://github.com/tradingstrategy-ai/web3-ethereum-defi/>`__
Expand Down
29 changes: 29 additions & 0 deletions docs/source/tutorials/slippage-and-price-impact.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.. meta::
:description: Python example to calculate slippage and price impact

.. _slippage and price impact:

Slippage and price impact
-------------------------

Below is an example Python script that analyses the historical Uniswap v3 trade.
It does some calculations around `slippage <https://tradingstrategy.ai/glossary/slippage>`__ and `price impact <https://tradingstrategy.ai/glossary/price-impact>`__
analysis.

- We know the block number when the trade decision was made
(time and block number at the time when the price impact was estimated)

- We know the block number when the trade was actually executed

- We use a Polygon JSON-RPC archive node to check Uniswap WMATIC-USDC
pool state at both blocks and compare the results

- Slippage is assumed execution price vs. realised execution price

- We also double check some other numbers like `TVL <https://tradingstrategy.ai/glossary/total-value-locked>`__
and `mid price <https://tradingstrategy.ai/glossary/mid-price>`__ of the underlying Uniswap v3 pool

- See :py:mod:`eth_defi.uniswap_v3.price` module for more information

.. literalinclude:: ../../../scripts/slippage-and-price-impact.py
:language: python
134 changes: 37 additions & 97 deletions eth_defi/uniswap_v3/price.py
Original file line number Diff line number Diff line change
@@ -1,95 +1,6 @@
"""Uniswap v3 price calculations.
- You can estimate the future price w/slippage
- You can check for the past price impact and slippage
Here is an example how to use price calculation to calculate the historical price impact of WMATIC->USDC trade on Polygon using 5 BPS fee tier
for two different blocks:
.. code-block:: python
import os
from decimal import Decimal
from web3 import Web3, HTTPProvider
from eth_defi.token import fetch_erc20_details
from eth_defi.uniswap_v3.deployment import fetch_deployment
from eth_defi.uniswap_v3.price import get_onchain_price, estimate_sell_received_amount
params = {"path":"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf12700001f42791bca1f2de4661ed88a30c99a7a9449aa84174","recipient":"0x19f61a2cdebccbf500b24a1330c46b15e5f54cbc","deadline":"9223372036854775808","amountIn":"14975601230579683413","amountOutMinimum":"10799953"}
amount_in = 14975601230579683413
path = params["path"]
# https://tradingstrategy.ai/trading-view/polygon/uniswap-v3/matic-usdc-fee-5
pool_address = "0xa374094527e1673a86de625aa59517c5de346d32"
block_estimated = 45_583_631
block_executed = 45_583_635
wmatic_address = "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"
usdc_address = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
wmatic_amount = Decimal("14.975601230579683413")
fee_tier = 0.0005 # BPS
# What is the max slippage value for Uniswap,
# as slippage is irrelevant in our
# calculations
max_slippage = 10000
json_rpc_url = os.environ["JSON_RPC_POLYGON"]
web3 = Web3(HTTPProvider(json_rpc_url))
wmatic = fetch_erc20_details(web3, wmatic_address)
usdc = fetch_erc20_details(web3, usdc_address)
wmatic_amount_raw = wmatic.convert_to_raw(wmatic_amount)
mid_price_estimated = get_onchain_price(web3, pool_address, block_identifier=block_estimated)
mid_price_executed = get_onchain_price(web3, pool_address, block_identifier=block_executed)
print(f"Mid price when estimate at block {block_estimated:,}:", mid_price_estimated)
print(f"Mid price at the time of execution at block {block_executed:,}:", mid_price_executed)
print(f"Price difference {(mid_price_executed - mid_price_estimated) / mid_price_estimated * 100:.2f}%")
# Uniswap v4 deployment addresses are the same across the chains
# https://docs.uniswap.org/contracts/v3/reference/deployments
uniswap = fetch_deployment(
web3,
"0x1F98431c8aD98523631AE4a59f267346ea31F984",
"0xE592427A0AEce92De3Edee1F18E0157C05861564",
"0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
"0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6",
)
estimated_sell_raw = estimate_sell_received_amount(
uniswap,
base_token_address=wmatic_address,
quote_token_address=usdc_address,
quantity=wmatic_amount_raw,
target_pair_fee=int(fee_tier * 1_000_000),
block_identifier=block_estimated,
slippage=max_slippage,
)
estimated_sell = usdc.convert_to_decimals(estimated_sell_raw)
print(f"Estimated quantity: {estimated_sell}")
executed_sell_raw = estimate_sell_received_amount(
uniswap,
base_token_address=wmatic_address,
quote_token_address=usdc_address,
quantity=wmatic_amount_raw,
target_pair_fee=int(fee_tier * 1_000_000),
block_identifier=block_executed,
slippage=max_slippage,
)
executed_sell = usdc.convert_to_decimals(executed_sell_raw)
print(f"Executed quantity: {executed_sell}")
print(f"Supposed price impact {(executed_sell - estimated_sell) / estimated_sell * 100:.2f}%")
See :ref:`slippage and price impact` tutorial.
"""

from decimal import Decimal
Expand All @@ -103,6 +14,8 @@


class UniswapV3PriceHelper:
"""Internal helper class for price calculations."""

def __init__(self, uniswap_v3: UniswapV3Deployment):
self.deployment = uniswap_v3

Expand Down Expand Up @@ -231,7 +144,11 @@ def estimate_buy_received_amount(
:param base_token_address: Base token address of the trading pair
:param quote_token_address: Quote token address of the trading pair
:param target_pair_fee: Trading fee of the target pair in raw format
:param slippage: Slippage express in bps
:param slippage:
Slippage express in bps.
The amount will be estimated for the maximum slippage.
:param block_identifier: A specific block to estimate price
:param verbose: If True, return more debug info
:return: Expected base token amount to receive
Expand Down Expand Up @@ -285,7 +202,11 @@ def estimate_sell_received_amount(
:param base_token_address: Base token address of the trading pair
:param quote_token_address: Quote token address of the trading pair
:param target_pair_fee: Trading fee of the target pair in raw format
:param slippage: Slippage express in bps
:param slippage:
Slippage express in bps.
The amount will be estimated for the maximum slippage.
:param block_identifier: A specific block to estimate price
:param verbose: If True, return more debug info
:return: Expected quote token amount to receive
Expand Down Expand Up @@ -325,11 +246,30 @@ def get_onchain_price(
):
"""Get the current price of a Uniswap pool.
:param web3: Web3 instance
:param pool_contract_address: Contract address of the pool
:param block_identifier: A specific block to query price
:param reverse_token_order: If set, assume quote token is token0
:return: Current price
Reads Uniswap v3 "slot 0" price.
TODO: Does this include fees or not?
- See `mid price <https://tradingstrategy.ai/glossary/mid-price>`__
:param web3:
Web3 instance
:param pool_contract_address:
Contract address of the Uniswap v3 pool
:param block_identifier:
A specific block to query price.
Block number or block hash.
:param reverse_token_order:
For switching the pair ticker around to make it human readable.
If set, assume quote token is token0
:return:
Current price
"""
pool_details = fetch_pool_details(web3, pool_contract_address)
_, tick, *_ = pool_details.pool.functions.slot0().call(block_identifier=block_identifier)
Expand Down
2 changes: 0 additions & 2 deletions scripts/pancakeswap-live-swaps-minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def main():

# Keep reading events as they land
while True:

start = next_block
end = web3.eth.block_number

Expand All @@ -83,7 +82,6 @@ def main():
end_block=end,
filter=filter,
):

decoded = decode_log(evt)

# Swap() events are generated by UniswapV2Pool contracts
Expand Down
107 changes: 107 additions & 0 deletions scripts/slippage-and-price-impact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Check the Uniswap v3 price estimation at a historical block.
- Understand why slippage was what it was
- Check what was the estimated and executed sell WMATIC->USDC on Uniswap v3
- See the TX https://polygonscan.com/tx/0x5b76bf15bce4de5f5d6db8d929f13e28b11816f282ecd1522e4ec6eca3a1655e
"""
import os
from decimal import Decimal

from web3 import Web3, HTTPProvider

from eth_defi.token import fetch_erc20_details
from eth_defi.uniswap_v3.deployment import fetch_deployment
from eth_defi.uniswap_v3.pool import fetch_pool_details
from eth_defi.uniswap_v3.price import get_onchain_price, estimate_sell_received_amount
from eth_defi.uniswap_v3.tvl import fetch_uniswap_v3_pool_tvl

# The test amount of WMATIC for which selling
# we calculate price impact and slippage numbers
wmatic_amount = Decimal("14.975601230579683413")

# WMATIC-USDC 5 BPS pool address
# https://tradingstrategy.ai/trading-view/polygon/uniswap-v3/matic-usdc-fee-5
pool_address = "0xa374094527e1673a86de625aa59517c5de346d32"
block_estimated = 45_583_631 # Assume this was when the trade was deciced
block_executed = 45_583_635 # Assume this was then the trade was executed
wmatic_address = "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"
usdc_address = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
fee_tier = 0.0005 # BPS

# Give Polygon archive node JSON-RPC access
json_rpc_url = os.environ["JSON_RPC_POLYGON"]
web3 = Web3(HTTPProvider(json_rpc_url))

wmatic = fetch_erc20_details(web3, wmatic_address)
usdc = fetch_erc20_details(web3, usdc_address)

pool = fetch_pool_details(web3, pool_address)

wmatic_amount_raw = wmatic.convert_to_raw(wmatic_amount)

mid_price_estimated = get_onchain_price(web3, pool_address, block_identifier=block_estimated)
mid_price_executed = get_onchain_price(web3, pool_address, block_identifier=block_executed)

tvl_estimated = fetch_uniswap_v3_pool_tvl(
pool,
quote_token=usdc,
block_identifier=block_estimated,
)

tvl_executed = fetch_uniswap_v3_pool_tvl(
pool,
quote_token=usdc,
block_identifier=block_executed,
)

print(f"WMATIC sold {wmatic_amount}")
print(f"TVL during estimation: {tvl_estimated:,} USDC at block {block_estimated:,}")
print(f"TVL during execution: {tvl_executed:,} USDC")
print(f"Mid price when estimate at block {block_estimated:,} USDC/MATIC:", mid_price_estimated)
print(f"Mid price at the time of execution at block {block_executed:,} USDC/MATIC:", mid_price_executed)
print(f"Mid price movement {(mid_price_executed - mid_price_estimated) / mid_price_estimated * 100:.2f}%")

# Uniswap v3 deployment addresses are the same across the chains
# https://docs.uniswap.org/contracts/v3/reference/deployments
uniswap = fetch_deployment(
web3,
"0x1F98431c8aD98523631AE4a59f267346ea31F984",
"0xE592427A0AEce92De3Edee1F18E0157C05861564",
"0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
"0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6",
)

# Get the amount of price impact
estimated_sell_raw = estimate_sell_received_amount(
uniswap,
base_token_address=wmatic_address,
quote_token_address=usdc_address,
quantity=wmatic_amount_raw,
target_pair_fee=int(fee_tier * 1_000_000),
block_identifier=block_estimated,
slippage=0,
)
estimated_sell = usdc.convert_to_decimals(estimated_sell_raw)

print(f"Estimated received quantity: {estimated_sell} USDC")

executed_sell_raw = estimate_sell_received_amount(
uniswap,
base_token_address=wmatic_address,
quote_token_address=usdc_address,
quantity=wmatic_amount_raw,
target_pair_fee=int(fee_tier * 1_000_000),
block_identifier=block_executed,
slippage=0,
)
executed_sell = usdc.convert_to_decimals(executed_sell_raw)

executed_sell_price = executed_sell / wmatic_amount

print(f"Executed received quantity: {executed_sell} USDC")
print(f"Executed sell price: {executed_sell_price} USDC/MATIC")
print(f"Executed price impact (includes fees) {(executed_sell_price - mid_price_executed) / mid_price_executed * 100:.2f}%")
print(f"Slippage {(executed_sell - estimated_sell) / estimated_sell * 100:.2f}%")
8 changes: 2 additions & 6 deletions scripts/uniswap-v2-swaps-live.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,7 @@ def main():
# Start from the existing save point
block_header_df = block_store.load()
reorg_mon.load_pandas(block_header_df)
logger.info("Loaded %d existing blocks from %s.\n"
"If the save checkpoint was long time ago, we need to catch up all blocks and it could be slow.", len(block_header_df), block_store.path)
logger.info("Loaded %d existing blocks from %s.\n" "If the save checkpoint was long time ago, we need to catch up all blocks and it could be slow.", len(block_header_df), block_store.path)
else:
# Start from the scratch,
# use tqdm progess bar for interactive progress
Expand Down Expand Up @@ -262,10 +261,7 @@ def main():
# Dump stats to the output regularly
if time.time() > next_stat_print:
req_count = api_request_counter["total"]
logger.info("**STATS** Reorgs detected: %d, block headers buffered: %d, API requests made: %d",
total_reorgs,
len(reorg_mon.block_map),
req_count)
logger.info("**STATS** Reorgs detected: %d, block headers buffered: %d, API requests made: %d", total_reorgs, len(reorg_mon.block_map), req_count)
next_stat_print = time.time() + stat_delay

# Save the current block headers on disk
Expand Down

0 comments on commit 8476e3a

Please sign in to comment.