Quant BuffetRelax, Not Over Thinking

Overnight Effect during High Volatility Days in Bitcoin

Log in to collect

Academic paper

The Seasonality of Bitcoin

AuthorsRadovan Vojtko; Juliána Javorská

Institute
  • ?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"))