| Overall Statistics |
|
Total Trades 24542 Average Win 0.03% Average Loss -0.02% Compounding Annual Return -46.045% Drawdown 57.100% Expectancy -0.196 Net Profit -45.495% Sharpe Ratio -1.524 Probabilistic Sharpe Ratio 0.112% Loss Rate 66% Win Rate 34% Profit-Loss Ratio 1.37 Alpha -0.392 Beta 0.069 Annual Standard Deviation 0.248 Annual Variance 0.062 Information Ratio -1.505 Tracking Error 0.377 Treynor Ratio -5.45 Total Fees $24595.17 |
from datetime import timedelta
import numpy as np
from scipy import stats
from collections import deque
from QuantConnect.Data.UniverseSelection import *
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
class ModulatedMultidimensionalReplicator(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 1)
#self.SetEndDate(2020, 1, 1)
self.SetCash(50000)
# Timedelta for the indicator with longes time
#self.SetWarmUp(timedelta(200))
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverseSelection(MyUniverseSelectionModel())
self.AddAlpha(MOMAlphaModel())
self.Settings.RebalancePortfolioOnInsightChanges = False
self.Settings.RebalancePortfolioOnSecurityChanges = False
#self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(self.DateRules.Every(DayOfWeek.Monday)))
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
self.SetExecution(ImmediateExecutionModel())
def OnData(self, data):
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
'''
# if not self.Portfolio.Invested:
# self.SetHoldings("SPY", 1)
class MyUniverseSelectionModel(FundamentalUniverseSelectionModel):
def __init__(self):
super().__init__(True, None, None)
def SelectCoarse(self, algorithm, coarse):
#filtered = [x for x in coarse if x.Price > 5 and x.DollarVolume > 1e9 ]
filtered = [x for x in coarse if x.Price > 5 and x.Volume > 1e6 ]
return [x.Symbol for x in filtered]
#def FineSelectionFunction(self, algorithm, fine):
#self.vola = {}
#for security in fine:
### Volatility
# initialize indicator
#self.vola[symbol] = CustomVolatility( 'My_Custom', 30 )
#algorithm.RegisterIndicator(symbol, self.vola[symbol], Resolution.Daily)
# warmup indicator
#history = algorithm.History(symbol, 30, Resolution.Daily)
#self.vola[symbol].WarmUp(history)
## filter stocks with the top market cap
#top = sorted(fine, key = lambda x: x.EarningReports.BasicAverageShares.ThreeMonths * (x.EarningReports.BasicEPS.TwelveMonths*x.ValuationRatios.PERatio), reverse=True)
#filtered = [ x for x in fine if (self.vola[x[0]].Value > 100) ]
#return [x.Symbol for x in filtered]
class MOMAlphaModel(AlphaModel):
'''
1. The stock’s closing price must be greater than $5/share.
2. average day volume 21 trading > 1e6 shares a day
3. 100-day Historical Volatility >= 100%.
4. Enter Short on ConnorsRSI (CRSI,3,2,10) > 90 (RSI, Streak, Percentage Change)
5. Short next day on a limit order 5% higher.
6. The exit Trade ConnorsRSI < 20
Rresults should be 2007-2017:
Results from 2007-2017 should be:
# Trades: around 500
Win Rate: 73%
Avg. Profit Per Trade: 6.5%
Avg. Days per Tr%ade: 12
Avg. Win: 16.5%
Avg. Loss: 19.5 %
'''
def __init__(self):
self.indi = {}
self.rsi = {}
self.vola = {}
self.indi_Filter = {}
self.rsi_Filter = {}
self.rsi_w = 3
self.streak_w = 2
self.pct_rank_w = 100
self.vola_w = 30
self.securities = []
self.avtive_sec = []
def OnSecuritiesChanged(self, algorithm, changes):
for security in changes.AddedSecurities:
#self.avtive_sec.append(security)
symbol = security.Symbol
### Connors RSI # DoDo include the standart RSI
# initialize indicator
self.indi[symbol] = CustomConnors( 'My_Custom', self.rsi_w, self.streak_w, self.pct_rank_w)
algorithm.RegisterIndicator(symbol, self.indi[symbol], Resolution.Daily)
# warmup indicator
history = algorithm.History(symbol, max(self.rsi_w, self.streak_w, self.pct_rank_w), Resolution.Daily)
self.indi[symbol].WarmUp(history)
### Standart RSI
# initialize indicator
self.rsi[symbol] = algorithm.RSI(symbol, 3, MovingAverageType.Simple, Resolution.Daily)
algorithm.RegisterIndicator(symbol, self.rsi[symbol], Resolution.Daily)
# warmup indicator not neccesary as RegisterIndicator is used
### Volatility
# initialize indicator
self.vola[symbol] = CustomVolatility( 'My_Custom', self.vola_w )
algorithm.RegisterIndicator(symbol, self.vola[symbol], Resolution.Daily)
# warmup indicator
history = algorithm.History(symbol, self.vola_w, Resolution.Daily)
self.vola[symbol].WarmUp(history)
# remove securities
for security in changes.RemovedSecurities:
#self.avtive_sec.remove(security)
symbol = security.Symbol
if security in self.indi:
self.indi[symbol].remove(security)
if security in self.rsi:
self.rsi[symbol].remove(security)
if security in self.vola:
self.vola[symbol].remove(security)
def Update(self, algorithm, data):
insights = []
C_RSI = {}
filter2 = []
filter1 = []
buy_filter = []
stocks_short = []
my_invested = []
# only stock with volatilytiy > 100%
filter1 = [x[0] for x in self.vola.items() if (self.vola[x[0]].Value > 100) ]
#filter1 = [x[0] for x in self.vola.items()]
# stock made a > 5% jump yesterday
for ticker in filter1:
prices = algorithm.History(ticker, 2, Resolution.Daily)
if not prices.empty:
if len(prices.close) == 2:
if (prices.close[-1]/prices.close[-2]) > 1.05 :
filter2.append(ticker)
# find short stocks
#my_invested = [ x.Symbol.Value for x in algorithm.Portfolio.Values if x.Invested ]
my_invested = [ x.Symbol for x in algorithm.Portfolio.Values if x.Invested ]
for ticker in my_invested:
if algorithm.Portfolio[ticker].IsShort:
stocks_short.append(ticker)
for ticker in (filter2 + stocks_short):
C_RSI[ticker]= (self.rsi[ticker].Current.Value + self.indi[ticker].Streak + self.indi[ticker].Pct_Rank)/3
## filtering for buy candiates
short = [x for x in filter2 if (C_RSI[x] > 90) ]
## filter portfolio for exit candiates
neutral = [x for x in stocks_short if (C_RSI[x] < 30) ]
## sorting
#ordered = sorted(self.indi_Filter.items(), key=lambda x: x[1].Value, reverse=True)[:self.num]
for ticker in short:
insights.append( Insight.Price(ticker, timedelta(14), InsightDirection.Down) )
for ticker in neutral:
insights.append( Insight.Price(ticker, timedelta(5), InsightDirection.Flat) )
# for testing
#algorithm.Plot("Custom_Slope", "Value", list(self.indi.values())[0].Streak *10000)
# for testing
#algorithm.Plot("Custom_Slope", "RSI", list(self.rsi.values())[0].Current.Value )
algorithm.Plot("nummer of short", "shorts", len(short) )
algorithm.Plot("nummer of closed", "flat", len(neutral) )
return insights
class CustomConnors:
def __init__(self, name, rsi_p, streak_p, pct_rank_p):
period = max(rsi_p, streak_p, pct_rank_p)
self.Name = name
self.Time = datetime.min
self.IsReady = False
self.Value = 0
self.Streak = 0
self.Pct_Rank = 0
self.queue = deque(maxlen=period)
self.rsi_p = rsi_p
self.streak_p = streak_p
self.pct_rank_p = pct_rank_p
def __repr__(self):
return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)
# Update method is mandatory
def Update(self, input):
return self.Update_Main(input.Time, input.Close)
def Update_warmup(self, input):
return self.Update_Main(input.Index, input.close)
def Update_Main(self, time, value):
#def Update(self, input):
#self.queue.appendleft(input.close) # used by warm up
#self.queue.appendleft(input.Close) # used by RegisterIndicator
self.queue.appendleft(value) # neutral
count = len(self.queue)
#self.Time = input.Index # used by warm up
#self.Time = input.Time # used by RegisterIndicator
self.Time = time # neutral
self.IsReady = count == self.queue.maxlen
# Connors streak indicator
#### start here the indicator calulation
if self.IsReady:
curstreak = 0
close = np.array(self.queue)[-self.streak_p:]
streak = np.zeros(self.streak_p)
for i in range(self.streak_p):
if close[i-1] < close[i]:
streak[i] = curstreak = max(1, curstreak + 1)
elif close[i-1] > close[i]:
streak[i] = curstreak = min(-1, curstreak - 1)
else:
streak[i] = curstreak = 0
self.Streak = (streak[-1] / (self.streak_p-1) + 1) * 50 # streak goes from 0 to 100 linear
#### finish the custom indicator
# Connors Pct Rank Indicator
#### start here the indicator calulation
#cl = np.zeros(count)
close = np.array(self.queue)[-self.pct_rank_p:]
#close = np.array([[1, 1],[2, 2],[4,4],[15,15],[226,22]])
daily_returns = np.diff(close) / close[0:-1]
# today return greater than how many past returns
today_gt_past = daily_returns[-1] > daily_returns [0:-1]
# sum of today > past
num = sum(today_gt_past)
# sum as as percentage
l=np.shape(today_gt_past)[0]
self.Pct_Rank = num / l * 100
return self.IsReady
def WarmUp(self,history):
for tuple in history.itertuples():
self.Update_warmup(tuple)
class CustomVolatility:
def __init__(self, name, period):
self.Name = name
self.Time = datetime.min
self.IsReady = False
self.Value = 0
self.Mean = 0
self.Std = 0
self.queue = deque(maxlen=period)
def __repr__(self):
return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)
# Update method is mandatory
def Update(self, input):
return self.Update_Main(input.Time, input.Close)
def Update_warmup(self, input):
return self.Update_Main(input.Index, input.close)
def Update_Main(self, time, value):
self.queue.appendleft(value) # neutral
count = len(self.queue)
self.Time = time # neutral
self.IsReady = count == self.queue.maxlen
#### start here the indicator calulation
if self.IsReady:
# [0:-1] is needed to remove last close since diff is one element shorter
close = np.array(self.queue)
daily_returns = np.diff(close) / close[0:-1]
self.Std = daily_returns.std() #* math.sqrt(252)
self.Mean = daily_returns.mean() #* math.sqrt(252)
self.Value = self.Std / abs(self.Mean) * 100 # in %
return self.IsReady
def WarmUp(self,history):
for tuple in history.itertuples():
self.Update_warmup(tuple)