uniswap v3 calculator code for public view

This commit is contained in:
Eric Stone 2021-09-23 10:37:19 -04:00 committed by GitHub
parent 62cf960de9
commit c3f1039095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1185 additions and 0 deletions

169
uniswap_v3/global.R Normal file
View File

@ -0,0 +1,169 @@
library(data.table)
library(shiny)
library(ggplot2)
library(scales)
library(shinyWidgets)
library(plotly)
library(bslib)
require(reshape2)
require(dplyr)
require(RPostgreSQL)
library(RJSONIO)
library(stringr)
library(showtext)
font_add_google(name = "Roboto Condensed", family = "roboto-condensed")
font_add_google(name = "Roboto Mono", family = "roboto-mono")
showtext_auto()
swap.sd.constant <- 6
smartRound <- function(x) {
if(x == 0) return(0)
if(x < 5 & x > 0.1) {
return(round(x, 4))
} else {
return(round(x, which(x * (10^(-2:20)) > 1)[1] - 2))
}
}
round_any <- function(x, accuracy, f=round){f(x/ accuracy) * accuracy}
# liquidity funcitons, translated from from the uniswap nf position manager contract:
getLiquidityForAmount0 <- function(amount0, lower, upper) {
amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
}
getLiquidityForAmount1 <- function(amount1, lower, upper) {
amount1 / (sqrt(upper) - sqrt(lower))
}
getLiquidityForAmounts <- function(amount0, amount1, cprice, lower, upper) {
sqrtRatioX96 <- cprice
sqrtRatioAX96 <- lower
sqrtRatioBX96 <- upper
# if the current price is below the lower bound:
#if (sqrtRatioX96 <= sqrtRatioAX96) {
if(cprice <= lower) {
liquidity <- getLiquidityForAmount0(lower, upper, amount0)
} else if (sqrtRatioX96 < sqrtRatioBX96) {
liquidity0 = getLiquidityForAmount0(amount0, sqrtRatioX96, sqrtRatioBX96)
liquidity1 = getLiquidityForAmount1(amount1, sqrtRatioAX96, sqrtRatioX96)
liquidity = ifelse(liquidity0 < liquidity1, liquidity0, liquidity1)
} else {
liquidity = getLiquidityForAmount1(amount1, sqrtRatioAX96, sqrtRatioBX96);
}
return(liquidity)
}
#-------------- ggplot THEMEs:
theme_univ3 <- function(base_size = 14,
bgcolor.dark = "white",
bgcolor.light = "white",
text.color = "#637381",
title.color = "#212B36",
lines.color = "#EDEDF3") {
half_line <- base_size/2
theme(text = element_text(family = 'roboto-mono',
face = "plain",
colour = text.color, size = base_size,
lineheight = 0.9, hjust = 0.5,
vjust = 0.5, angle = 0,
margin = margin(), debug = FALSE),
plot.background = element_rect(color = NA, fill = "transparent"),
plot.title = element_text(size = rel(1.2),
color = title.color,
margin = margin(b = half_line/2)),
strip.background = element_rect(fill = bgcolor.dark, colour = NA),
strip.text = element_text(colour = text.color, size = rel(0.8)),
strip.text.x = element_text(margin = margin(t = half_line/2,
b = half_line/2)),
strip.text.y = element_text(angle = -90,
margin = margin(l = half_line/2,
r = half_line/2)),
axis.line = element_blank(),
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(),
axis.text = element_text(color = text.color, size = base_size - 2),
panel.background = element_rect(fill = "transparent", colour = NA),
panel.border = element_blank(),
panel.grid.major.x = element_line(colour = 'white', linetype = 1, size = 0.7),
panel.grid.major.y = element_line(colour = 'white', linetype = 1, size = 0.7),
panel.grid.minor.x = element_blank(),
panel.grid.minor.y = element_blank(),
legend.background = element_rect(colour = NA, fill = "transparent"),
legend.key = element_rect(colour = NA, fill = "transparent"))
}
# same theme but smaller
theme_univ3_small <- function(base_size = 12,
bgcolor.dark = "white",
bgcolor.light = "white",
text.color = "#637381",
title.color = "#212B36",
lines.color = "#EDEDF3") {
half_line <- base_size/2
theme(text = element_text(family = 'roboto-mono',
face = "plain",
colour = text.color, size = base_size,
lineheight = 0.9, hjust = 0.5,
vjust = 0.5, angle = 0,
margin = margin(), debug = FALSE),
plot.background = element_rect(color = NA, fill = "transparent"),
plot.title = element_text(size = rel(1.2),
color = title.color,
margin = margin(b = half_line/2)),
strip.background = element_rect(fill = bgcolor.dark, colour = NA),
strip.text = element_text(colour = text.color, size = rel(0.8)),
strip.text.x = element_text(margin = margin(t = half_line/2,
b = half_line/2)),
strip.text.y = element_text(angle = -90,
margin = margin(l = half_line/2,
r = half_line/2)),
axis.line = element_blank(),
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(),
axis.text = element_text(color = text.color, size = base_size - 2),
panel.background = element_rect(fill = "transparent", colour = NA),
panel.border = element_blank(),
panel.grid.major.x = element_line(colour = 'white', linetype = 1, size = 0.7),
panel.grid.major.y = element_line(colour = 'white', linetype = 1, size = 0.7),
panel.grid.minor.x = element_blank(),
panel.grid.minor.y = element_blank(),
legend.background = element_rect(colour = NA, fill = "transparent"),
legend.key = element_rect(colour = NA, fill = "transparent"),
axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.title.y=element_blank(),
axis.text.y=element_blank())
}

