| Overall Statistics |
|
Total Trades 4364 Average Win 0.62% Average Loss -0.29% Compounding Annual Return 85.602% Drawdown 50.900% Expectancy 0.599 Net Profit 2452.129% Sharpe Ratio 1.732 Probabilistic Sharpe Ratio 72.263% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 2.13 Alpha 0.56 Beta 2.42 Annual Standard Deviation 0.524 Annual Variance 0.275 Information Ratio 1.887 Tracking Error 0.405 Treynor Ratio 0.375 Total Fees $12316.95 Estimated Strategy Capacity $150000.00 |
from QuantConnect.Data.UniverseSelection import *
import pandas as pd
import numpy as np
class EnhancedShortTermMeanReversionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2016, 1, 1) #Set Start Date
#self.SetEndDate(2018, 1, 27) #Set Start Date
self.SetCash(50000) #Set Strategy Cash
self.security = "AAPL"
self.AddEquity(self.security, Resolution.Minute)
# rebalance the universe selection once a month
self.rebalence_flag = 0
# make sure to run the universe selection at the start of the algorithm even it's not the manth start
self.first_month_trade_flag = 1
self.trade_flag = 0
# Number of quantiles for sorting returns for mean reversion
self.nq = 7
# Number of quantiles for sorting volatility over five-day mean reversion period
self.nq_vol = 7
# the symbol list after the coarse and fine universe selection
self.universe = None
self.down_trend = False
self.Insights_Store = []
# Universe Settings
#---------------------------------------------------------------------------------------
self.UniverseSettings.Resolution = Resolution.Minute
self.UniverseSettings.ExtendedMarketHours = False
self.UniverseSettings.Leverage = 2
#---------------------------------------------------------------------------------------
# Algorithm Framework Configuration
#---------------------------------------------------------------------------------------
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.SetPortfolioConstruction(MyPCM())
self.SetExecution(ImmediateExecutionModel())
self.SetRiskManagement(NullRiskManagementModel())
# self.SetRiskManagement(TrailingStopRiskManagementModel())
#---------------------------------------------------------------------------------------
# Algorithm Schedules
#---------------------------------------------------------------------------------------
time = 15
self.Schedule.On(self.DateRules.MonthStart(self.security), self.TimeRules.At(0, 0), Action(self.monthly_rebalance))
self.Schedule.On(self.DateRules.EveryDay(self.security), self.TimeRules.At(time, 9), Action(self.can_trade))
self.Schedule.On(self.DateRules.WeekStart(self.security), self.TimeRules.At(time, 10), Action(self.get_prices))
self.Schedule.On(self.DateRules.WeekStart(self.security), self.TimeRules.At(time, 11), Action(self.daily_rebalance))
self.Schedule.On(self.DateRules.WeekStart(self.security), self.TimeRules.At(time, 12), Action(self.short))
self.Schedule.On(self.DateRules.WeekStart(self.security), self.TimeRules.At(time, 13), Action(self.long))
#---------------------------------------------------------------------------------------
def monthly_rebalance(self):
# rebalance the universe every month
self.rebalence_flag = 1
def CoarseSelectionFunction(self, coarse):
if self.rebalence_flag or self.first_month_trade_flag:
# drop stocks which have no fundamental data or have too low prices
selected = [x for x in coarse if (x.HasFundamentalData) and (float(x.Price) > 5)]
# rank the stocks by dollar volume and choose the top 50
filtered = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
return [ x.Symbol for x in filtered[:50]]
else:
return self.universe
def can_trade(self):
security = "SPY"
self.AddEquity(security)
# self.AddEquity(security_VXX)
spy = self.History([security], 30, Resolution.Daily)
roll = 20
spy_roll_mean = (spy.loc[security]['close'][-roll:].mean())
if spy.loc[security]['close'][-1] < spy_roll_mean:
for symbol in self.Portfolio.Keys:
symbol = symbol
self.Insights_Store.append(Insight.Price(symbol, timedelta(999), InsightDirection.Flat, None, None, None, 1))
self.down_trend = True
else:
self.down_trend = False
# self.SetHoldings(security_VXX,0)
self.RemoveSecurity(security)
# self.RemoveSecurity(security_VXX)
def FineSelectionFunction(self, fine):
if self.rebalence_flag or self.first_month_trade_flag:
# filter the stocks which have positive EV To EBITDA
filtered_fine = [x for x in fine if x.ValuationRatios.EVToEBITDA > 0 and x.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Technology]
self.universe = [x.Symbol for x in filtered_fine]
self.rebalence_flag = 0
self.first_month_trade_flag = 0
self.trade_flag = 1
return self.universe
def OnData(self, data):
pass
def short(self):
if self.down_trend == True: return
if self.universe is None: return
SPY_Velocity = 0
self.long_leverage = 0
self.short_leverage = 0
# request the history of benchmark
pri = self.History([self.security], 200, Resolution.Daily)
pos_one = (pri.loc[self.security]['close'][-1])
pos_six = (pri.loc[self.security]['close'][-75:].mean())
# calculate velocity of the benchmark
velocity_stop = (pos_one - pos_six)/100.0
SPY_Velocity = velocity_stop
if SPY_Velocity > 0.0:
self.long_leverage = 1
self.short_leverage = 0
else:
self.long_leverage = 0.5
self.short_leverage = 0.5
for symbol in self.shorts:
if len(self.shorts) + self.existing_shorts == 0: return
self.AddEquity(symbol, Resolution.Minute)
self.Insights_Store.append(Insight.Price(symbol, timedelta(999), InsightDirection.Down, None, None, None, self.short_leverage))
def long(self):
if self.down_trend == True: return
if self.universe is None: return
for symbol in self.longs:
if len(self.longs) + self.existing_longs == 0: return
self.AddEquity(symbol, Resolution.Minute)
self.Insights_Store.append(Insight.Price(symbol, timedelta(999), InsightDirection.Up, None, None, None, self.long_leverage))
self.EmitInsights(Insight.Group(self.Insights_Store))
self.Insights_Store = []
def get_prices(self):
if self.universe is None: return
# Get the last 6 days of prices for every stock in our universe
prices = {}
hist = self.History(self.universe, 14, Resolution.Daily)
for i in self.universe:
if str(i) in hist.index.levels[0]:
prices[i.Value] = hist.loc[str(i)]['close']
df_prices = pd.DataFrame(prices, columns = prices.keys())
# calculate the daily log return
daily_rets = np.log(df_prices/df_prices.shift(1))
# calculate the latest return but skip the most recent price
rets = (df_prices.iloc[-2] - df_prices.iloc[0]) / df_prices.iloc[0]
# standard deviation of the daily return
stdevs = daily_rets.std(axis = 0)
self.ret_qt = pd.qcut(rets, 5, labels=False) - 1
self.stdev_qt = pd.qcut(stdevs, 3, labels=False) -1
self.longs = list((self.ret_qt[self.ret_qt == 1].index) & (self.stdev_qt[self.stdev_qt < 2].index))
self.shorts = list((self.ret_qt[self.ret_qt == self.nq].index) & (self.stdev_qt[self.stdev_qt < 2].index))
def daily_rebalance(self):
if self.down_trend == True: return
# rebalance the position in portfolio every day
if self.universe is None: return
self.existing_longs = 0
self.existing_shorts = 0
for symbol in self.Portfolio.Keys:
if (symbol.Value != self.security) and (symbol.Value in self.ret_qt.index):
current_quantile = self.ret_qt.loc[symbol.Value]
if self.Portfolio[symbol].Quantity > 0:
if (current_quantile == 1) and (symbol not in self.longs):
self.existing_longs += 1
elif (current_quantile > 1) and (symbol not in self.shorts):
self.Insights_Store.append(Insight.Price(symbol, timedelta(999), InsightDirection.Flat, None, None, None, 1))
elif self.Portfolio[symbol].Quantity < 0:
if (current_quantile == self.nq) and (symbol not in self.shorts):
self.existing_shorts += 1
elif (current_quantile < self.nq) and (symbol not in self.longs):
self.Insights_Store.append(Insight.Price(symbol, timedelta(999), InsightDirection.Flat, None, None, None, 1))
class MyPCM(EqualWeightingPortfolioConstructionModel):
leverage = 1
def CreateTargets(self, algorithm, insights):
targets = super().CreateTargets(algorithm, insights)
return [PortfolioTarget(x.Symbol, x.Quantity*(1+self.leverage)) for x in targets]