Overall Statistics |
Total Trades 288 Average Win 0.12% Average Loss -0.11% Compounding Annual Return 8.928% Drawdown 3.600% Expectancy 0.098 Net Profit 1.012% Sharpe Ratio 0.891 Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.17 Alpha 0.332 Beta -15.661 Annual Standard Deviation 0.083 Annual Variance 0.007 Information Ratio 0.691 Tracking Error 0.083 Treynor Ratio -0.005 Total Fees $325.68 |
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. # Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from clr import AddReference AddReference("System") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Algorithm.Framework") AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Orders import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework import * from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel #from Selection.UncorrelatedToSPYUniverseSelectionModel import UncorrelatedToSPYUniverseSelectionModel from datetime import datetime, timedelta import pandas as pd import numpy as np class UncorrelatedToSPYFrameworkAlgorithm(QCAlgorithmFramework): def Initialize(self): self.UniverseSettings.Resolution = Resolution.Daily self.SetStartDate(2019,2,2) # Set Start Date self.SetEndDate(2019,3,15) # Set End Date self.SetCash(100000) # Set Strategy Cash self.SetUniverseSelection(UncorrelatedUniverseSelectionModel()) self.SetAlpha(UncorrelatedToSPYAlphaModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetExecution(ImmediateExecutionModel()) def OnOrderEvent(self, orderEvent): if orderEvent.Status == OrderStatus.Filled: self.Debug("Purchased Stock: {0}".format(orderEvent.Symbol)) class UncorrelatedToSPYAlphaModel(AlphaModel): '''Uses ranking of intraday percentage difference between open price and close price to create magnitude and direction prediction for insights''' def __init__(self, *args, **kwargs): self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 1 self.numberOfStocks = kwargs['numberOfStocks'] if 'numberOfStocks' in kwargs else 10 self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.Daily self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), self.lookback) self.symbolDataBySymbol = {} def Update(self, algorithm, data): insights = [] ret = [] symbols = [] activeSec = [x.Key for x in algorithm.ActiveSecurities] for symbol in activeSec: if algorithm.ActiveSecurities[symbol].HasData: open = algorithm.Securities[symbol].Open close = algorithm.Securities[symbol].Close if open != 0: openCloseReturn = close/open - 1 ret.append(openCloseReturn) symbols.append(symbol) # Intraday price change symbolsRet = dict(zip(symbols,ret)) # Rank on price change symbolsRanked = dict(sorted(symbolsRet.items(), key=lambda kv: kv[1],reverse=False)[:self.numberOfStocks]) # Emit "up" insight if the price change is positive and "down" insight if the price change is negative for key,value in symbolsRanked.items(): if value > 0: insights.append(Insight.Price(key, self.predictionInterval, InsightDirection.Up, value, None)) else: insights.append(Insight.Price(key, self.predictionInterval, InsightDirection.Down, value, None)) return insights class UncorrelatedUniverseSelectionModel(FundamentalUniverseSelectionModel): '''This universe selection model picks stocks that currently have their correlation to a benchmark deviated from the mean.''' def __init__(self, benchmark = Symbol.Create("SPY", SecurityType.Equity, Market.USA), numberOfSymbolsCoarse = 100, numberOfSymbols = 10, windowLength = 5, historyLength = 25): '''Initializes a new default instance of the OnTheMoveUniverseSelectionModel Args: benchmark: Symbol of the benchmark numberOfSymbolsCoarse: Number of coarse symbols numberOfSymbols: Number of symbols selected by the universe model windowLength: Rolling window length period for correlation calculation historyLength: History length period''' super().__init__(False) self.benchmark = benchmark self.numberOfSymbolsCoarse = numberOfSymbolsCoarse self.numberOfSymbols = numberOfSymbols self.windowLength = windowLength self.historyLength = historyLength # Symbols in universe self.symbols = [] self.coarseSymbols = [] self.cor = None self.removedSymbols = [] self.symbolDataBySymbol = {} self.lastSymbols = [] def SelectCoarse(self, algorithm, coarse): #if not self.coarseSymbols: # The stocks must have fundamental data # The stock must have positive previous-day close price # The stock must have positive volume on the previous trading day filtered = [x for x in coarse if x.HasFundamentalData and x.Volume > 0 and x.Price > 0] sortedByDollarVolume = sorted(filtered, key = lambda x: x.DollarVolume, reverse=True)[:self.numberOfSymbolsCoarse] self.coarseSymbols = [x.Symbol for x in sortedByDollarVolume] return self.corRanked(algorithm, self.coarseSymbols) def corRanked(self, algorithm, symbols): # Not enough symbols to filter if len(symbols) <= self.numberOfSymbols: return symbols hist = algorithm.History(symbols + [self.benchmark], self.historyLength, Resolution.Daily) returns=hist.close.unstack(level=0).pct_change() for symbol in symbols: if not symbol in self.symbolDataBySymbol.keys(): symbolData = self.SymbolData(algorithm=algorithm,returns=returns,symbol=symbol, benchmark = self.benchmark,windowLength = self.windowLength, historyLength = self.historyLength) symbolData.WarmUp() self.symbolDataBySymbol[symbol] = symbolData elif symbol in self.lastSymbols: self.symbolDataBySymbol[symbol].update(returns) else: self.symbolDataBySymbol.pop(symbol, None) self.lastSymbols = self.symbols zScore = {} for symbol in self.symbolDataBySymbol.keys(): obj = self.symbolDataBySymbol[symbol] if abs(obj.getMu()) > 0.5: zScore.update({symbol : obj.getScore()}) return sorted(zScore, key=lambda symbol: zScore[symbol],reverse=True)[:self.numberOfSymbols] class SymbolData: '''Contains data specific to a symbol required by this model''' def __init__(self,algorithm,returns, symbol, benchmark,windowLength, historyLength): self.algorithm = algorithm self.symbol = symbol self.benchmark = benchmark self.windowLength = windowLength self.historyLength = historyLength self.returns = returns self.colSelect = [str(self.symbol),str(self.benchmark)] self.zScore = None self.cor = None self.corMu = None def calcScore(self): # Calculate the mean of correlation self.corMu = self.cor.mean()[0] # Calculate the standard deviation of correlation corStd = self.cor.std()[0] # Current correlation corCur = self.cor.tail(1).unstack()[0] # Calculate absolute value of Z-Score for stocks in the Coarse Universe. self.zScore = (abs(corCur - self.corMu) / corStd) def WarmUp(self): # Calculate stdev(correlation) using rolling window for all history corMat=self.returns[self.colSelect].rolling(self.windowLength,min_periods = self.windowLength).corr().dropna() # Correlation of all securities against SPY self.cor = corMat[str(self.benchmark)].unstack() self.calcScore() def update(self,returns): self.returns = returns corRow=self.returns[self.colSelect].tail(self.windowLength).corr()[str(self.benchmark)] self.cor = self.cor.append(corRow).tail(self.historyLength) self.calcScore() ## Accessors def getScore(self): return self.zScore def getMu(self): return self.corMu