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)