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.

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()