| Overall Statistics |
|
Total Trades 1663 Average Win 0.48% Average Loss -0.48% Compounding Annual Return -3.577% Drawdown 41.100% Expectancy -0.050 Net Profit -21.336% Sharpe Ratio -0.145 Loss Rate 53% Win Rate 47% Profit-Loss Ratio 1.00 Alpha -0.068 Beta 2.295 Annual Standard Deviation 0.16 Annual Variance 0.026 Information Ratio -0.268 Tracking Error 0.16 Treynor Ratio -0.01 Total Fees $7844.59 |
# https://quantpedia.com/Screener/Details/66
from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import scipy as sp
from collections import deque
class CombiningMomentumEffectWithVolume(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2012, 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/3 of the portfolio is rebalanced every month
self.portfolios = deque(maxlen=3)
self.AddEquity("SPY", Resolution.Daily)
self.Schedule.On(self.DateRules.MonthStart("SPY"),self.TimeRules.At(0, 0), self.Rebalance)
# the lookback period for return calculation
self.lookback = 252
self.filteredFine = None
self.monthly_rebalance = False
def CoarseSelectionFunction(self, coarse):
for i in coarse:
if i.Symbol not in self.dataDict:
self.dataDict[i.Symbol] = SymbolData(i.Symbol, self.lookback)
self.dataDict[i.Symbol].ROC.Update(i.EndTime, i.AdjustedPrice)
self.dataDict[i.Symbol].Volume = i.Volume
if self.monthly_rebalance:
# drop stocks which have no fundamental data
filteredCoarse = [x for x in coarse if (x.HasFundamentalData)]
return [i.Symbol for i in filteredCoarse]
else:
return []
def FineSelectionFunction(self, fine):
if self.monthly_rebalance:
dataReady = {symbol: symbolData for (symbol, symbolData) in self.dataDict.items() if symbolData.ROC.IsReady}
if len(dataReady) < 100:
self.filteredFine = []
else:
sortedFine = [i for i in fine if i.EarningReports.BasicAverageShares.ThreeMonths != 0 and i.Symbol in dataReady]
sortedFineSymbols = [i.Symbol for i in sortedFine]
filteredData = {symbol: symbolData for (symbol, symbolData) in dataReady.items() if symbol in sortedFineSymbols}
for i in sortedFine:
if i.Symbol in filteredData and filteredData[i.Symbol].Volume != 0:
filteredData[i.Symbol].Turnover = i.EarningReports.BasicAverageShares.ThreeMonths / filteredData[i.Symbol].Volume
sortedByROC = sorted(filteredData.values(), key = lambda x: x.ROC.Current.Value, reverse = True)
topROC = sortedByROC[:int(len(sortedByROC)*0.2)]
bottomROC = sortedByROC[-int(len(sortedByROC)*0.2):]
HighTurnoverTopROC = sorted(topROC, key = lambda x: x.Turnover, reverse = True)
HighTurnoverBottomROC = sorted(bottomROC, key = lambda x: x.Turnover, reverse = True)
self.long = [i.Symbol for i in HighTurnoverTopROC[:int(len(HighTurnoverTopROC)*0.01)]]
self.short = [i.Symbol for i in HighTurnoverBottomROC[:int(len(HighTurnoverBottomROC)*0.01)]]
self.filteredFine = self.long + self.short
self.portfolios.append(self.filteredFine)
else:
self.filteredFine = []
if not self.filteredFine:
self.monthly_rebalance = False
return self.filteredFine
def Rebalance(self):
self.monthly_rebalance = True
def OnData(self, data):
if self.monthly_rebalance and self.filteredFine:
self.filteredFine = None
self.monthly_rebalance = False
# 1/3 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 3 months
short_weight = 1/len(self.short)
for i in self.short:
self.SetHoldings(i, -1/3*short_weight)
long_weight = 1/len(self.long)
for i in self.long:
self.SetHoldings(i, 1/3*long_weight)
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, lookback):
self.Symbol = symbol
self.ROC = RateOfChange(lookback)
self.Volume = None