564
uniswap_v3/server.R Normal file
View File

@ -0,0 +1,564 @@
server <- function(input, output, session) {
load("data.RData")
output$poolselect <- renderUI({
selectInput(inputId = 'poolname', label = "Select Pool",
selected = "USDC-USDT 3000 60",
choices = pool.choices)
})
# reverse token prices depending on which token price is larger
revTokes <- reactive({
ifelse(pool.stats[pool_name == input$poolname][1]$price_0_1 <
pool.stats[pool_name == input$poolname][1]$price_1_0,
TRUE, FALSE)
})
poolToken1 <- reactive({
ifelse(revTokes(),
strsplit(strsplit(input$poolname, " ", fixed = TRUE)[[1]][1], "-", fixed = TRUE)[[1]][1],
strsplit(strsplit(input$poolname, " ", fixed = TRUE)[[1]][1], "-", fixed = TRUE)[[1]][2])
})
poolToken2 <- reactive({
ifelse(revTokes(),
strsplit(strsplit(input$poolname, " ", fixed = TRUE)[[1]][1], "-", fixed = TRUE)[[1]][2],
strsplit(strsplit(input$poolname, " ", fixed = TRUE)[[1]][1], "-", fixed = TRUE)[[1]][1])
})
output$n_open_positions <- renderText({
paste0(nrow(current.positions[pool_name == input$poolname]), " Open Positions")
})
output$poolfee <- renderText({
paste0(current.positions[pool_name == input$poolname]$fee[1] / 10^6 * 100, "%")
})
output$moveprice <- renderUI({
if(revTokes()) {
tmp.min <- swapSDs()$lower_bound_10
tmp.max <- swapSDs()$upper_bound_10
} else {
tmp.min <- swapSDs()$lower_bound_01
tmp.max <- swapSDs()$upper_bound_01
}
sliderInput(
inputId = "moveprice", "Move Active Price Assumption",
min = ifelse(tmp.min > 10, floor(tmp.min), smartRound(tmp.min)),
max = ifelse(tmp.max > 10, ceiling(tmp.max), smartRound(tmp.max)),
value = getCurrentPrice(),
step = smartRound(abs(tmp.max - tmp.min) / 1000)
)
})
output$lcurveselect <- renderUI({
if(revTokes()) {
tmp.min2 <- swapSDs()$lower_2sd_10
tmp.max2 <- swapSDs()$upper_2sd_10
tmp.min <- swapSDs()$lower_bound_10
tmp.max <- swapSDs()$upper_bound_10
} else {
tmp.min2 <- swapSDs()$lower_2sd_01
tmp.max2 <- swapSDs()$upper_2sd_01
tmp.min <- swapSDs()$lower_bound_01
tmp.max <- swapSDs()$upper_bound_01
}
sliderInput(inputId = "lcurveslider", "Select Liquidity Bounds",
min = ifelse(tmp.min > 10, floor(tmp.min), smartRound(tmp.min)),
max = ifelse(tmp.max > 10, ceiling(tmp.max), smartRound(tmp.max)),
value = c(ifelse(tmp.min > 10, floor(tmp.min2), smartRound(tmp.min)),
ifelse(tmp.max > 10, ceiling(tmp.max2), smartRound(tmp.max))),
step = smartRound(abs(tmp.max - tmp.min) / 1000))
})
output$dates <- renderUI({
sliderInput("daterange",
"Select Swaps Date Range",
min = as.Date(min(swaps$block_timestamp),"%Y-%m-%d"),
max = as.Date(max(swaps$block_timestamp),"%Y-%m-%d"),
value = c(as.Date(min(swaps$block_timestamp),"%Y-%m-%d"),
max = as.Date(max(swaps$block_timestamp),"%Y-%m-%d")),
timeFormat="%Y-%m-%d",
dragRange = TRUE)
})
getCurrentPrice <- reactive({
max.swap <- max(swaps[pool_name == input$poolname]$block_id)
c.price <- ifelse(revTokes(),
swaps[pool_name == input$poolname & block_id == max.swap]$price_1_0[1],
swaps[pool_name == input$poolname & block_id == max.swap]$price_0_1[1])
ifelse(c.price > 1, round(c.price, 2), smartRound(c.price))
return(c.price)
})
output$current_price <- renderText({
paste0("Current Price: ", getCurrentPrice(), " ", poolToken2(), " per ", poolToken1())
})
output$last_update <- renderText({
paste0("Last Pool Update: ", gsub(x = gsub(x = as.character(max(pool.stats[pool_name == input$poolname]$block_timestamp)), "T", " ", fixed = T), "Z", "", fixed = TRUE), " UTC")
})
output$swapvol24h <- renderText({
usd.last.day <- swaps[block_timestamp > max(formattedSwapData()$block_timestamp) - (60*60*24) & pool_name == input$poolname,
ifelse(amount0_usd < 0, abs(amount0_usd), abs(amount1_usd))]
paste0("24 Hour Swap Volume: $", formatC(round(sum(abs(usd.last.day))), format="f", digits=0, big.mark=","))
})
output$swapvol24h2 <- renderText({
usd.last.day <- formattedSwapData()[block_timestamp > max(formattedSwapData()$block_timestamp) - (60*60*24)]$amount0_usd
formatC(round(sum(abs(usd.last.day))), format="f", digits=0, big.mark=",")
})
output$pool_token1 <- renderText({
paste0("Token 1: ", poolToken1())
})
output$pool_token2 <- renderText({
paste0("Token 2: ", poolToken2())
})
poolAddy <- reactive({
current.positions[pool_name == input$poolname]$pool_address[1]
})
poolStats <- reactive({
pool.stats[pool_name == input$poolname]
})
formattedSwapData <- reactive({
swaps[pool_name == input$poolname &
block_timestamp >= as.POSIXct(input$daterange[1]) &
block_timestamp <= as.POSIXct(input$daterange[2] + 1)]
})
# get the boundaries for whatever sd's away from mean swaps:
swapSDs <- reactive({
data.table(lower_bound_01 = max(0.0000001, mean(swaps[pool_name == input$poolname]$price_0_1) + (-swap.sd.constant)*sd(swaps[pool_name == input$poolname]$price_0_1)),
lower_2sd_01 = max(0.0000001, mean(swaps[pool_name == input$poolname]$price_0_1) + (-swap.sd.constant/2)*sd(swaps[pool_name == input$poolname]$price_0_1)),
upper_bound_01 = mean(swaps[pool_name == input$poolname]$price_0_1) + (swap.sd.constant)*sd(swaps[pool_name == input$poolname]$price_0_1),
upper_2sd_01 = mean(swaps[pool_name == input$poolname]$price_0_1) + (swap.sd.constant/2)*sd(swaps[pool_name == input$poolname]$price_0_1),
lower_bound_10 = max(0.0000001, mean(swaps[pool_name == input$poolname]$price_1_0) + (-swap.sd.constant)*sd(swaps[pool_name == input$poolname]$price_1_0)),
lower_2sd_10 = max(0.0000001, mean(swaps[pool_name == input$poolname]$price_1_0) + (-swap.sd.constant/2)*sd(swaps[pool_name == input$poolname]$price_1_0)),
upper_bound_10 = mean(swaps[pool_name == input$poolname]$price_1_0) + (swap.sd.constant)*sd(swaps[pool_name == input$poolname]$price_1_0),
upper_2sd_10 = mean(swaps[pool_name == input$poolname]$price_1_0) + (swap.sd.constant/2)*sd(swaps[pool_name == input$poolname]$price_1_0)
)
})
# only keep positions that overlap with the avg last 3 days of trading +/- 5sd
goodPoolPos <- reactive({
if(revTokes()) {
this.pool.good.pos <- current.positions[pool_name == input$poolname & liquidity_adjusted > 0 &
(price_upper_1_0 >= swapSDs()$lower_bound_10 &
price_lower_1_0 <= swapSDs()$upper_bound_10)]
} else {
this.pool.good.pos <- current.positions[pool_name == input$poolname & liquidity_adjusted > 0 &
(price_upper_0_1 >= swapSDs()$lower_bound_01 &
price_lower_0_1 <= swapSDs()$upper_bound_01)]
}
return(this.pool.good.pos)
})
# make data to do the existing liquidity plot
# we'll "spread out" each position over its ticks
summarizeLiquidity <- reactive({
n.price.bins <- swap.sd.constant * 20
tick.dec.adj <- poolStats()[1]$pool_dec_adj
if(revTokes()) {
closest.lower.tick <- floor( log( swapSDs()$lower_bound_10 * (10^tick.dec.adj), 1.0001))
closest.upper.tick <- ceiling( log( swapSDs()$upper_bound_10 * (10^tick.dec.adj), 1.0001) )
} else {
closest.lower.tick <- floor( log( 1 / swapSDs()$upper_bound_01 * (10^tick.dec.adj), 1.0001) )
closest.upper.tick <- ceiling( log( 1 / swapSDs()$lower_bound_01 * (10^tick.dec.adj), 1.0001) )
}
unique.positions <- goodPoolPos()
bin.size <- ifelse(revTokes(),
(swapSDs()$upper_bound_10 - swapSDs()$lower_bound_10) / (swap.sd.constant * 20),
(swapSDs()$upper_bound_01 - swapSDs()$lower_bound_01) / (swap.sd.constant * 20))
unique.positions[, bin_size := bin.size]
if(revTokes()) {
unique.positions[, avg_chunk_liq := liquidity_adjusted / ((price_upper_1_0 - price_lower_1_0) / bin_size)]
} else {
unique.positions[, avg_chunk_liq := liquidity_adjusted / ((price_upper_0_1 - price_lower_0_1) / bin_size)]
}
unique.positions <- unique.positions[, list(liquidity_by_pos = sum(avg_chunk_liq),
liquidity_max = sum(liquidity_adjusted)),
by = "tick_lower,tick_upper,price_lower_0_1,price_upper_0_1"]
# the tick to price, undo it and get the right dec adjustment:
tick.spacing <- ceiling(abs((closest.upper.tick - closest.lower.tick)/n.price.bins))
by.tick <- data.table(tick = seq(closest.lower.tick, closest.upper.tick,
by = tick.spacing))
if(revTokes()) {
extra.tick.price <- (((1.0001^(closest.upper.tick + tick.spacing)) / (10^tick.dec.adj)))
} else {
extra.tick.price <- 1 / (((1.0001^(ifelse(closest.lower.tick < 0, closest.upper.tick, closest.lower.tick) + tick.spacing)) / (10^tick.dec.adj)))
}
# merge trick:
unique.positions[, m := 1]
by.tick[, m := 1]
by.tick <- merge(by.tick, unique.positions, by = "m", all = TRUE, allow.cartesian = TRUE)
if(revTokes()) {
by.tick[, tick_price := (((1.0001^tick) / (10^tick.dec.adj)))]
} else {
by.tick[, tick_price := 1 / (((1.0001^tick) / (10^tick.dec.adj)))]
}
by.tick <- by.tick[ (tick >= tick_lower) & (tick <= tick_upper)]
if(revTokes()) {
liquidity.by.tick <- by.tick[, list(max_liquidity = sum(liquidity_max),
avg_liquidity = sum(liquidity_by_pos, na.rm = TRUE)),
by = "tick,tick_price"] %>%
.[tick_price > swapSDs()$lower_bound_10 & tick_price < swapSDs()$upper_bound_10]
liquidity.by.tick[, tick_end := c(liquidity.by.tick$tick_price[2:length(liquidity.by.tick$tick_price)], extra.tick.price)]
} else {
liquidity.by.tick <- by.tick[, list(max_liquidity = sum(liquidity_max),
avg_liquidity = sum(liquidity_by_pos, na.rm = TRUE)),
by = "tick,tick_price"] %>%
.[tick_price > swapSDs()$lower_bound_01 & tick_price < swapSDs()$upper_bound_01]
liquidity.by.tick[, tick_end := c(liquidity.by.tick$tick_price[2:length(liquidity.by.tick$tick_price)], extra.tick.price)]
}
liquidity.by.tick
})
output$lcurve_plot <- renderPlot({
tmp.data <- formattedSwapData()
if(revTokes()) {
tmp.data[, covered := ifelse(price_1_0 >= input$lcurveslider[1] & price_1_0 <= input$lcurveslider[2],
"covered", "out")]
lcurve.plot <- ggplot() +
theme_univ3() +
geom_line(data = tmp.data,
aes(x = price_1_0, y = price_0_1, alpha = covered), size = 6, color = "#01c3b3") +
geom_point(data = tmp.data,
aes(x = price_1_0, y = price_0_1)) +
geom_point(data = data.table(price_1_0 = input$moveprice, price_0_1 = (1/input$moveprice), covered = 'covered'),
aes(x = price_1_0, y = price_0_1),
size = 5, color = 'white') +
labs(y = paste0("\n", poolToken1(), " per ", poolToken2()),
x = paste0(poolToken2(), " per ", poolToken1(), "\n")) +
theme(legend.position = 'none') +
scale_alpha_manual(values = c(1, 0))
} else {
tmp.data[, covered := ifelse(price_0_1 >= input$lcurveslider[1] & price_0_1 <= input$lcurveslider[2],
"covered", "out")]
lcurve.plot <- ggplot() +
theme_univ3() +
geom_line(data = tmp.data,
aes(x = price_0_1, y = price_1_0, alpha = covered), size = 6, color = "#01c3b3") +
geom_point(data = tmp.data,
aes(x = price_0_1, y = price_1_0)) +
geom_point(data = data.table(price_0_1 = input$moveprice, price_1_0 = (1/input$moveprice), covered = 'covered'),
aes(x = price_0_1, y = price_1_0),
size = 5, color = 'white') +
labs(y = paste0("\n", poolToken1(), " per ", poolToken2()),
x = paste0(poolToken2(), " per ", poolToken1(), "\n")) +
theme(legend.position = 'none') +
scale_alpha_manual(values = c(1, 0))
}
return(lcurve.plot)
}, bg = "transparent")
output$concentration_view_plot <- renderPlot({
ggplot() +
geom_rect(data = summarizeLiquidity(),
aes(xmin = tick_price, xmax = tick_end, ymin = 0, ymax = avg_liquidity),
fill = "grey80") +
geom_rect(data = summarizeLiquidity()[tick_price >= input$lcurveslider[1] &
tick_end <= input$lcurveslider[2]],
aes(xmin = tick_price, xmax = tick_end, ymin = 0, ymax = avg_liquidity),
fill = "#01c3b3") +
geom_vline(xintercept = input$moveprice, color = "black", linetype = 2) +
# geom_vline(xintercept = input$lcurveslider[1], color = "#F8ABA7") +
# geom_vline(xintercept = input$lcurveslider[2], color = "#F8ABA7") +
theme_univ3() +
geom_text(data = data.table(x = input$moveprice, y = 0,
label = " active swap price", xmin = 1, xmax = 1, ymin = 1, ymax = 1),
aes(x = x, y = y, label = label),
hjust = 0, vjust = -1, family = "roboto-mono") +
labs(x = paste0(poolToken2(), " per ", poolToken1(), "\n"), y = "Existing Liquidity")
}, bg = "transparent")
output$timepriceplot <- renderPlot({
plot.data <- swaps[pool_name == input$poolname &
as.Date(block_timestamp) >= min(input$daterange) &
as.Date(block_timestamp) <= max(input$daterange) ]
if(revTokes()) {
min.max.data <- rbind(plot.data[which.min(plot.data$price_1_0), list(block_timestamp, price = smartRound(price_1_0))],
plot.data[which.max(plot.data$price_1_0), list(block_timestamp, price = smartRound(price_1_0))])
plot.data$price_0_1 <- plot.data$price_1_0
.limits <- c(swapSDs()$lower_bound_10*.99, swapSDs()$upper_bound_10*1.01)
} else {
min.max.data <- rbind(plot.data[which.min(plot.data$price_0_1), list(block_timestamp, price = smartRound(price_0_1))],
plot.data[which.max(plot.data$price_0_1), list(block_timestamp, price = smartRound(price_0_1))])
.limits <- c(swapSDs()$lower_bound_01*.99, swapSDs()$upper_bound_01*1.01)
}
ggplot() +
geom_rect(data = data.table(xmin = min(plot.data$block_timestamp),
xmax = max(plot.data$block_timestamp),
ymin = input$lcurveslider[1],
ymax = input$lcurveslider[2]),
aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
fill = "#01c3b3", alpha = 0.5) +
geom_point(data = plot.data,
aes(x = block_timestamp, y = price_0_1), size = 0.6) +
geom_label(data = min.max.data,
aes(x = block_timestamp, y = price, label = price),
color = "black", fill = "#e7d3f2", family = "roboto-mono", hjust = 0, vjust = 1, size = 6) +
theme_univ3_small() +
scale_y_continuous(limits = .limits) +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank()) +
scale_x_datetime(expand = c(0, 0))
},
height = 130,
bg = "transparent")
output$swaps5days <- renderPlot({
plot.data <- swaps[pool_name == input$poolname &
swap_date >= Sys.Date() - 4 &
swap_date <= Sys.Date() &
!is.na(amount0_usd) &
!is.na(amount1_usd),
sum(ifelse(amount0_usd < 0, abs(amount0_usd), abs(amount1_usd))),
by = swap_date]
plot.data[, label := round(V1/1000000, 2)]
tmp.plot <- ggplot(plot.data,
aes(x = swap_date, y = V1)) +
geom_bar(stat = "identity", fill = "#01c3b3") +
geom_text(aes(x = swap_date, y = V1, label = label),
color = "black", family = "roboto-mono", size = 7, vjust = 1.25, color = "white") +
labs(y = NULL, x = NULL) +
theme_univ3_small()
return(tmp.plot)
},
height = 130,
bg = "transparent")
output$lcurve_prop_covered <- renderText({
if(revTokes()) {
prop <- round( (sum(abs(formattedSwapData()[price_1_0 >= input$lcurveslider[1] & price_1_0 <= input$lcurveslider[2]]$amount1_adjusted)) /
sum(abs(formattedSwapData()$amount1_adjusted))) *100)
} else {
prop <- round( (sum(abs(formattedSwapData()[price_0_1 >= input$lcurveslider[1] & price_0_1 <= input$lcurveslider[2]]$amount0_adjusted)) /
sum(abs(formattedSwapData()$amount0_adjusted))) *100)
}
paste(prop, "% Swap Volume Covered")
})
# figure out the token amounts
# this is also taken from the position manager contract
tokenAmounts <- reactive({
token0.price <- poolStats()$token0_balance_usd / poolStats()$token0_balance_adjusted
token1.price <- poolStats()$token1_balance_usd / poolStats()$token1_balance_adjusted
if(!revTokes()) {
price.sqrt <- sqrt(1 / input$moveprice)
price.upper.sqrt <- sqrt(1 / input$lcurveslider[1])
price.lower.sqrt <- sqrt(1 / input$lcurveslider[2])
} else {
price.sqrt <- sqrt(input$moveprice)
price.lower.sqrt <- sqrt(input$lcurveslider[1])
price.upper.sqrt <- sqrt(input$lcurveslider[2])
}
relation <- (price.sqrt - price.lower.sqrt) / ( (1 / price.sqrt) - (1 / price.upper.sqrt) )
token0_amt <- input$lpamt / (token0.price + relation * token1.price)
token1_amt <- token0_amt * relation
tmp <- data.table(token0.price = token0.price,
token1.price = token1.price,
token0_amt = token0_amt,
token1_amt = token1_amt)
print(tmp)
print(token0_amt * token0.price + token1_amt * token1.price)
return(tmp)
})
# total liquidity for the selected position
posTotalVL <- reactive({
if(!revTokes()) {
pos.total.vl <- getLiquidityForAmounts(tokenAmounts()$token1_amt, tokenAmounts()$token0_amt, input$moveprice, input$lcurveslider[1], input$lcurveslider[2])
} else {
pos.total.vl <- getLiquidityForAmounts(tokenAmounts()$token0_amt, tokenAmounts()$token1_amt, input$moveprice, input$lcurveslider[1], input$lcurveslider[2])
}
return(pos.total.vl)
})
findLPexisting <- reactive({
if(revTokes()) {
in.positions <- current.positions[pool_name == input$poolname & price_lower_1_0 <= input$moveprice & price_upper_1_0 >= input$moveprice]
} else {
in.positions <- current.positions[pool_name == input$poolname & price_lower_0_1 <= input$moveprice & price_upper_0_1 >= input$moveprice]
}
#in.positions <- current.positions[pool_name == input$poolname]
return(sum(in.positions$liquidity_adjusted))
})
output$investamt <- renderUI({
shinyWidgets::numericInputIcon("lpamt",
NULL,
value = 777,
step= 0.01)
})
output$simpledailyfee <- renderText({
if(input$lcurveslider[1] > input$moveprice |
input$lcurveslider[2] < input$moveprice) {
tmp <- "$0 (0%)"
} else {
tmp.vol.usd <- swaps[pool_name == input$poolname & block_timestamp > max(swaps[pool_name == input$poolname]$block_timestamp) - (60*60*24)] %>%
.[, ifelse(amount0_usd > 0, abs(amount1_usd), abs(amount0_usd))] %>%
sum()
pool.fee <- poolStats()$fee[1] / 10^6
est.fee <- round(( posTotalVL() / (posTotalVL() + findLPexisting()) ) * tmp.vol.usd * pool.fee, 2)
annual.prop <- round((est.fee*365 / input$lpamt) * 100, 2)
tmp <- paste0("$", est.fee, " (", annual.prop, "%)")
}
tmp
})
output$simpledailyfee2 <- renderText({
if(input$lcurveslider[1] > input$moveprice |
input$lcurveslider[2] < input$moveprice) {
tmp <- "$0 (0%)"
} else {
tmp.vol.usd <- formattedSwapData()[block_timestamp > max(formattedSwapData()$block_timestamp) - (60*60*24)] %>%
.[, ifelse(amount0_usd > 0, abs(amount1_usd), abs(amount0_usd))] %>%
sum()
pool.fee <- poolStats()$fee[1] / 10^6
est.fee <- round(( posTotalVL() / (posTotalVL() + findLPexisting()) ) * tmp.vol.usd * pool.fee, 2)
annual.prop <- round((est.fee*365 / input$lpamt) * 100, 2)
tmp <- paste0("$", est.fee, " (", annual.prop, "%)")
}
tmp
})
output$posVLProp <- renderText({
paste0(round(posTotalVL() / (posTotalVL() + findLPexisting())*100, 5), "% of Total Virtual Liquidity")
})
output$yourVL <- renderText({
posTotalVL()
})
output$alltickVL <- renderText({
findLPexisting()
})
output$token0amt <- renderText({
paste0(round(tokenAmounts()$token0_amt, 2), " ", ifelse(!revTokes(), poolToken2(), poolToken1()),
" ($", round(tokenAmounts()$token0_amt * tokenAmounts()$token0.price, 2), ")")
})
output$token1amt <- renderText({
paste0(round(tokenAmounts()$token1_amt, 2), " ", ifelse(!revTokes(), poolToken1(), poolToken2()),
" ($", round(tokenAmounts()$token1_amt * tokenAmounts()$token1.price, 2), ")")
})
output$link2uniswap <- renderUI({
# copy this link:
#https://app.uniswap.org/#/add/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2/3000
HTML(paste0('<a href = "',
paste0("https://info.uniswap.org/#/pools/", poolAddy()),
'" target = "_blank"> 🦄 </a>'))
})
}

