Moving Average Strategies for Cryptocurrencies
Log in to collectAcademic paper
Bitcoin: Predictability and Profitability via Technical Analysis
Andrew L. Detzel; Hong Liu; Jack Strauss; Guofu Zhou; Yingzi Zhu
- 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"))