Overall Statistics |
Total Trades
8298
Average Win
0.03%
Average Loss
-0.03%
Compounding Annual Return
-0.954%
Drawdown
6.500%
Expectancy
-0.031
Net Profit
-4.299%
Sharpe Ratio
-0.25
Loss Rate
53%
Win Rate
47%
Profit-Loss Ratio
1.07
Alpha
-0.06
Beta
2.903
Annual Standard Deviation
0.032
Annual Variance
0.001
Information Ratio
-0.805
Tracking Error
0.032
Treynor Ratio
-0.003
Total Fees
$8299.51
|
# https://quantpedia.com/Screener/Details/155 from QuantConnect.Data.UniverseSelection import * import math import numpy as np import pandas as pd import scipy as sp from collections import deque class MomentumReversalCombinedWithVolatility(QCAlgorithm): def Initialize(self): self.SetStartDate(2014, 1, 1) # Set Start Date self.SetEndDate(2018, 8, 1) # Set Start Date self.SetCash(100000) # Set Strategy Cash self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.dataDict = {} # 1/6 of the portfolio is rebalanced every month self.portfolios = deque(maxlen=6) self.AddEquity("SPY", Resolution.Daily) self.Schedule.On(self.DateRules.MonthStart("SPY"),self.TimeRules.AfterMarketOpen("SPY"), self.Rebalance) # the lookback period for volatility and return is six months self.lookback = 20*6 self.filteredFine = None self.monthly_rebalance = False def CoarseSelectionFunction(self, coarse): # update the price of stocks in universe everyday for i in coarse: if i.Symbol not in self.dataDict: self.dataDict[i.Symbol] = SymbolData(i.Symbol, self.lookback) self.dataDict[i.Symbol].Update(i.AdjustedPrice) if self.monthly_rebalance: # drop stocks which have no fundamental data or have too low prices filteredCoarse = [x.Symbol for x in coarse if (x.HasFundamentalData) and (float(x.Price) > 5)] return filteredCoarse else: return [] def FineSelectionFunction(self, fine): if self.monthly_rebalance: sortedFine = sorted(fine, key = lambda x: x.EarningReports.BasicAverageShares.Value * self.dataDict[x.Symbol].Price, reverse=True) # select stocks with large size topFine = sortedFine[:int(0.5*len(sortedFine))] self.filteredFine = [x.Symbol for x in topFine] return self.filteredFine else: return [] def Rebalance(self): self.monthly_rebalance = True def OnData(self, data): if self.monthly_rebalance and self.filteredFine: filtered_data = {symbol: symbolData for (symbol, symbolData) in self.dataDict.items() if symbol in self.filteredFine and symbolData.IsReady()} self.filteredFine = None self.monthly_rebalance = False # if the dictionary is empty, then return if len(filtered_data) < 100: return # sort the universe by volatility and select stocks in the top high volatility quintile sortedByVol = sorted(filtered_data.items(), key=lambda x: x[1].Volatility(), reverse = True)[:int(0.2*len(filtered_data))] sortedByVol = dict(sortedByVol) # sort the stocks in top-quintile by realized return sortedByReturn = sorted(sortedByVol, key = lambda x: sortedByVol[x].Return(), reverse = True) long = sortedByReturn[:int(0.2*len(sortedByReturn))] short = sortedByReturn[-int(0.2*len(sortedByReturn)):] self.portfolios.append(short+long) # 1/6 of the portfolio is rebalanced every month if len(self.portfolios) == self.portfolios.maxlen: for i in list(self.portfolios)[0]: self.Liquidate(i) # stocks are equally weighted and held for 6 months short_weight = 1/len(short) for i in short: self.SetHoldings(i, -1/6*short_weight) long_weight = 1/len(long) for i in long: self.SetHoldings(i, 1/6*long_weight) class SymbolData: '''Contains data specific to a symbol required by this model''' def __init__(self, symbol, lookback): self.symbol = symbol # self.History = RollingWindow[Decimal](lookback) self.History = deque(maxlen=lookback) self.Price = None def Update(self, value): # update yesterday's close price self.Price = value # update the history price series self.History.append(float(value)) # self.History.Add(value) def IsReady(self): return len(self.History) == self.History.maxlen def Volatility(self): # one week (5 trading days) prior to the beginning of each month is skipped prices = np.array(self.History)[:-5] returns = (prices[1:]-prices[:-1])/prices[:-1] # calculate the annualized realized volatility return np.std(returns)*np.sqrt(250/len(returns)) def Return(self): # one week (5 trading days) prior to the beginning of each month is skipped prices = np.array(self.History)[:-5] # calculate the annualized realized return return (prices[-1]-prices[0])/prices[0]