159
uniswap_v3/ui.R Normal file
View File

@ -0,0 +1,159 @@
fluidPage(
title = "Flipside: Uniswap V3 Fee Calculator",
theme = bs_theme(
version = version_default(),
bootswatch = NULL,
bg = "#f2defe", fg = "#110403", primary = "#01c3b3", secondary = "#f2defe",
base_font = font_google("Roboto Mono"),
code_font = font_google("Roboto Mono"),
heading_font = font_google("Roboto Mono")
),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "shinycss.css"),
tags$link(rel = "icon", href = "fliptrans.png")
),
tags$style(type="text/css",
".shiny-output-error { visibility: hidden; }",
".shiny-output-error:before { visibility: hidden; }"
),
fluidRow(
column(6,
div(h2(a(href = "http://www.flipsidecrypto.com", target = "_blank", img(src = 'fliptrans.png', width = '50px')), "Uniswap V3 Fee Calculator"))),
column(
6,
div(h5(
a(href = "http://app.flipsidecrypto.com/",
target = "_blank", "Data from Velocity by Flipside Crypto"),
a(href = "https://twitter.com/flipsidecrypto", target = "_blank",
img(src = 'twitter.png', width = '30px')),
a(href = "https://flipsidecrypto.com/discord", target = "_blank",
img(src = 'discord.png', width = '35px')),
style = "font-style: italic; font-weight: 150; text-align: right;"),
style = "padding-top: 21px;")
),
column(12,div("change the inputs on the left, see current swaps & liqudity + estimate fee income on the right, read disclaimer at the bottom")
) #close column
), # close intro row
hr(),
fluidRow(
column(4, class = 'leftinputs',
a(target = "_blank", uiOutput("poolselect")),
div(id = 'lildate',
p("To search, select the dropdown, hit backspace/delete and type"),
textOutput('last_update')),
div(class = 'smallstats', textOutput('n_open_positions')),
div(class = 'smallstats', textOutput('swapvol24h')),
div(class = 'smallstats', textOutput('current_price')),
hr(),
a(target = "_blank", uiOutput("moveprice")),
hr(),
a(target = "_blank", uiOutput("lcurveselect")),
hr(),
uiOutput("dates")
), # close inputs column
# this is the results / graphs column
column(8,
fluidRow(h4("Simple Daily Fee Estimate:")),
fluidRow(class = 'feedesc',
column(4,class = 'feedesc', "Investment $ Here"),
column(4,class = 'feedesc', "24h Fee $ (annual %)"),
column(2, "GO ↯")
),
fluidRow(class = 'feedesc',
column(4, class = 'centerit',
a(target = "_blank",
uiOutput('investamt'),
)),
column(4, div(id = "fee", textOutput('simpledailyfee'))),
column(2, div(id = 'uni', uiOutput("link2uniswap")))
),
hr(),
fluidRow(
column(6,h4("Swap Curve"),
textOutput("lcurve_prop_covered")),
column(6,
h4("Liquidity Positions"),
textOutput("posVLProp"))
),
fluidRow(
column(6,plotOutput("lcurve_plot", height = '350px')),
column(6,plotOutput("concentration_view_plot",height = '350px'))
),
fluidRow(
column(6,
h5("Prices Last 5 Days"),
plotOutput("timepriceplot", height = "130px")),
column(6,
h5("5 Day Swap Volume ($MM)"),
plotOutput("swaps5days", height = "130px"))
)
) # results columns
), # close inputs/results row
fluidRow(
column(12,
hr(),
div(
id = 'disclaimer',
"This is not investment advice, use it at your own risk. ",
a(href = "http://velocity-app.flipsidecrypto.com",
target = "_blank",
"DYOR Here with Velocity"),
br(),
"Questions? Join our ",
a(href = "https://flipsidecrypto.com/discord", "discord", target = "_blank"), " and let's chat.",
hr(),
"This tool presents a simple point-in-time estimate of how much you could
potentially earn in fees for providing liquidity in Uniswap V3.
It assumes no changes to swap price, swap volumes or liquidity positions for 24 hours,
which is not realistic. It does not account for Impermanent Loss.",
br(),
"Use this tool to get an idea of where you want to invest,
and directionally what your fee income could be. We strive to provide accurate information about the present and the past but make no guarantees about the future."
),
hr()
)
),
fluidRow(
column(
6,
div(h5("How Fee Income is Estimated:")),
div("Basic Formula (L = liquidity): (L_you / L_others) * (24h_swap_volume * pool_fee_rate)"),
div("Calculation Details for This Position"),
div("Liquidity for This Position: ", textOutput("yourVL")),
div("All Other Existing Liquidity (liquidity for all other positions crossing the current price tick): ", textOutput("alltickVL")),
div("24 Hour Swap Volume (across all ticks):", textOutput("swapvol24h2")),
div("Pool Fee: ", textOutput("poolfee")),
div("Fee Income: ", textOutput('simpledailyfee2')),
div("Token Amounts Needed:"),
div(textOutput("token0amt")),
div(textOutput("token1amt"))
),
column(
6,
div(h5("More on Liquidity:")),
div("The liquidity amount is calculated from the following numbers that describe a position: amount of token 0 (amt0), amount of token 1 (amt1), price (as x token 1's per token 0) at the upper limit of the position (upper), price at the lower limit of the position (lower) and the current swap price (cprice). Then liquidity for a position is calculated as follows:"),
div("Case 1: cprice <= lower"),
div("liquidity = amt0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))"),
div("Case 2: lower < cprice <= upper"),
div("liquidity is the min of the following two calculations:"),
div("amt0 * (sqrt(upper) * sqrt(cprice)) / (sqrt(upper) - sqrt(cprice))"),
div("amt1 / (sqrt(cprice) - sqrt(lower))"),
div("Case 3: upper < cprice"),
div("liquidity = amt1 / (sqrt(upper) - sqrt(lower))")
),
br()
)
) # close fluid page

