| Overall Statistics |
|
Total Trades 3964 Average Win 0.07% Average Loss -0.11% Compounding Annual Return 24.627% Drawdown 52.900% Expectancy 0.283 Net Profit 275.143% Sharpe Ratio 0.781 Probabilistic Sharpe Ratio 20.392% Loss Rate 24% Win Rate 76% Profit-Loss Ratio 0.68 Alpha 0.315 Beta -0.317 Annual Standard Deviation 0.356 Annual Variance 0.127 Information Ratio 0.384 Tracking Error 0.414 Treynor Ratio -0.876 Total Fees $3996.35 Estimated Strategy Capacity $1500000.00 |
# https://quantpedia.com/Screener/Details/14
from QuantConnect.Indicators import *
class MomentumEffectAlgorithm(QCAlgorithm):
# Trading time
# The other side selling: not great when the market is always increasing
# 1) change the weight of selling 2) correlated with the market
# Setting up the threshold
# rsi index?or second derivative
# short using bad stocks
# restructure code (check)
# threshold: could be solved by sorting stocks?
# change stocks
def Initialize(self):
#self.AddEquity(self.temp_ticker, Resolution.Daily)
self.SetStartDate(2015, 1, 1) # Set Start Date
self.SetEndDate(2021, 1, 1) # Set Start Date
self.SetCash(100000) # Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.hparams = {'lookback': 1 * 7}
self.num_coarse = 100
self.num_fine = 50
# Momentum-related variables
self.moment = {}
self.last_price = {}
self.lookback = 1 * 7 # Momentum indicator lookback period
self.last_trade_mom = 0
self.volume = {}
self.seen_day = {}
# Long-related variables
self.long_enable = True
self.curr_long_selected = []
self.selected_stocks_long = []
self.num_long = 10
# Short-related variables
self.short_enable = False
self.curr_short_selected = []
self.selected_stocks_short = []
self.num_short = 10
def CoarseSelectionFunction(self, coarse):
'''Drop securities which have no fundamental data or have too low prices.
Select those with highest by dollar volume'''
if self.Time.weekday() != 3:
return Universe.Unchanged
self.rebalance = True
#self.month = self.Time.month
self.day = self.Time.day
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 10],
key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in selected[:self.num_coarse]]
def FineSelectionFunction(self, fine):
'''Select security with highest market cap for long'''
fine = [f for f in fine if f.ValuationRatios.PERatio > 0
and f.EarningReports.BasicEPS.TwelveMonths > 0
and f.EarningReports.BasicAverageShares.ThreeMonths > 0]
selected_long = sorted(fine,
key=lambda f: f.MarketCap,
reverse=True)
selected_short = sorted(fine,
key=lambda f: f.MarketCap,
reverse=False)
self.selected_stocks_long = [x.Symbol for x in selected_long[:self.num_fine]]
self.selected_stocks_short = [x.Symbol for x in selected_short[:self.num_fine]]
return self.selected_stocks_long + self.selected_stocks_short
def OnSecuritiesChanged(self, changes):
# Clean up data for removed securities and Liquidate
for security in changes.RemovedSecurities:
symbol = security.Symbol
if self.moment.pop(symbol, None) is not None:
self.Liquidate(symbol, 'Removed from universe')
for security in changes.AddedSecurities:
if security.Symbol not in self.moment:
self.prepare_indicator(security.Symbol)
def prepare_indicator(self, ticker):
history = self.History([ticker], self.hparams['lookback'] + 1, Resolution.Daily)
for time, row in history.loc[ticker].iterrows():
if ticker not in self.last_price:
self.last_price[ticker] = row["close"]
self.volume[ticker] = row["volume"]
self.seen_day[ticker] = 1
elif ticker not in self.moment:
self.moment[ticker] = (row["close"] - self.last_price[ticker]) / self.last_price[ticker]
self.volume[ticker] = self.volume[ticker] + row["volume"]
self.seen_day[ticker] += 1
else:
mu = row["volume"] * self.seen_day[ticker] / self.volume[ticker]
percentage_return = (row["close"] - self.last_price[ticker]) / self.last_price[ticker]
self.moment[ticker] = (self.moment[ticker] + mu * percentage_return) / (1 + mu)
self.volume[ticker] = self.volume[ticker] + row["volume"]
self.seen_day[ticker] += 1
self.Debug("Mu: ")
self.Debug(mu)
if ticker in self.last_price and ticker in self.moment:
self.Debug('return: ')
self.Debug(row["close"] - self.last_price[ticker])
self.Debug('momentum: ')
#self.Debug(self.moment[self.temp_ticker])
self.last_price[ticker] = row["close"]
def update_indicator(self, ticker):
#guranteed to have moment, volume, seen_day exist for 'ticker'
if ticker not in self.moment:
self.prepare_indicator(ticker)
if self.Securities[ticker].Close == 0:
return
percentage_return = (self.Securities[ticker].Close - self.last_price[ticker]) / self.last_price[ticker]
mu = self.Securities[ticker].Volume * self.seen_day[ticker] / self.volume[ticker]
self.seen_day[ticker] += 1
self.volume[ticker] += self.Securities[ticker].Volume
self.moment[ticker] = (self.moment[ticker] + mu * percentage_return) / (1 + mu)
self.last_price[ticker] = self.Securities[ticker].Close
def OnData(self, data):
self.Debug(str(self.Time.month))
#self.Debug(self.moment[self.temp_ticker])
#if self.Time.weekday() != 2 and self.Time.weekday() != 4:
# return
for symbol, _ in self.moment.items():
self.update_indicator(symbol)
if self.long_enable:
mom_long = [(k, v) for k, v in self.moment.items() if k in self.selected_stocks_long]
sorted_mom_long = sorted([k for k,v in mom_long],
key=lambda x: self.moment[x], reverse=True)
selected_long = sorted_mom_long[:self.num_long]
# Liquidate securities that are not in the list
for symbol in self.curr_long_selected:
if symbol not in selected_long:
self.Liquidate(symbol, 'Not selected')
# Buy selected securities
for symbol in selected_long:
self.SetHoldings(symbol, 0.8 / self.num_long)
if self.short_enable:
mom_short = [(k, v) for k, v in self.moment.items() if k in self.selected_stocks_short]
sorted_mom_short = sorted([k for k,v in mom_short],
key=lambda x: self.moment[x], reverse=False)
selected_short = sorted_mom_short[:self.num_short]
# Liquidate securities that are not in the list
for symbol in self.curr_short_selected:
if symbol not in selected_short:
self.Liquidate(symbol, 'Not selected')
# Buy selected securities
for symbol in selected_short:
self.SetHoldings(symbol, -0.8 / self.num_short)