Quant BuffetRelax, Not Over Thinking

Moving Average Strategies for Cryptocurrencies

Log in to collect

Academic paper

Bitcoin: Predictability and Profitability via Technical Analysis

AuthorsAndrew L. Detzel; Hong Liu; Jack Strauss; Guofu Zhou; Yingzi Zhu

Institute
  • Baylor University
  • ?Baylor University - Hankamer School of Business
  • Institute of Economics
  • Washington University in St. Louis
  • Fudan University
  • ?Fudan University - China Institute of Economics and Finance
  • ?Washington University in St. Louis - Olin Business School
  • University of Denver
  • ?University of Denver - Daniels College of Business
  • ?Washington University in St. Louis - John M. Olin Business School
  • Tsinghua University
  • ?Tsinghua University - School of Economics & Management

Strategy in a nutshell

Trades Bitcoin using multiple moving averages (1-, 2-, 4-, 10-, and 20-day). Buy when price > MA, sell to risk-free when below. Sub-strategies equally weighted, 10% of portfolio actively traded, rebalanced daily.

Economic rationale

Moving averages provide predictive power for Bitcoin prices, reducing drawdowns and enhancing risk-adjusted returns. Combining multiple MA signals improves timing and performance, outperforming macroeconomic predictors.

Backtest performance

Annualised return10.03%
Volatility4.97%
Beta0.033
Sharpe ratio2.45
Sortino ratio0.571
Maximum drawdown-4.01%
Win rate51%

Full Python code

from AlgorithmImports import *
class MovingAverageCryptocurrencies(QCAlgorithm):
def Initialize(self):
self.set_start_date(2015, 1, 1)
self.set_cash(100_000)
self.symbol: Symbol = self.add_crypto('BTCUSD', Resolution.DAILY, Market.BITFINEX).symbol
self.securities[self.symbol].set_fee_model(CustomFeeModel())

self.MAs: List[SimpleMovingAverage] = [
    self.SMA(self.symbol, 1, Resolution.Daily),
    self.SMA(self.symbol, 2, Resolution.Daily),
    self.SMA(self.symbol, 4, Resolution.Daily),
    self.SMA(self.symbol, 10, Resolution.Daily),
    self.SMA(self.symbol, 20, Resolution.Daily)
]
    
def OnData(self, slice: Slice) -> None:
if not self.symbol in slice: return
    
price: float = slice[self.symbol].Value
if price == 0: return
long_signal_count: int = 0
for ma in self.MAs:
    if ma.IsReady:
        if price > ma.Current.Value:
            long_signal_count += 1
else:
    self.Liquidate()

w: float = (0.1 / len(self.MAs)) * long_signal_count
self.SetHoldings(self.symbol, w)
        
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))