Overnight Effect during High Volatility Days in Bitcoin
Log in to collectAcademic paper
Radovan Vojtko; Juliána Javorská
- ?Quantpedia
- ?Quantpedia.com
Strategy in a nutshell
The strategy focuses on Bitcoin, calculating its 30-day historical volatility daily at 0:00 UTC. The median of the past 365 days’ volatility is used as a benchmark. If the 30-day volatility exceeds the moving median (“High Volatility”), the strategy buys Bitcoin at 21:00 UTC and sells at 23:00 UTC, capitalizing on short-term price movements during these hours
Economic rationale
The strategy exploits the “Overnight effect,” where major exchanges in Europe and London are closed, while Bitcoin trading continues globally. High-volatility periods amplify returns due to increased price swings. Empirical evidence shows
Backtest performance
Annualised return37.26%
Volatility24.63%
Beta0.021
Sharpe ratio1.51
Sortino ratio0.927
Maximum drawdown-18.87%
Win rate56%
Full Python code
from AlgorithmImports import *
import numpy as np
from typing import List
from pandas.core.frame import DataFrame
# endregion
class OvernightEffectduringHighVolatilityDaysinBitcoin(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
self.volatility_period:int = 30 * 24
self.history_period:int = 365
self.warmup_period:int = self.history_period * 24
self.btc:Symbol = self.AddCrypto('BTCUSD', Resolution.Hour, Market.Bitfinex).Symbol
self.Securities[self.btc].SetFeeModel(CustomFeeModel())
self.calculation_hour:int = 0 # calculation at 00:00
self.traded_window:List[int] = [21, 23] # trading from 21:00 to 23:00
self.trade_flag:bool = False
self.btc_volatility:RollingWindow = RollingWindow[float](self.history_period)
self.SetWarmup(self.warmup_period, Resolution.Hour)
def OnData(self, data: Slice) -> None:
if self.UtcTime.hour == self.calculation_hour:
monthly_volatility:float = self.History(self.btc, self.volatility_period, Resolution.Hour).close.unstack(level=0).pct_change().std().values[0]
self.btc_volatility.Add(monthly_volatility)
if self.IsWarmingUp:
return
if not self.btc_volatility.IsReady:
return
if self.btc_volatility[0] > np.median(list(self.btc_volatility)[1:]):
self.trade_flag = True
# trade execution
if self.UtcTime.hour == self.traded_window[0]:
if not self.trade_flag:
return
self.trade_flag = False
if self.btc in data and data[self.btc]:
self.SetHoldings(self.btc, 1)
if self.UtcTime.hour == self.traded_window[1] and self.Portfolio[self.btc].Invested:
self.Liquidate()
# custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))