diff --git a/eth_usdc_retroactive.Rmd b/eth_usdc_retroactive.Rmd index 7be1460..0f6ba13 100644 --- a/eth_usdc_retroactive.Rmd +++ b/eth_usdc_retroactive.Rmd @@ -154,7 +154,6 @@ saveRDS(filled_eth_prices, "eth_prices.rds") ```{r, message = FALSE, warning = FALSE} library(gmp) # large numbers library(reactable) # clean tables -library(plotly) # graphs library(dplyr) # data manipulation options(scipen = 10) @@ -1049,7 +1048,7 @@ id_fees_fixed <- lapply(1:length(id_fees_list), }) # cleanup -rm(closed_lp_actions, closed_fees, closed_lp_actions, id_fees_list) +rm(closed_lp_actions, closed_fees, id_fees_list) ``` @@ -1059,12 +1058,6 @@ rm(closed_lp_actions, closed_fees, closed_lp_actions, id_fees_list) Now with the fixed fee list organized by unique IDs and lp_actions list organized by unique IDs we can create `r length(id_fees_fixed)` accounting tables. -```{r} -# fix this -accounting(id_lp_actions = id_lp_list[[68]],id_fees = id_fees_fixed[[68]]) - -``` - ```{r} id_accounting <- lapply(1:length(id_fees_fixed), @@ -1107,7 +1100,7 @@ strategy_results <- bind_rows(id_strat, .id = "unique_id") ethusdc_results <- merge(pnl_results, hodl_results, by = "unique_id") ethusdc_results <- merge(ethusdc_results, strategy_results, by = "unique_id") -saveRDS(ethusdc_results) +saveRDS(ethusdc_results, file = "ethusdc_results.rds") reactable( ethusdc_results[1:20, ] diff --git a/ethbtc/collect_ethbtc_data.R b/ethbtc/collect_ethbtc_data.R new file mode 100644 index 0000000..28960b2 --- /dev/null +++ b/ethbtc/collect_ethbtc_data.R @@ -0,0 +1,91 @@ +library(shroomDK) +library(zoo) # infill NAs & rolling Median +source("../key_functions.R") +# LP Actions + + + +ethbtc_lp_actions <- auto_paginate_query( + query = " + SELECT + BLOCK_NUMBER, BLOCK_TIMESTAMP, + TX_HASH, ACTION, + NF_TOKEN_ID, + AMOUNT0_ADJUSTED, AMOUNT1_ADJUSTED, + LIQUIDITY, + TOKEN0_SYMBOL, TOKEN1_SYMBOL, + TICK_LOWER, TICK_UPPER, + PRICE_LOWER_0_1, PRICE_UPPER_0_1, + LIQUIDITY_PROVIDER, + NF_POSITION_MANAGER_ADDRESS + FROM ethereum.uniswapv3.ez_lp_actions + WHERE POOL_ADDRESS = '0xcbcdf9626bc03e24f779434178a73a0b4bad62ed' AND + BLOCK_NUMBER <= 15576600 + ORDER BY BLOCK_NUMBER DESC + ", + api_key = readLines("api_key.txt") +) + + +# Collected Fees +# Known Issue where Closure of positions mix withdrawn tokens as if they were collected fees +# subtraction will be sorted out +ethbtc_fees <- auto_paginate_query( + query = " + SELECT + BLOCK_NUMBER, BLOCK_TIMESTAMP, + TX_HASH, NF_TOKEN_ID, + AMOUNT0_ADJUSTED, AMOUNT1_ADJUSTED, + TICK_LOWER, TICK_UPPER, + PRICE_LOWER, PRICE_UPPER, + LIQUIDITY_PROVIDER, + NF_POSITION_MANAGER_ADDRESS + FROM ethereum.uniswapv3.ez_position_collected_fees + WHERE POOL_ADDRESS = '0xcbcdf9626bc03e24f779434178a73a0b4bad62ed' AND + BLOCK_NUMBER <= 15576600 + ORDER BY BLOCK_NUMBER DESC + ", + api_key = readLines("api_key.txt") +) + +# Historical BTC prices at all LP_ACTIONS blocks + +min_block = min(ethbtc_lp_actions$BLOCK_NUMBER) - 101 + +blocks = c(min_block, min_block + 1e6, + min_block + 2e6, min_block + 3e6, + min_block + 4e6) + +ethbtc_price <- list() +for(i in 1:length(blocks)){ + ethbtc_price[[i]] <- get_ethbtc_price(min_block = blocks[i], + max_block = blocks[i] + 1e6, + api_key = readLines('api_key.txt')) + +} + +all_ethbtc_prices <- do.call(rbind, ethbtc_price) +all_ethbtc_prices <- all_ethbtc_prices[order(all_ethbtc_prices$BLOCK_NUMBER),] + +# if a block has no trades, infill the BLOCK_NUMBER and persist the most recent +# ETH Weighted Average Price, with 0 VOLUME and 0 NUM_SWAPS +infill <- data.frame( + BLOCK_NUMBER = min(all_ethbtc_prices$BLOCK_NUMBER):max(all_ethbtc_prices$BLOCK_NUMBER) +) + +filled_ethbtc_prices <- merge(all_ethbtc_prices, infill, all.x = TRUE, all.y = TRUE) + +filled_ethbtc_prices[is.na(filled_ethbtc_prices$"BTC_VOLUME"), c("BTC_VOLUME","NUM_SWAPS")] <- 0 + +# Improves analysis speed to front-load these calculations and is more smoothed +filled_ethbtc_prices$BTC_WAVG_PRICE <- zoo::na.locf(filled_ethbtc_prices$BTC_WAVG_PRICE) +BTC_MARKET_PRICE <- zoo::rollmedian(x = filled_ethbtc_prices$BTC_WAVG_PRICE, k = 99, align = "left") +diff_median <- nrow(filled_ethbtc_prices) - length(BTC_MARKET_PRICE) +BTC_MARKET_PRICE <- c(filled_ethbtc_prices$BTC_WAVG_PRICE[1:diff_median], BTC_MARKET_PRICE) + +filled_ethbtc_prices$BTC_MARKET_PRICE <- BTC_MARKET_PRICE + +# R Save Format +saveRDS(ethbtc_lp_actions, "ethbtc_lp_actions.rds") +saveRDS(ethbtc_fees, "ethbtc_fees.rds") +saveRDS(filled_ethbtc_prices, "ethbtc_prices.rds") diff --git a/ethbtc/ethbtc_retroactive.Rmd b/ethbtc/ethbtc_retroactive.Rmd new file mode 100644 index 0000000..3e5a38a --- /dev/null +++ b/ethbtc/ethbtc_retroactive.Rmd @@ -0,0 +1,286 @@ +--- +title: "eth_btc_retroactive" +author: "Charliemarketplace" +date: "`r Sys.Date()`" +output: + html_document: + code_folding: hide + toc: yes + toc_float: + collapsed: false +editor_options: + chunk_output_type: console +--- + +# Intro + +This is a Profit and Loss calculation including potential divergent loss (DL) of all closed positions in the ETH-WBTC 0.3% Uniswap v3 pool from inception of the pool up to the block height of 15,576,600 (September 20, 2022). It reviews *all* changes in liquidity and accumulated fees to identify the profit and loss of every unique position across 3 accountings: + +- Direct Profit & Loss, accounting for price changes in the underlying assets +- Opportunity Cost, i.e., HODL Reference Value: what assets deposited would have been worth as of the block where the position was closed. +- Realized Value, i.e., Strategy Reference Value: what the assets withdrawn were worth as of the block where the position was closed including accumulated fees. + +Direct P&L is useful for understanding gains in the context of changing prices, here, P&L is measured in both ETH and BTC terms. + +Divergent Loss (sometimes called Impermanent Loss) is the difference between the results of using a strategy (here, fees and automatic market making in Uniswap v3) and the counter factual: the amount of assets if one were *not* to have used the strategy. +A more in depth view on definitions and calculations is available in the ETH USDC Retroactive over the same time range. + +# Data Collection + +The `collect_ethbtc_data.R` script included in this repo uses the Flipside Crypto's shroomDK API to pull all the relevant data. For brevity, this markdown reads from a saved RDS copy *not* available in the repo. To reproduce this analysis, you can run the `collect_ethbtc_data.R` script using your own shroomDK API key available for free. + +```{r} +library(gmp) # large numbers +library(reactable) # clean tables +library(dplyr) # data manipulation +options(scipen = 10) + +source("../key_functions.R") + +lp_actions <- readRDS("ethbtc_lp_actions.rds") + +# remove actions that don't remove liquidity, these are fee collection events +lp_actions <- lp_actions %>% filter(LIQUIDITY != 0) + +fees <- readRDS("ethbtc_fees.rds") +btc_prices <- readRDS("ethbtc_prices.rds") +btc_prices <- btc_prices[,c("BLOCK_NUMBER", "BTC_MARKET_PRICE")] +colnames(btc_prices) <- c("BLOCK_NUMBER","btc_price") + +``` + +### Closed Positions at Block Height + +```{r} +lp_actions$unique_id <- lp_actions$NF_TOKEN_ID + +custom_index <- which(is.na(lp_actions$unique_id)) + +lp_actions[custom_index, "unique_id"] <- paste0(lp_actions[custom_index, "NF_POSITION_MANAGER_ADDRESS"], + "--", + lp_actions[custom_index, "TICK_LOWER"], + "--", + lp_actions[custom_index, "TICK_UPPER"]) + +# adding BTC Market Price from btc_prices to lp_actions via merge for later +lp_actions <- merge(lp_actions, y = btc_prices, all.x = TRUE, all.y = FALSE, by = "BLOCK_NUMBER") + +# manual insertion of first price +lp_actions$btc_price[is.na(lp_actions$btc_price)] <- 16.07226 + +# while here do fees too +fees$unique_id <- fees$NF_TOKEN_ID +custom_index <- which(is.na(fees$unique_id)) +fees[custom_index,"unique_id"] <- paste0(fees[custom_index, "NF_POSITION_MANAGER_ADDRESS"], + "--", + fees[custom_index, "TICK_LOWER"], + "--", + fees[custom_index, "TICK_UPPER"]) + +# also while here, ensure only 1 fee collection row per tx_hash, i.e., just add them + +fees <- fees %>% + group_by(across(c(-AMOUNT0_ADJUSTED, -AMOUNT1_ADJUSTED))) %>% + summarise( + AMOUNT0_ADJUSTED = sum(AMOUNT0_ADJUSTED), + AMOUNT1_ADJUSTED = sum(AMOUNT1_ADJUSTED), .groups = "drop") %>% + relocate(c(NF_TOKEN_ID, AMOUNT0_ADJUSTED, AMOUNT1_ADJUSTED), .after = NF_TOKEN_ID) %>% + as.data.frame() + + +liquidity_at_timestamp <- lp_actions %>% mutate( + liquidity_signed = ifelse(ACTION == "DECREASE_LIQUIDITY", LIQUIDITY * -1, LIQUIDITY) +) %>% group_by(unique_id) %>% + summarise(sumliq = sum(liquidity_signed)) + +# due to some large number precision errors, some liquidity may be technically negative +# but effectively 0 for our purposes. +closed_positions <- liquidity_at_timestamp %>% filter(sumliq <= 0) + +``` + +There are `r nrow(closed_positions)` ETH-BTC 0.3% closed positions, including vaults, as of Sept 20, 2022. + +## Functions + +```{r} +price_col = "btc_price" + +pnl <- function(id_accounting, t0col, t1col, price_col, price_base = "t1/t0"){ + + # allow user to choose the base that is easier to read + # e.g., usdc/eth may be easier to read than eth/usdc; which means t0/t1 instead of t1/t0 + # while ETH/BTC may be easier than BTC/ETH; which is t1/t0 the default! + + if(price_base == 't1/t0'){ + pnl = data.frame( + pnl_t0_terms = sum(id_accounting$token0, (id_accounting$token1/id_accounting[[price_col]])), + pnl_t1_terms = sum(id_accounting$token1, (id_accounting$token0*id_accounting[[price_col]])) + ) + } else if(price_base == 't0/t1'){ + pnl = data.frame( + pnl_t0_terms = sum(id_accounting$token0, (id_accounting$token1*id_accounting[[price_col]])), + pnl_t1_terms = sum(id_accounting$token1, (id_accounting$token0/id_accounting[[price_col]])) + ) + } else { + stop("price_base must be 't1/t0' (default) or 't0/t1'") + } + + +colnames(pnl) <- c(t0col, t1col) + +return(pnl) +} + +hodl_reference <- function(id_accounting, t0col, t1col, price_col, price_base = "t1/t0"){ + + price_at_close_block = id_accounting[[price_col]][id_accounting$BLOCK_NUMBER == max(id_accounting$BLOCK_NUMBER)][1] + + if(price_base == 't1/t0'){ + hodl <- id_accounting %>% + filter(accounting == 'cost_basis') %>% + summarise( + hodl_t0_terms = abs(sum(token0) + sum(token1 / price_at_close_block)), + hodl_t1_terms = abs(sum(token1) + sum(token0 * price_at_close_block)), + ) + } else if(price_base == 't0/t1'){ + hodl <- id_accounting %>% + filter(accounting == 'cost_basis') %>% + summarise( + hodl_t0_terms = abs(sum(token0) + sum(token1 * price_at_close_block)), + hodl_t1_terms = abs(sum(token1) + sum(token0 / price_at_close_block)), + ) + } else { + stop("price_base must be 't1/t0' (default) or 't0/t1'") + } + + colnames(hodl) <- c(t0col, t1col) + + return(hodl) +} + +strategy_reference <- function(id_accounting, t0col, t1col, price_col, price_base = "t1/t0"){ + + price_at_close_block = id_accounting[[price_col]][id_accounting$BLOCK_NUMBER == max(id_accounting$BLOCK_NUMBER)][1] + + if(price_base == 't1/t0'){ + strat <- id_accounting %>% + filter(accounting != 'cost_basis') %>% + summarise( + strategy_t0_terms = abs(sum(token0) + sum(token1 / price_at_close_block)), + strategy_t1_terms = abs(sum(token1) + sum(token0 * price_at_close_block)), + ) + } else if(price_base == 't0/t1'){ + strat <- id_accounting %>% + filter(accounting != 'cost_basis') %>% + summarise( + strategy_t0_terms = abs(sum(token0) + sum(token1 * price_at_close_block)), + strategy_t1_terms = abs(sum(token1) + sum(token0 / price_at_close_block)), + ) + } else { + stop("price_base must be 't1/t0' (default) or 't0/t1'") + } + + colnames(strat) <- c(t0col, t1col) + return(strat) + +} + + +``` + +# Fee Correction + +```{r} + +closed_fees <- fees %>% + filter(unique_id %in% closed_positions$unique_id) %>% + arrange(BLOCK_NUMBER) + +closed_lp_actions <- lp_actions %>% + filter(unique_id %in% closed_positions$unique_id) + +closed_lp_w_fees <- closed_lp_actions %>% filter( + unique_id %in% closed_fees$unique_id +) %>% arrange(BLOCK_NUMBER) %>% + select(BLOCK_NUMBER, TX_HASH, unique_id, ACTION, AMOUNT0_ADJUSTED, AMOUNT1_ADJUSTED, btc_price) + + +# split automatically alphabetizes by ID # +id_fees_list <- split(closed_fees, f = closed_fees$unique_id) +id_lp_list <- split(closed_lp_w_fees, f = closed_lp_w_fees$unique_id) + +# If the order of IDs don't exactly match note the problem +if( + ! identical(names(id_lp_list), names(id_fees_list)) +) { + stop("List of IDs are not aligned") +} + +# otherwise run through the list of fees and correct them +# NOTE fee_correction only safe to use when split by unique_id +# Possible for a tx_hash to include multiple position interactions + +id_fees_fixed <- lapply(1:length(id_fees_list), + FUN = function(i){ + fee_correction(id_fees_list[[i]], id_lp_list[[i]]) + }) +``` + +### Accounting + +```{r} +aa <- accounting(id_lp_actions = id_lp_list[[100]], + id_fees = id_fees_fixed[[100]], + price_col = "btc_price") + +pnl(aa, "pnl_btc_terms", "pnl_eth_terms","btc_price") +hodl_reference(aa, "hodl_btc_terms", "hodl_eth_terms","btc_price") +strategy_reference(aa, "strat_btc_terms", "strat_eth_terms","btc_price") + +``` + +```{r} + +id_accounting <- lapply(1:length(id_lp_list), + FUN = function(i){ + tryCatch( + accounting(id_lp_actions = id_lp_list[[i]], + id_fees = id_fees_fixed[[i]],price_col = "btc_price"), + error = function(e){ + print(paste0("Error found in position: ", i)) + }) + }) + +names(id_accounting) <- names(id_lp_list) + +# to simplify segmentation of analysis saving this +saveRDS(id_accounting, "post_ethbtc_accounting.rds") + +# for brevity read id_accounting to continue here +id_accounting <- readRDS("post_ethbtc_accounting.rds") + +id_pnl <- lapply(id_accounting, function(i){ + pnl(i, "pnl_btc_terms", "pnl_eth_terms","btc_price") + }) +pnl_results <- bind_rows(id_pnl, .id = "unique_id") + +id_hodl <- lapply(id_accounting, function(i){ + hodl_reference(i, "hodl_btc_terms", "hodl_eth_terms","btc_price") +}) + +hodl_results <- bind_rows(id_hodl, .id = "unique_id") + +id_strat <- lapply(id_accounting, function(i){ + strategy_reference(i, "strat_btc_terms", "strat_eth_terms","btc_price") +}) + +strategy_results <- bind_rows(id_strat, .id = "unique_id") + +ethbtc_results <- merge(pnl_results, hodl_results, by = "unique_id") +ethbtc_results <- merge(ethbtc_results, strategy_results, by = "unique_id") + +saveRDS(ethbtc_results, file = "ethbtc_results.rds") + + +``` diff --git a/ethbtc_initial_summary_results.R b/ethbtc_initial_summary_results.R new file mode 100644 index 0000000..b1d9cb8 --- /dev/null +++ b/ethbtc_initial_summary_results.R @@ -0,0 +1,20 @@ +library(dplyr) +options(scipen = 10) + +ethbtc_results <- readRDS("ethbtc/ethbtc_results.rds") + +n = nrow(ethbtc_results) + +table(ethbtc_results$pnl_btc_terms > 0)/n +table(ethbtc_results$pnl_eth_terms > 0)/n + +table(ethbtc_results$pnl_btc_terms > 0 & ethbtc_results$pnl_eth_terms > 0)/n + +table(ethbtc_results$strat_btc_terms > ethbtc_results$hodl_btc_terms)/n + +table(ethbtc_results$strat_btc_terms > ethbtc_results$hodl_btc_terms & + ethbtc_results$strat_eth_terms > ethbtc_results$hodl_eth_terms)/n + + + + diff --git a/ethusdc_initial_summary_results.R b/ethusdc_initial_summary_results.R new file mode 100644 index 0000000..8aff0e8 --- /dev/null +++ b/ethusdc_initial_summary_results.R @@ -0,0 +1,20 @@ +library(dplyr) +options(scipen = 10) + +ethusdc_results <- readRDS("ethusdc_results.rds") + +n = nrow(ethusdc_results) + +table(ethusdc_results$pnl_usd_terms > 0)/n +table(ethusdc_results$pnl_eth_terms > 0)/n + +table(ethusdc_results$pnl_usd_terms > 0 & ethusdc_results$pnl_eth_terms > 0)/n + +table(ethusdc_results$strategy_usd_terms > ethusdc_results$hodl_usd_terms)/n + +table(ethusdc_results$strategy_usd_terms > ethusdc_results$hodl_usd_terms & + ethusdc_results$strategy_eth_terms > ethusdc_results$hodl_eth_terms)/n + + + + diff --git a/key_functions.R b/key_functions.R index 64fb2bc..0b180a6 100644 --- a/key_functions.R +++ b/key_functions.R @@ -1,5 +1,90 @@ library(gmp) # big int math +fee_correction <- function(id_fees, id_lp_actions){ + + # fee-collection problem only occurs in decrease_liquidity + # sometimes in the same transaction the same exact liquidity is added and immediately removed + # occurs in poorly written NF_POSITION_MANAGERS. + + id_lp_actions <- id_lp_actions %>% filter(ACTION == "DECREASE_LIQUIDITY") + + fee_tx_in_lp = (id_fees$TX_HASH %in% id_lp_actions$TX_HASH) + lp_tx_in_fee = (id_lp_actions$TX_HASH %in% id_fees$TX_HASH) + amount_cols = c("AMOUNT0_ADJUSTED", "AMOUNT1_ADJUSTED") + + # if there are no tx hashes in both tables move on + if(sum(fee_tx_in_lp) == 0){ + return(id_fees) + + } else { + # if it perfectly lines up that ALL tx hashes in + # both tables have fees collected double counting withdrawals; + # fix by subtracting the double counting + # note: it is assumed transactions are ordered by block number for alignment to work + if(sum(fee_tx_in_lp) == sum(lp_tx_in_fee)){ + if(mean(id_fees[fee_tx_in_lp, amount_cols] >= id_lp_actions[lp_tx_in_fee, amount_cols]) == 1){ + id_fees[fee_tx_in_lp, amount_cols] <- { + id_fees[fee_tx_in_lp, amount_cols] - id_lp_actions[lp_tx_in_fee, amount_cols] + } + } + } else { + # else go 1 by one to overlapping tx and check again + + for(i in id_fees$TX_HASH[fee_tx_in_lp]){ + if( + mean( + id_fees[id_fees$TX_HASH == i, amount_cols] >= + id_lp_actions[id_lp_actions$TX_HASH == i, amount_cols] + ) == 1 + ){ + id_fees[id_fees$TX_HASH == i, amount_cols] <- { + id_fees[id_fees$TX_HASH == i, amount_cols] - + id_lp_actions[id_lp_actions$TX_HASH == i, amount_cols] + } + } else next() + } + } + + return(id_fees) + } +} + +accounting <- function(id_lp_actions, id_fees, price_col){ + + # cost basis negative for subtraction + cost_basis <- id_lp_actions %>% filter(ACTION == "INCREASE_LIQUIDITY") %>% + mutate( + token0 = -1*AMOUNT0_ADJUSTED, + token1 = -1*AMOUNT1_ADJUSTED, + accounting = "cost_basis" + ) %>% + select(BLOCK_NUMBER, accounting, token0, token1, all_of(price_col)) + + withdrawals <- id_lp_actions %>% filter(ACTION == "DECREASE_LIQUIDITY") %>% + mutate( + token0 = AMOUNT0_ADJUSTED, + token1 = AMOUNT1_ADJUSTED, + accounting = "withdrawal" + ) %>% + select(BLOCK_NUMBER, accounting, token0, token1, all_of(price_col)) + + fees <- data.frame( + BLOCK_NUMBER = max(withdrawals$BLOCK_NUMBER), + accounting = "fee revenue", + token0 = sum(id_fees$AMOUNT0_ADJUSTED), + token1 = sum(id_fees$AMOUNT1_ADJUSTED) + ) + + # price fees at position closure, i.e., last withdrawal + fees[[price_col]] <- tail(withdrawals[[price_col]], n = 1) + + accounting_tbl <- rbind(cost_basis, withdrawals, fees) + + return(accounting_tbl) + +} + + get_eth_price <- function(min_block, max_block, api_key){ price_query <- { " @@ -49,6 +134,51 @@ ORDER BY BLOCK_NUMBER DESC prices = auto_paginate_query(price_query, api_key = api_key) } +get_ethbtc_price <- function(min_block, max_block, api_key){ + + price_query <- { + " + with uniswap_btc_eth_swaps AS ( +SELECT * FROM ethereum.uniswapv3.ez_swaps WHERE POOL_ADDRESS IN ( +'0xcbcdf9626bc03e24f779434178a73a0b4bad62ed', -- wbtc ETH 0.3% +'0x4585fe77225b41b697c938b018e2ac67ac5a20c0' -- wbtc ETH 0.05% + ) AND +BLOCK_NUMBER >= _min_block_ AND +BLOCK_NUMBER <= _max_block_ + ), + +btc_eth_price AS ( +SELECT BLOCK_NUMBER, BLOCK_TIMESTAMP, +IFF(TOKEN1_SYMBOL = 'WBTC', ABS(DIV0(AMOUNT0_ADJUSTED, AMOUNT1_ADJUSTED)), + ABS(DIV0(AMOUNT1_ADJUSTED, AMOUNT0_ADJUSTED)) + ) as btc_eth_trade_price, + IFF(TOKEN1_SYMBOL = 'WBTC', ABS(AMOUNT1_ADJUSTED), ABS(AMOUNT0_ADJUSTED)) as btc_volume, +IFF(TOKEN1_SYMBOL = 'WBTC', TOKEN0_SYMBOL, TOKEN1_SYMBOL) as eth + FROM uniswap_btc_eth_swaps + WHERE ABS(AMOUNT0_ADJUSTED) > 1e-8 AND ABS(AMOUNT1_ADJUSTED) > 1e-8 +), + +btc_block_price AS ( +SELECT BLOCK_NUMBER, BLOCK_TIMESTAMP, + div0(SUM(btc_eth_trade_price * btc_volume),sum(btc_volume)) as btc_wavg_price, + SUM(btc_volume) as btc_volume, + COUNT(*) as num_swaps + FROM btc_eth_price + GROUP BY BLOCK_NUMBER, BLOCK_TIMESTAMP +) + +SELECT * FROM btc_block_price +ORDER BY BLOCK_NUMBER DESC + " + } + + price_query <- gsub("_min_block_", min_block, price_query) + price_query <- gsub("_max_block_", max_block, price_query) + + prices = auto_paginate_query(price_query, api_key = api_key) +} + + market_eth_price_at_block <- function(eth_prices, block){ eth_prices[eth_prices$BLOCK_NUMBER == block, "ETH_MARKET_PRICE"] }