| 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