Overall Statistics
Total Orders
3454
Average Win
0.89%
Average Loss
-0.78%
Compounding Annual Return
28.888%
Drawdown
30.500%
Expectancy
0.198
Start Equity
100000.0
End Equity
1204086.73
Net Profit
1104.087%
Sharpe Ratio
1.202
Sortino Ratio
0.985
Probabilistic Sharpe Ratio
77.684%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
1.14
Alpha
0.182
Beta
0.026
Annual Standard Deviation
0.153
Annual Variance
0.024
Information Ratio
0.498
Tracking Error
0.21
Treynor Ratio
7.182
Total Fees
$83086.22
Estimated Strategy Capacity
$61000.00
Lowest Capacity Asset
BTCUSD E3
Portfolio Turnover
96.18%
# https://quantpedia.com/strategies/overnight-effect-during-high-volatility-days-in-bitcoin/
# 
# The investment universe consists of Bitcoin cryptocurrency.
# Firstly, each day at 0.00 UTC, calculate the 30-day historical volatility of the BTC returns. Subsequently, determine the median of the historical volatility over one 
# year (365 days). Use this moving median value as the benchmark for categorizing the next 24-hour period as “High Volatility” (the 30-day historical volatility is above 
# the moving median) or “Low Volatility” (the 30-day historical volatility is above the moving median).
# Finally, if the period is classified as “High Volatility” period, buy Bitcoin at 21:00 and sell it at 23:00.

# region imports
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.settings.daily_precise_end_time = False
        self.settings.minimum_order_margin_portfolio_percentage = 0.
        
        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"))