Dollar cost averaging (DCA) is a popular strategy for buying assets like bitcoin or shares, where a fixed amount of money is spent each day, irrespective of the price of the asset. When the asset falls in price, more can be purchased for the same amount of money. It is common to buy daily, weekly or monthly.
A strategy was implemented to buy bitcoin every day on the the Binance cryptocurrency exchange. Rather than buy at a fixed time each day, the time of purchasing was chosen in what was believed might allow one to accumulated more bitcoin for the same outlay.
Initially an algorithm was though of, that checked the bitcoin price each minute, and made a decision whether to buy or not. The price every minute was stored for a period of March to June 2026, giving approximately 96,000 observations of price.
Method
Initial statergy, based on a gut feeling, rather than any statistical analysis.
The work started by crating a Python script, with the aid of ChatGPT, which used the Binance API and bought BTC once ever calandar day. The strategy was to execute a script every minute of every day using cron on a Debian server. The script determined.
The highest price in the last 24 hours, high_24h
The loweest price in the last 24 hours, low_24h
The current price, current_price
A trigger price was calculated:
trigger_price = low_24h + k × (high_24h - low_24h)
where ‘k=0.15’, which corresponds to a price 15% of the way between the 24-hour low and the 24-hour high. 1. If the current_price was below the trigger_price, BTC was purchased. 1. If BTC was bought, no further buys would take place that day. 1. If the condition current_price < trigger_price: was not met any time up until 23:59 UTC, a buy would occur at 23:59 UTC. 1. The amount spent on BTC was the same each day. irrespective of whether the buy occurred at 23:59 or before then.
It was believed this strategy may work, giving an improvement over DCA, but clearly this needed to be tested properly. Due to lack of time, this was not initially tested, and just allowed to run.
Statistical analysis of historical bitcoin price.
A historical data set of BTC price on the Bitstamp exchange is available from Github reporisotry. This covers the period 1st January 2002 to the present day. One file contains data up to the end of 2015, and a second file, undated daily, contains more recent data.
The first column is ihe time in seconds since the UNIX Epoch (1/1/1970). It was decided best to ignore data before the price reached exceeded $10,000, as this would not be represitive of how trading of bitcoin occurs today.
Next, the amount of BTC collected was determined if bought at 00:00, 12:00 or 23:59, and compared to what was obtained if used at various trigger levels.
library(data.table)setDT(BTC_since_2018)buy_usd <-100# start_date <- as.Date("2018-01-02")start_date <-as.Date("2022-01-02")BTC_since_2018[, datetime :=as.POSIXct( timestamp,origin ="1970-01-01",tz ="UTC")]BTC_since_2018[, date :=as.Date(datetime, tz ="UTC")]BTC_since_2018[, hhmm :=format(datetime, "%H:%M")]BTC_since_2018[, hour :=as.integer(format(datetime, "%H"))]# Rolling 24-hour high/low, including the current minute,# to approximate Binance's reported previous 24-hour high/low.BTC_since_2018[, high_24h :=frollmax(high, n =1440, align ="right")]BTC_since_2018[, low_24h :=frollmin(low, n =1440, align ="right")]# Fixed-time DCA comparisons, using opening price of that minute#fixed_times <- c("00:00", "06:00", "12:00", "18:00", "23:59")fixed_times <-sprintf("%02d:00",0:23)fixed_time_results <-rbindlist(lapply(fixed_times, function(t) { buys <- BTC_since_2018[ date >= start_date & hhmm == t ] btc_bought <- buy_usd / buys$opendata.table(strategy = t,days_bought =nrow(buys),total_usd_spent =nrow(buys) * buy_usd,total_btc_bought =sum(btc_bought),average_buy_price = (nrow(buys) * buy_usd) /sum(btc_bought) )}))test_k <-function(k) { BTC_since_2018[, trigger_price := low_24h + k * (high_24h - low_24h)] trigger_buys <- BTC_since_2018[ date >= start_date &!is.na(trigger_price) & open < trigger_price, .SD[1], by = date ] trigger_buys[, buy_type :="trigger"] trigger_buys[, buy_price := open] fallback_buys <- BTC_since_2018[ date >= start_date & hhmm =="23:59"&!(date %in% trigger_buys$date) ] fallback_buys[, buy_type :="23:59 fallback"] fallback_buys[, buy_price := open] all_buys <-rbindlist(list(trigger_buys, fallback_buys),use.names =TRUE,fill =TRUE ) all_buys[, btc_bought := buy_usd / buy_price]data.table(k = k,days_bought =nrow(all_buys),trigger_buys =nrow(trigger_buys),fallback_buys =nrow(fallback_buys),percent_trigger =100*nrow(trigger_buys) /nrow(all_buys),total_usd_spent =nrow(all_buys) * buy_usd,total_btc_bought =sum(all_buys$btc_bought),average_buy_price = (nrow(all_buys) * buy_usd) /sum(all_buys$btc_bought),mean_buy_hour =mean(all_buys$hour),median_buy_hour =median(all_buys$hour) )}k_values <-seq(0.01, 0.99, by =0.01)k_results <-rbindlist(lapply(k_values, test_k))fixed_time_results[order(-total_btc_bought)]