Quant BuffetRelax, Not Over Thinking

Mean-reversion and Trend-Following Based on MIN and MAX in BTC

Log in to collect

Academic paper

Seasonality, Trend-following, and Mean reversion in Bitcoin

AuthorsMatus Padysak; Radovan Vojtko

Institute
  • SKComenius University Bratislava
  • ?Comenius University - Faculty of Mathematics, Physics and Informatics
  • ?Quantpedia.com

Strategy in a nutshell

Trade Bitcoin using 10-day price extremes from Gemini data. Go long when the price equals the 10-day maximum or minimum, holding positions for one day.

Economic rationale

Bitcoin shows strong mean-reversion at local minima and trend-following at local maxima. Trading at these extremes captures high-probability moves while reducing risk, yielding better risk-adjusted returns than passive holding.

Backtest performance

Annualised return98.43%
Volatility47.75%
Beta0.394
Sharpe ratio2.06
Sortino ratio1.046
Maximum drawdown-37.67%
Win rate51%

Full Python code

from AlgorithmImports import *
# endregion

class MeanreversionandTrendFollowingBasedonMINandMAXinBTC(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)

# NOTE Coinbase Pro, CoinAPI, and Bitfinex data is all set in UTC Time. This means that when accessing data from this brokerage, all data will be time stamped in UTC Time.
self.crypto:Crypto = self.AddCrypto("BTCUSD", Resolution.Minute, Market.GDAX)
self.crypto.SetLeverage(10)
self.crypto.SetFeeModel(CustomFeeModel())
self.crypto:Symbol = self.crypto.Symbol

self.period:int = 10
self.daily_prices:RollingWindow = RollingWindow[float](self.period)
self.daily_close_hour:int = 22

def OnData(self, data):
if self.crypto in data and data[self.crypto]:
    time:datetime.datetime = self.Time
    if time.hour == self.daily_close_hour and time.minute == 0:
        price:float = data[self.crypto].Value
        self.daily_prices.Add(price)

        if self.daily_prices.IsReady:
            daily_prices:list[float] = [x for x in self.daily_prices]
            daily_max:float = np.max(daily_prices)
            daily_min:float = np.min(daily_prices)

            # open/rebalance long position
            if price == daily_max or price == daily_min:
                self.SetHoldings(self.crypto, 1)
            else:
                # close position
                if self.Portfolio[self.crypto].Invested:
                    self.Liquidate(self.crypto)

class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))