| Overall Statistics |
|
Total Trades 89 Average Win 0.64% Average Loss -0.46% Compounding Annual Return 21.792% Drawdown 9.700% Expectancy 0.415 Net Profit 23.983% Sharpe Ratio 1.581 Loss Rate 41% Win Rate 59% Profit-Loss Ratio 1.40 Alpha 0.089 Beta 5.874 Annual Standard Deviation 0.13 Annual Variance 0.017 Information Ratio 1.428 Tracking Error 0.13 Treynor Ratio 0.035 Total Fees $127.16 |
# Transported to QuantConnect Lean Framework by Serge d'Adesky
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
from System import *
from QuantConnect import *
from QuantConnect.Data import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *
from System.Collections.Generic import List
import decimal as d
import sys
VERBOSEMODE= True # set this to True to rough detailed error logging
VERBOSEMODE2=False # set this to True to see finer detailed error logging
class TripleGapDaytradeAlgorithm(QCAlgorithm):
def Initialize(self):
'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
self.SetStartDate(2016,1,1) #Set Start Date
self.SetEndDate(2017,2,1) #Set End Date
self.SetCash(100000) #Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
self.UniverseSettings.Leverage = 2
self.symbols = [] # this will be our symbols to populate
self.coarse_count = 10
self.indicators = { }
self.rebalance = True # whether or not to recalculate allocation. Default is True
# this add symbols method accepts two parameters:
# - coarse selection function: accepts an IEnumerable<CoarseFundamental> and returns an IEnumerable<Symbol>
self.symbols = self.AddUniverse(self.CoarseSelectionFunction)
self.securitiesRegistered = False # rist time we want to reregister all equities
self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
def registerAll(self):
if len(self.symbols) == 0:
self.securitiesRegistered = False
# for some reason our symbols does not correctly register all symbols so test for that
for symbol in self.symbols:
if symbol is None:
self.Log('initialize() unable to find symbol {} . Haling process'.format(symbol) )
return
try:
self.AddEquity(symbol,Resolution.Daily).Symbol
if VERBOSEMODE2: self.Log('initialize() added equity in case not defined'.format(symbol) )
self.securitiesRegistered = True
except:
if VERBOSEMODE2: self.Log('unable to add equity in initialize() '.format(symbol) )
def OnData(self, data):
try:
# first have to make sure our symbols have populate once in CoarseSelectionFuncion
if not self.securitiesRegistered:
self.registerAll()
# This updates the indicators at each data step(based on resolution)
for symbol in self.symbols:
# is symbol iin Slice object? (do we even have data on this step for this asset)
if not data.ContainsKey(symbol):
continue
# 686 | 13:35:43: Runtime Error: Python.Runtime.PythonException: AttributeError : 'NoneType' object has no attribute 'Price'
if data[symbol] is None:
continue
# Does this slice have the price data we need at this moment?
if data[symbol].Price is None:
continue
# Either create a new indicator, or update one we already have
if symbol not in self.indicators:
self.indicators[symbol] = SymbolData(symbol)
self.indicators[symbol].update(data[symbol])
# We are warming up the indicators, cannot trade or other stuff
if self.IsWarmingUp: continue
# now you can use logic to trade, random example:
#lowerband = self.indicators[symbol].bb10.LowerBand.Current.Value
#upperband = self.indicators[symbol].bb10.UpperBand.Current.Value
# Log the symbol, price & indicators.
self.Log("{0}\tPrice : {1:0.2f}\tUPPERBAND : {2:0.2f}\tLOWERBAND : {3:0.2f}".format(symbol, data[symbol].Price,upperband, lowerband))
except:
self.Log('Last good line was {} . An unknown error to print indicators of bollinger upper and lower. {} ' .format(str() , str(sys.exc_info()[0] )) )
# sort the data by daily dollar volume and take the top 'NumberOfSymbols'
def CoarseSelectionFunction(self, coarse):
try:
# if the rebalance flag is not true, return null list to save time.
if self.rebalance == False:
return self.symbols
# make symbols selection once a month
# 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]
sortedByDollarVolume = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
#self.Log('in CoarseSelectionFunction values at after is_sortedByDollarVolume of coarse have count of {}'.format(len(sortedByDollarVolume)))
# Save the top X (self.coarse_count) symbols for rebalance flag not 1
self.symbols = [ x.Symbol for x in sortedByDollarVolume[:self.coarse_count] ]
self.Log('in CoarseSelectionFunction selected count is {} _sortedByDollarVolume count is {} '.format(len(selected), len(self.symbols)))
# We are going to use a dictionary to refer to the object that will keep the moving averages
# so we can load them once now but access them during the day
for cf in selected:
if cf.Symbol not in self.indicators:
self.indicators[cf.Symbol] = SymbolData(cf.Symbol)
# Updates the SymbolData object with current EOD price
self.indicators[cf.Symbol].update(cf)
# Filter the values of the dict: wait for indicator to be ready
values = list(filter(lambda x: x.is_ready, self.indicators.values()))
self.Log('values at after is_ready filter of coarse have count of {}'.format(len(values)))
# Sorts the values of the dict: we want those with greater difference between the moving averages
values.sort(key=lambda x: x.vol30.Current.Value, reverse=True)
self.Log('values at after vol30 filter of coarse have count of {}'.format(len(values)))
for x in values[:self.coarse_count]:
self.Log('symbol: ' + str(x.symbol.Value) + ' mean vol: ' + str(x.vol30.Current.Value) + ' mean price: ' + str(x.sma_short.Current.Value))
# we need to return only the symbol objects
return [ x.symbol for x in values[:self.coarse_count] ]
except:
self.Log('in CoarseSelectionFunction An unknown error {} ' .format( str(sys.exc_info()[0] )) )
# this event fires whenever we have changes to our symbols
def OnSecuritiesChanged(self, changes):
# liquidate removed securities
for security in changes.RemovedSecurities:
if security.Invested:
self.Liquidate(security.Symbol)
# we want 20% allocation in each security in our symbols
for security in changes.AddedSecurities:
self.SetHoldings(security.Symbol, 0.1)
class SymbolData(object):
def __init__(self, symbol):
try:
self.qc = QCAlgorithm()
self.symbol = symbol
self.sma_short = SimpleMovingAverage(30)
self.vol30 = SimpleMovingAverage(30)
self.sma_medium = SimpleMovingAverage(55)
self.sma_long = SimpleMovingAverage(100)
self.is_ready = False
except:
self.qc.Log('In SymbolData.__init__ An unknown error occurred in SymbolData loading Algorithm library'), sys.exc_info()[0]
def update(self, value):
self.is_ready = self.sma_short.Update(value.EndTime, value.Price) and self.vol30.Update(value.EndTime, value.DollarVolume)
def get_history_by_symbol(self,symbol,period,resolution=None,attr=None):
newDF = pd.DataFrame() #creates a new dataframe that's empty
try:
#quantopian s history method allows symbol as object or string but not QC so we need to always
# convert to symbol before calling history method to streamline compatibility
sym = self.symbolAsString(symbol)
if attr is None:
attr = "close"
if resolution is None:
resolution = "Resolution.Daily"
try:
#make sure it exists in our securities list first
price = Securities[sym].Close
except:
equity=self.AddEquity(sym,resolution)
history = self.History(equity.Symbol,period,resolution)
df_history = history[attr].unstack(level=0).dropna()
return df_history
except:
self.Log('An unknown error occurred trying get history by_symbol.' + str(sys.exc_info()[0]) )
return newDF
def symbolAsString(self,symbol):
if isinstance(symbol,Securities.Equity.Equity):
return str(symbol.Symbol)
else:
return str(symbol)