Uniswap V3 price analysis
In this notebook we will show how to download price events from Uniswap V3 to your computer as CSV files and use them to analyse price in each pool.
For more background information, see this blog post about the topic
For easier access to data, see also Trading Strategy historical datasets where you can batch download historical data in one file
You need to understand Jupyter Notebook and Pandas basics
You need to understand Ethereum and Web3.py basics
You will need to have Ethereum API node and its JSON-RPC URL in order to pull out the data from Ethereum blockchain. The notebook will interactively ask you for your API key.
You can click Launch binder button on this documentation page to automatically open this notebook to be run in Binder cloud notebook service. Note that we recommend to run the notebook on your local computer, as generated CSV needs are large and take long time to generate.
Download the raw data from Ethereum blockchain
For simplicity, you can sign up for free access to an Ethereum node for example at Infura, however we recommend you to run your own Ethereum node.
[10]:
from web3 import Web3, HTTPProvider
# Get your node JSON-RPC URL
json_rpc_url = input("Please enter your Ethereum mainnet JSON-RPC URL here")
web3 = Web3(HTTPProvider(json_rpc_url))
As an example, here we download raw events from first few blocks after Uniswap V3 was deployed. The events will be stored in several CSV files at /tmp
folder.
Depends on your internet connection and latency to the Ethereum node, the scan might take hours. However it can resume in case there is a crash, as we save the last scanned block in a JSON state file.
[11]:
from eth_defi.uniswap_v3.constants import UNISWAP_V3_FACTORY_CREATED_AT_BLOCK
from eth_defi.uniswap_v3.events import fetch_events_to_csv
from eth_defi.event_reader.json_state import JSONFileScanState
start_block = UNISWAP_V3_FACTORY_CREATED_AT_BLOCK
end_block = UNISWAP_V3_FACTORY_CREATED_AT_BLOCK + 250_000
# Stores the last block number of event data we store
state = JSONFileScanState("/tmp/uniswap-v3-price-scan.json")
fetch_events_to_csv(json_rpc_url, state, start_block=start_block, end_block=end_block)
Restored previous scan state, data until block 12,619,621, we are skipping 250,000 blocks out of 250,000 total
Scanning block range 12,619,621 - 12,619,621
Wrote 0 PoolCreated events
Wrote 0 Swap events
Wrote 0 Mint events
Wrote 0 Burn events
Analysing Uniswap v3 price formation
In Uniswap V3, you can get the current price of any pool from any given moment using swap events.
[12]:
import pandas as pd
swap_df = pd.read_csv("/tmp/uniswap-v3-swap.csv")
print(f"We have total {len(swap_df):,} swaps in the dataset")
We have total 1,229,361 swaps in the dataset
Choose a pool to analyse, for example: USDC/ETH 0.3%. You can find pool addresses on Uniswap v3 info site.
[13]:
from eth_defi.uniswap_v3.pool import fetch_pool_details
pool_address = "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"
pool_details = fetch_pool_details(web3, pool_address)
print(pool_details)
print("token0 is", pool_details.token0, "with", pool_details.token0.decimals, "decimals")
print("token1 is", pool_details.token1, "with", pool_details.token1.decimals, "decimals")
Pool 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8 is USDC-WETH, with the fee 0.3000%
token0 is <USD Coin (USDC) at 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48> with 6 decimals
token1 is <Wrapped Ether (WETH) at 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2> with 18 decimals
Extract the swaps of this pool from the Uniswap v3 full dataset of all pools.
[14]:
df = swap_df.loc[swap_df.pool_contract_address == pool_address.lower()]
df
[14]:
block_number | timestamp | tx_hash | log_index | pool_contract_address | amount0 | amount1 | sqrt_price_x96 | liquidity | tick | |
---|---|---|---|---|---|---|---|---|---|---|
19 | 12371376 | 2021-05-05T01:56:23 | 0xce7c3c307d820785caa12938012372fc9366a614a6aa... | 26 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | -329608 | 100000000000000 | 1377932816571815120446551350158799 | 4303369674465501 | 195285 |
21 | 12373132 | 2021-05-05T08:23:26 | 0x9a1c51b0bffbf840948f3b6e3f3e495ba1cd3fa64854... | 192 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | -164694492 | 50000000000000000 | 1378850591292581266780357299649652 | 4303369674465501 | 195298 |
25 | 12373520 | 2021-05-05T09:50:51 | 0xc58715c62a5bf70a6ca09f0e51546d6cad76c8d4fff0... | 8 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | -329169 | 100000000000000 | 1378852426842022799073024911548633 | 4303369674465501 | 195298 |
26 | 12374077 | 2021-05-05T11:59:57 | 0x288c21b8b4fbf449b1d086a06e43b124ac2bc088c3f5... | 86 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | 2 | -329169 | 1378852426842016741051966412054516 | 4304946248093346 | 195298 |
27 | 12374320 | 2021-05-05T12:56:56 | 0x67502d8ba373287f6d301f6baa77c5a5f4c80d0753c3... | 257 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | 1559137299 | -467880854065813753 | 1370241555019945317645788135487819 | 4304946248093346 | 195173 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1229165 | 12619596 | 2021-06-12T12:16:13 | 0x8c1f6c5639b304eb0087c2b6d5b65e32ee4f1a51e8b0... | 112 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | 92432639474 | -38329545577539549888 | 1615736234513681125600943820478722 | 25998097729965548918 | 198469 |
1229184 | 12619598 | 2021-06-12T12:16:25 | 0xac8e34aa704a1ab4068bd93d4b5aef0fb7655d292fc9... | 3 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | 76760509615 | -31826486546079303210 | 1615639241428978886102798535892145 | 25997255949450154096 | 198468 |
1229186 | 12619598 | 2021-06-12T12:16:25 | 0x0752c6529f939f046c7f9648e8d27d76b1cf767d6902... | 8 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | 482579746068 | -200000000000000000000 | 1615029729696907486273078115160620 | 25997255949450154096 | 198460 |
1229190 | 12619598 | 2021-06-12T12:16:25 | 0xbd9193ff70e4828a43679f955c9707fa63fe1dabeed3... | 45 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | 679981136491 | -281554906013205614934 | 1614171674604720940565192048438458 | 25997255949450154096 | 198449 |
1229350 | 12619619 | 2021-06-12T12:21:51 | 0x91f7b948333f452324270b09d6cb074b69dda7647f4c... | 281 | 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 | 8388626 | -3471572647773651 | 1614171664025625771996673894396803 | 25999040327671324243 | 198449 |
64090 rows × 10 columns
In Uniswap V3, we can get pool’s price from pool’s tick.
[15]:
def convert_price(row):
# USDC/WETH pool has reverse token order, so let's flip it WETH/USDC
tick = row["tick"]
return pool_details.convert_price_to_human(tick, reverse_token_order=True)
def convert_value(row):
# USDC is token0 and amount0
price = float(row["price"])
return abs(float(row["amount0"])) / (10**pool_details.token0.decimals)
df = df.copy(deep=True) # https://stackoverflow.com/a/60885847/315168
df["price"] = df.apply(convert_price, axis=1)
df["value"] = df.apply(convert_value, axis=1)
df[["block_number", "timestamp", "tick", "price", "value"]]
[15]:
block_number | timestamp | tick | price | value | |
---|---|---|---|---|---|
19 | 12371376 | 2021-05-05T01:56:23 | 195285 | 3306.010571 | 0.329608 |
21 | 12373132 | 2021-05-05T08:23:26 | 195298 | 3301.715764 | 164.694492 |
25 | 12373520 | 2021-05-05T09:50:51 | 195298 | 3301.715764 | 0.329169 |
26 | 12374077 | 2021-05-05T11:59:57 | 195298 | 3301.715764 | 0.000002 |
27 | 12374320 | 2021-05-05T12:56:56 | 195173 | 3343.244146 | 1559.137299 |
... | ... | ... | ... | ... | ... |
1229165 | 12619596 | 2021-06-12T12:16:13 | 198469 | 2404.538795 | 92432.639474 |
1229184 | 12619598 | 2021-06-12T12:16:25 | 198468 | 2404.779249 | 76760.509615 |
1229186 | 12619598 | 2021-06-12T12:16:25 | 198460 | 2406.703745 | 482579.746068 |
1229190 | 12619598 | 2021-06-12T12:16:25 | 198449 | 2409.352444 | 679981.136491 |
1229350 | 12619619 | 2021-06-12T12:21:51 | 198449 | 2409.352444 | 8.388626 |
64090 rows × 5 columns
Then we can convert linear price data to OHLC candles.
[16]:
from eth_defi.research.candle import convert_to_ohlcv_candles
candles = convert_to_ohlcv_candles(df, time_bucket=pd.Timedelta("4h"))
# Show only 100 first candles
candles = candles.head(100)
candles
[16]:
open | high | low | close | volume | |
---|---|---|---|---|---|
timestamp | |||||
2021-05-05 00:00:00 | 3306.010571 | 3306.010571 | 3306.010571 | 3306.010571 | 3.296080e-01 |
2021-05-05 04:00:00 | NaN | NaN | NaN | NaN | 0.000000e+00 |
2021-05-05 08:00:00 | 3301.715764 | 3301.715764 | 3301.715764 | 3301.715764 | 1.650237e+02 |
2021-05-05 12:00:00 | 3343.244146 | 3343.244146 | 3343.244146 | 3343.244146 | 1.559137e+03 |
2021-05-05 16:00:00 | 3343.244146 | 3476.895080 | 3343.244146 | 3430.617750 | 1.393686e+05 |
... | ... | ... | ... | ... | ... |
2021-05-20 20:00:00 | 2789.467427 | 2890.845172 | 2668.863966 | 2775.833150 | 1.082835e+08 |
2021-05-21 00:00:00 | 2775.833150 | 2924.572530 | 2775.833150 | 2797.008788 | 4.045975e+07 |
2021-05-21 04:00:00 | 2792.816630 | 2809.342124 | 2694.068302 | 2694.068302 | 4.383654e+07 |
2021-05-21 08:00:00 | 2691.375715 | 2771.395590 | 2638.085543 | 2707.571699 | 4.017013e+07 |
2021-05-21 12:00:00 | 2709.738515 | 2750.138850 | 2388.005409 | 2420.944555 | 1.300869e+08 |
100 rows × 5 columns
Drawing OHLCV chart
Now we can plot the OHLC chart using Plotly.
[17]:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
candlesticks = go.Candlestick(
x=candles.index,
open=candles['open'],
high=candles['high'],
low=candles['low'],
close=candles['close'],
showlegend=False
)
volume_bars = go.Bar(
x=candles.index,
y=candles['volume'],
showlegend=False,
marker={
"color": "rgba(128,128,128,0.5)",
}
)
fig = go.Figure(candlesticks)
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(candlesticks, secondary_y=True)
fig.add_trace(volume_bars, secondary_y=False)
fig.update_layout(title="ETH/USDC pool after Uniswap v3 deployment", height=800)
fig.update_yaxes(title="Price $", secondary_y=True, showgrid=True)
fig.update_yaxes(title="Volume $", secondary_y=False, showgrid=False)
fig.show()