165
uniswap_v3/update_data.R Normal file
View File

@ -0,0 +1,165 @@
max.blocks <- c(QuerySnowflake("SELECT max(block_id) FROM uniswapv3.swaps"),
QuerySnowflake("SELECT max(block_id) FROM uniswapv3.pool_stats"),
QuerySnowflake("SELECT max(block_id) FROM uniswapv3.positions"))
max.pull.block <- min(unlist(max.blocks))
current.positions <- QuerySnowflake(paste0("
WITH positions AS (
SELECT * FROM uniswapv3.positions WHERE block_id <= ", max.pull.block, "
),
max_blocks AS (
SELECT
max(block_id) AS block_id,
pool_address,
liquidity_provider,
nf_token_id
FROM positions
GROUP BY
pool_address,
liquidity_provider,
nf_token_id
)
SELECT
blockchain,
p.block_id,
block_timestamp,
tx_id,
fee_percent,
liquidity_adjusted,
p.liquidity_provider,
nf_position_manager_address,
p.nf_token_id,
p.pool_address,
p.pool_name,
p.tick_lower,
p.tick_upper,
p.price_lower_0_1,
p.price_lower_0_1_usd,
p.price_upper_0_1,
p.price_upper_0_1_usd,
p.price_lower_1_0,
p.price_lower_1_0_usd,
p.price_upper_1_0,
p.price_upper_1_0_usd
FROM positions p
INNER JOIN max_blocks mb ON
mb.block_id = p.block_id AND
mb.pool_address = p.pool_address AND
mb.liquidity_provider = p.liquidity_provider AND
mb.nf_token_id = p.nf_token_id
WHERE liquidity_adjusted > 0
"))
current.positions[, fee := as.numeric(strsplit(pool_name, " ", fixed = TRUE)[[1]][2]), by = pool_name ]
current.positions[, tick_spacing := as.numeric(strsplit(pool_name, " ", fixed = TRUE)[[1]][3]), by = pool_name ]
pool.stats <- QuerySnowflake(paste0("
WITH max_blocks AS (
SELECT
max(block_id) AS block_id,
pool_address
FROM
uniswapv3.pool_stats
WHERE block_id <= ", max.pull.block, "
GROUP BY
pool_address)
SELECT
ps.block_id,
block_timestamp,
ps.pool_address,
pool_name,
tick,
price_0_1,
price_1_0,
virtual_liquidity_adjusted,
virtual_reserves_token0_adjusted,
virtual_reserves_token1_adjusted,
token0_balance_adjusted,
token1_balance_adjusted,
token0_balance_usd,
token1_balance_usd,
token0_balance,
token1_balance
FROM
uniswapv3.pool_stats ps
JOIN
max_blocks mb
ON
mb.block_id = ps.block_id
AND
mb.pool_address = ps.pool_address
"))
#tx_id, block_timestamp, pool_address, pool_name, price_1_0, price_0_1, tick, amount0_adjusted, amount1_adjusted, amount0_usd, amount1_usd
swaps <- QuerySnowflake(paste0("SELECT
*
FROM
uniswapv3.swaps sw
WHERE
block_timestamp > getdate() - interval '5 days' AND block_id <= ", max.pull.block))
swaps[, swap_date := as.Date(substr(block_timestamp, 1, 10))]
# what is the price
pool.stats[, token0_dec := log(token0_balance / token0_balance_adjusted, 10)]
pool.stats[, token1_dec := log(token1_balance / token1_balance_adjusted, 10)]
pool.stats[, fee := as.numeric(strsplit(pool_name, " ", fixed = TRUE)[[1]][2]), by = pool_name ]
pool.stats[, tick_spacing := as.numeric(strsplit(pool_name, " ", fixed = TRUE)[[1]][3])]
pool.stats[, pool_dec_adj := token1_dec - token0_dec]
pool.stats <- pool.stats[!is.na(pool_dec_adj)]
swaps <- merge(swaps,
pool.stats[, list(pool_address, pool_dec_adj)],
by = "pool_address",
all = FALSE)
#swaps[, price_0_1 := ifelse(tick >= 0, ((1.0001^tick) / (10^pool_dec_adj)), 1/((1.0001^tick) / (10^pool_dec_adj)))]
#swaps[, price_1_0 := ifelse(tick >= 0, 1 / ((1.0001^tick) / (10^pool_dec_adj)), ((1.0001^tick) / (10^pool_dec_adj)) )]
swaps[, price_0_1 := 1 / ((1.0001^tick) / (10^pool_dec_adj))]
swaps[, price_1_0 := ((1.0001^tick) / (10^pool_dec_adj))]
keep.pools <- swaps[, list(n_swaps = .N), by = pool_address][n_swaps > 50]$pool_address
current.positions <- current.positions[pool_address %in% keep.pools]
swaps <- swaps[pool_address %in% keep.pools]
pool.stats <- pool.stats[pool_address %in% keep.pools]
pool.choices <- swaps[, .N, by = pool_name][order(-N)]
pool.choices[,fancy_name := paste0(
strsplit(pool_name, " ", fixed = TRUE)[[1]][1]," ",
as.numeric(strsplit(pool_name, " ", fixed = TRUE)[[1]][2]) / 10000,
"% fee",collapse = ""), by = pool_name ]
pool.choices <- {
.vec <- unique(pool.choices$pool_name)
names(.vec) <- unique(pool.choices$fancy_name)
.vec
}
special.addresses <- QuerySnowflake("select address as pool_address,
meta:token0 as token0,
meta:token1 as token1
from silver.ethereum_contracts where address in (select pool_address from uniswapv3.pools)")
save(pool.choices, current.positions, swaps, pool.stats, file = "shiny_location")

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

BIN
uniswap_v3/www/fliplogo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

128
uniswap_v3/www/shinycss.css Normal file
View File

@ -0,0 +1,128 @@
body {
background: url('Background_03.png');
background-attachment:fixed;
background-size: auto 100%;
background-position: center;
padding-left: 10%;
padding-right: 10%;
}
#lildate {
margin-top: -15px;
padding-top: 0;
padding-bottom: 10px;
color: #8A9391;
font-size: 0.8em;
}
.smallstats {
color: #8A9391;
font-size: 0.9em;
}
.selectize-input {
margin-bottom: 0px;
padding-bottom: 0px;
}
.selectize-control {
padding-bottom: 0px;
margin-bottom: 0px;
}
#fee {
text-align: center;
height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
font-size: 1.03rem;
font-weight: 900;
line-height: 1.5;
color: #9933cc;
background-color: #e7d3f2;
background-clip: padding-box;
border: 2px solid #815c99;
border-radius: .25rem;
}
.irs-handle {
background-color: #acf3e4 !important;
border: 1px solid #acf3e4 !important;
}
#dates {
width: 90%;
margin: auto;
align: center;
text-align: center;
}
#lcurveselect {
width: 90%;
margin: auto;
align: center;
text-align: center;
}
#lpamt {
width: 90%;
text-align: center;
height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
font-weight: 600;
line-height: 1.5;
color: #55454E;
background-color: #acf3e4;
background-clip: padding-box;
border: 1px solid #98879A;
border-radius: .25rem;
}
#moveprice {
width: 90%;
margin: auto;
align: center;
text-align: center;
}
.fa {
vertical-align: top;
size: 0.25em !important;
}
.irs-bar--single {
background-color: #e7d3f2 !important;
border: 1px solid #d3bfdb !important;
border-radius: 8px !important;
}
#disclaimer {
text-align: justify;
color: #5c5760;
}
.centerit {
vertical-align: middle !important;
}
#equals {
size: 6em;
font-weight: 600;
}
h4 {
margin-top: 0px !important;
margin-bottom: 0px !important;
}
#uni {
font-size: 1.9em !important;
margin-top: 0;
padding-top: 0;
vertical-align: top;
}