Overall Statistics |
Total Trades 391 Average Win 0.44% Average Loss -0.47% Compounding Annual Return 12.483% Drawdown 14.300% Expectancy 0.235 Net Profit 16.954% Sharpe Ratio 0.532 Loss Rate 36% Win Rate 64% Profit-Loss Ratio 0.93 Alpha 0.431 Beta -15.171 Annual Standard Deviation 0.267 Annual Variance 0.071 Information Ratio 0.461 Tracking Error 0.267 Treynor Ratio -0.009 Total Fees $1161.88 |
""" This file contains QuantConnect order codes for easy conversion and more intuitive custom order handling References: https://github.com/QuantConnect/Lean/blob/master/Common/Orders/OrderTypes.cs https://github.com/QuantConnect/Lean/blob/master/Common/Orders/OrderRequestStatus.cs """ OrderTypeKeys = [ 'Market', 'Limit', 'StopMarket', 'StopLimit', 'MarketOnOpen', 'MarketOnClose', 'OptionExercise', ] OrderTypeCodes = dict(zip(range(len(OrderTypeKeys)), OrderTypeKeys)) OrderDirectionKeys = ['Buy', 'Sell', 'Hold'] OrderDirectionCodes = dict(zip(range(len(OrderDirectionKeys)), OrderDirectionKeys)) ## NOTE ORDERSTATUS IS NOT IN SIMPLE NUMERICAL ORDER OrderStatusCodes = { 0:'New', # new order pre-submission to the order processor 1:'Submitted', # order submitted to the market 2:'PartiallyFilled', # partially filled, in market order 3:'Filled', # completed, filled, in market order 5:'Canceled', # order cancelled before filled 6:'None', # no order state yet 7:'Invalid', # order invalidated before it hit the market (e.g. insufficient capital) 8:'CancelPending', # order waiting for confirmation of cancellation }
import pandas as pd import numpy as np import decimal as d from datetime import datetime, timedelta, time from order_codes import (OrderTypeCodes, OrderDirectionCodes, OrderStatusCodes) class ModulatedResistanceContainmentField(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) # Set Start Date self.SetEndDate(2019, 5, 1) # Set End Date self.SetCash(10000) # Set Strategy Cash # self.AddEquity("SPY", Resolution.Minute) self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol # resolution for the data added to the universe self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.MyCoarseFilterFunction,self.FineSelectionFunction) self.symbols = [] self.stocks = [] self.orders_list = [] self.Age = {} self.stateData = {} self.stocks_worst =[] self.volume_filter = None self.pct_diff = None self.MaxCandidates=30 self.MaxBuyOrdersAtOnce=15 self.MyLeastPrice=1.15 self.MyMostPrice=1.49 self.MyFireSalePrice= self.MyLeastPrice self.MyFireSaleAge=3 self.MyCandidate = [] self.lossOrders = {} self.profitOrders = {} self.portfolioOrders = {} self.start_date = self.Time self.LowVar = 6 self.HighVar = 40 # self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen(self.spy, -45), Action(self.EveryDayBeforeMarketOpen)) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen(self.spy, 1), self.myRebalance) self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday,DayOfWeek.Tuesday,DayOfWeek.Wednesday,DayOfWeek.Thursday, DayOfWeek.Friday), self.TimeRules.At(13, 0), self.myRebalance) self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 10), self.track_variables) #self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose(self.spy, 1), self.cancel_open_orders) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose(self.spy, 1), self.cancel_open_buy_orders) def MyCoarseFilterFunction(self,coarce): '''The stocks must have fundamental data The stock must have positive previous-day close price and volume ''' # The first filter is to select stocks withs fundamental data, and price between the MyLeastPrice and MyMostPrice values filtered = [x for x in coarce if (x.HasFundamentalData) and (x.Volume > 0) and (x.Price >= self.MyLeastPrice) and (x.Price <= self.MyMostPrice)] first_filter_symbol = [x.Symbol for x in filtered] #self.Debug('first filter %s' % len(first_filter_symbol)) #self.Debug('%s length coarse fundamental filter %s' % (self.Time.date(),len(filtered))) # In this loop, for each symbol, we add the symbol to the self.stateData dictionary and update it with the values of EndTime,AdjustedPrice and DollarVolume # in order to have these values in the required past windows for each stock. for cf in filtered: if cf.Symbol not in self.stateData: self.stateData[cf.Symbol] = SelectionData(cf.Symbol) # Updates the SymbolData object with current EOD price avg = self.stateData[cf.Symbol] avg.update(cf.EndTime, cf.AdjustedPrice, cf.DollarVolume) # Get the stocks with the values of the three variables. With the is_ready method we take the stocks that have the # historical data required. values = list(filter(lambda x: x.is_ready, self.stateData.values())) number_of_days = (self.Time.date() - self.start_date.date()).days #self.Debug('Number of days loading stocks %s' % number_of_days) volume_values = [x.mean_volume for x in values] #self.Debug('Number of stocks in values %s' % len(values)) if volume_values: vol_small_percentile = np.percentile(volume_values, self.LowVar) vol_large_percentile = np.percentile(volume_values, self.HighVar) volume_filter = [x for x in values if (x.mean_volume > vol_small_percentile) and (x.mean_volume < vol_large_percentile)] # self.Debug('Number of stocks after volume filter %s' % volume_filter) #self.Debug('small percentile volume is %s' % vol_small_percentile) #self.Debug('large percentile volume is %s' % vol_large_percentile) stocks_by_perc_diff = sorted(volume_filter, key=lambda x:x.percent_difference) percent_diff = [x.percent_difference for x in stocks_by_perc_diff] # short_avg = [x.ShortAvg.Current.Value for x in values] # long_avg = [x.LongAvg.Current.Value for x in values] #self.Debug('short sma %s at date %s' % (short_avg, self.Time)) #self.Debug('long sma %s' % long_avg) #self.Debug('percent_diff %s' % percent_diff) symbols = [x.symbol for x in stocks_by_perc_diff] self.stocks = [x.Value for x in symbols] #self.Debug('on date %s' % self.Time) #self.Debug('symbol list length %s' % len(symbols)) #self.Debug(self.stocks_worst) #self.Debug(symbols) #self.Debug('length final symbols %s' % len(symbols)) if self.stocks: return [x.symbol for x in stocks_by_perc_diff] else: return [x.symbol for x in values[0:30]] def FineSelectionFunction(self,fine): ''' This function takes the stock of the CoarceFundamental function and narrow the list adding specific fundamental filters ''' #self.Debug('Lenght of Universe in fine at first %s' % len(fine)) fine_filter = [x.Symbol for x in fine if x.SecurityReference.IsPrimaryShare == 1 and x.SecurityReference.SecurityType == 'ST00000001' and x.CompanyReference.IsLimitedPartnership == 0 and x.SecurityReference.IsDepositaryReceipt == 0 ] # self.stocks_worst store the companies that meet all criteria self.symbols = [x for x in fine_filter] self.stocks_worst = [x for x in fine_filter[0:self.MaxCandidates]] # #self.Debug('%s length fine fundamental filter %s' % (self.Time.date(),len(self.symbols))) values = [x.Value for x in self.symbols] #self.Debug('Length of Universe in fine at final %s' % len(self.stocks_worst)) return self.stocks_worst def EveryDayBeforeMarketOpen(self): ''' This function runs at 8:45 of each day and look for stocks that are in the Portfolio. Then track the number of days the stock is in the portfolio with the self.Age dictionary and update this value ''' if not self.stocks_worst: return lowest_price = self.MyLeastPrice #reset beginning of day securities_invested = [x.Key for x in self.Portfolio if x.Value.Invested] #self.Debug('securities invested') #self.Debug(securities_invested) symbols_invested = [x.Symbol.Value for x in self.Portfolio.Values if x.Invested ] for stock in securities_invested: if stock == 'SPY': continue #self.Debug('stock invested %s' % stock) CurrPrice = self.Securities[stock].Price #self.Debug(CurrPrice) if CurrPrice < lowest_price: lowest_price = CurrPrice if stock.Value in self.Age.keys(): self.Age[stock.Value] += 1 else: self.Age[stock.Value] = 1 for stock in self.Age.keys(): if stock not in symbols_invested: self.Age[stock] = 0 message = 'stock.symbol: {symbol} : age: {age}' #self.Log(message.format(symbol=stock, age=self.Age[stock])) # this event fires whenever we have changes to our universe def OnSecuritiesChanged(self, changes): ''' This function is a built in function of QC, who trigger events when stocks are added or removed from the universe. In this function is possible to get the stock that are added to the universe using the AddedSecurities method and on the other hand is possible to know the stocks that were removed from the universe with the RemovedSecurities method ''' # # If the self.stocks_worst is not ready, this function does't run as only run with the stocks that were added to the # # custom universe if not self.stocks_worst: return # else: # self.Debug(self.stocks_worst) #self.Debug('Active Securities') #self.cancel_open_buy_orders() self.orders_stocks = [] BuyFactor = 0.97 WeightThisBuyOrder=float(1.00/self.MaxBuyOrdersAtOnce) cash= self.Portfolio.Cash #self.ActiveSecurities.Keys for security in changes.AddedSecurities: #self.Debug('Open orders for stock %s' % symbol.Value) #self.Debug('Security added to Universe %s at date %s' % (security.Symbol.Value, self.Time)) #self.Debug(self.Transactions.GetOpenOrders(symbol)) open_orders = self.Transactions.GetOpenOrders(security.Symbol) # Add another time filter price because splits and dividends events could affect the price #and not open_orders if not self.Portfolio[security.Symbol].Invested and security.Symbol.Value != 'SPY' and (self.MyLeastPrice <= self.Securities[security.Symbol].Price <= self.MyMostPrice):# and self.Securities.ContainsKey(security.Symbol): #not open_orders and: #self.Debug('Active Security in universe %s' % symbol.Value) PH = self.History(security.Symbol, 20, Resolution.Daily) if str(security.Symbol) not in PH.index: return close_history = PH.loc[str(security.Symbol)]['close'] PH_Avg = float(close_history.mean()) #self.Debug('mean price %s' % PH_Avg) CurrPrice = round(self.Securities[security.Symbol].Price,2) #self.Debug('Curr Price and PH_Avg %s %s of stock %s' % (CurrPrice,PH_Avg,security.Symbol.Value)) if np.isnan(CurrPrice) or CurrPrice == d.Decimal(0): pass # probably best to wait until nan goes away else: if CurrPrice > float(1.25 * PH_Avg): BuyPrice= round(CurrPrice,3) # self.Debug('price %s symbol %s' % (BuyPrice,symbol.Value)) else: BuyPrice= round(CurrPrice*BuyFactor,3) # self.Debug('price %s symbol %s' % (BuyPrice,symbol.Value)) StockShares = int(WeightThisBuyOrder*cash/BuyPrice) self.LimitOrder(security.Symbol, StockShares,BuyPrice, 'Purchase') #self.Debug('Submit limit order for stock %s with price %s at time %s' % (symbol.Value, BuyPrice,self.Time)) #self.SetHoldings(security.Symbol, 0.1) def myRebalance(self): ''' This function would sell stocks that are invested and meet conditions to sell. The function would run two times each day, once ate 9:30 am and second at 13:00 pm ''' #if not self.stocks_worst: # return stocks_worst = [x.Value for x in self.stocks_worst] #self.Debug('length of stocks_worst %s' % len(stocks_worst)) #self.Debug(stocks_worst) BuyFactor = 0.97 SellFactor=1.03 #self.Debug(self.Time) cash= self.Portfolio.Cash # Cancel all Open Orders #self.cancel_open_buy_orders() securities_invested = [x.Key for x in self.Portfolio if x.Value.Invested] # # Order sell at profit target in hope that somebody actually buys it for stock in securities_invested: sym = str(stock.Value) #self.Debug(' stock %s containskey %s' % (stock, self.Securities.ContainsKey(stock))) is_in_age = stock.Value in self.Age.keys() open_orders = self.Transactions.GetOpenOrders(stock) #self.Debug('open order for stock %s are %s' % (stock.Value, len(open_orders))) #self.Debug(' stock % age %s ' % (stock.Value ,age)) # self.Debug('stock %s is in age %s' % (stock.Value, is_in_age)) #self.Debug('%s Open orders %s' % (stock.Value ,self.Transactions.GetOpenOrders(stock))) StockShares = self.Portfolio[stock].Quantity CurrPrice = round(self.Securities[stock].Price,3) CostBasis = round(self.Portfolio[stock].AveragePrice,3) SellPrice = round(CostBasis*SellFactor,2) profits = round(self.Portfolio[stock].UnrealizedProfit,2) if len(open_orders) == 0 : if np.isnan(SellPrice): pass # probably best to wait until nan goes away if self.Securities.ContainsKey(stock): profit_order = self.LimitOrder(stock, -StockShares, SellPrice,'Sell with profit') self.profitOrders[sym] = profit_order self.Debug('send a limit order to sell stock %s for profit at price %s' % (stock.Value, SellPrice)) # if np.isnan(SellPrice): # pass # probably best to wait until nan goes away if sym in self.profitOrders.keys(): orderData = self.profitOrders[sym] profit_order_id = orderData.OrderId order = self.Transactions.GetOrderTicket(profit_order_id) if (OrderStatusCodes[order.Status]=='Filled'): return if (OrderStatusCodes[order.Status]=='Submitted') and (sym not in self.lossOrders.keys()): if is_in_age and self.MyFireSaleAge < self.Age[stock.Value] and ((self.MyFireSalePrice > CurrPrice or CostBasis>CurrPrice)): #and profits < -100 : # self.Debug('Condition to sell at loss is met for stock age %s %s' % (stock.Value, self.Age[stock.Value])) self.Debug('%s %s %s %s' % (stock.Value, self.Age[stock.Value], CurrPrice,CostBasis)) self.Debug('limit order to sell with loss stock %s' % stock.Value) #self.Liquidate(stock) SellPrice = round(.95*CurrPrice,2) loss_order = self.LimitOrder(stock, -StockShares, SellPrice,'Sell with loss') self.lossOrders[sym] = loss_order #self.loss_orders[stock.Value] = loss_order_ticket #self.Debug('send a limit order to sell to cut loss for stock %s at price %s' % (stock, SellPrice)) # else: # self.LimitOrder(stock, -StockShares, SellPrice,'Sell with profit') #self.Debug('send a limit order to sell to for profit for stock %s at price %s' % (stock, SellPrice)) def track_variables(self): SellFactor=1.03 if not self.stocks_worst: return securities_invested = [x.Key for x in self.Portfolio if x.Value.Invested] for stock in securities_invested: underlying = self.Securities[stock].Price StockShares= self.Portfolio[stock].Quantity lastPrice = self.Securities[stock].Price profits = round(self.Portfolio[stock].UnrealizedProfit,0) profit_percentage = self.Portfolio[stock].UnrealizedProfitPercent CurrPrice = round(self.Securities[stock].Price,3) CostBasis = round(self.Portfolio[stock].AveragePrice,3) SellPrice = round(CostBasis*SellFactor,2) age = self.Age[stock.Value] ##if age > 10: # self.Debug('Profits for stock %s are %s with age %s' % (stock.Value,profits,age)) # Uncomment these lines to track individual stocks # self.Debug("Stock %s: Quantity %s: Last Price %s" % (stock.Value, quantity, lastPrice)) # self.Debug('Unrealized profits and profit percentage at date %s for stock %s are %s and %s' % (self.Time.date(), stock.Value, profits,"{0:.0%}".format(profit_percentage))) # self.Debug('-------------------------------------------------------------------------------') openOrders = self.Transactions.GetOpenOrders() profits = self.Portfolio.TotalProfit unrealizedProfits = self.Portfolio.TotalUnrealizedProfit positions = len(securities_invested) if 0<len(self.Age): MaxAge=self.Age[max(self.Age.keys(), key=(lambda k: self.Age[k]))] #self.Debug(self.Age) #self.Debug('Max Age is %s' % MaxAge) leverage = self.Portfolio.TotalMarginUsed account_leverage = self.Portfolio.TotalAbsoluteHoldingsCost / self.Portfolio.TotalPortfolioValue #self.Debug('Total Profits %s , Total UnrealizedProfits %s , number of positions %s number openOrders %s at time %s' % (profits, unrealizedProfits, positions, len(openOrders), self.Time)) def log_open_orders(): oo = self.Transactions.GetOpenOrders() if len(oo) == 0: return for stock, orders in oo.iteritems(): for order in orders: message = 'Found open order for {amount} shares in {stock}' self.Log(message.format(amount=order.amount, stock=stock)) def cancel_open_buy_orders(self): oo = self.Transactions.GetOpenOrders() if len(oo) == 0: return # Cancel open orders to buy stocks that were open for more than 5 days for order in oo: #stock, orders in oo.iteritems(): order_time = order.Time.date() if order.Quantity > 0 and (self.Time.date() - order_time).days > 5:# and not self.Securities[order.Symbol].Invested and (self.Time.date() - order_time).days > 10: self.Transactions.CancelOrder(order.Id) #self.Transactions.CancelOpenOrders(order.Symbol) #for order in orders: message = 'Canceling buy order after 5 days with {amount} shares for {stock}' self.Log(message.format(amount=order.Quantity, stock=order.Symbol.Value)) # if 0 < order.amount: #it is a buy order # self.Transactions.CancelOpenOrders(stock) def cancel_open_orders(self): oo = self.Transactions.GetOpenOrders() securities_invested = [x.Key for x in self.Portfolio if x.Value.Invested] if len(oo) == 0: return #if not securities_invested: return for order in oo: order_time = order.Time.date() #order.Quantity < 0 and if (self.Time.date() - order_time).days > 10: self.Transactions.CancelOrder(order.Id) message = 'Canceling order of {amount} shares in {stock}' self.Log(message.format(amount=order.Quantity, stock=order.Symbol)) #self.Transactions.CancelOpenOrders(order.Symbol) #if self.Securities[order.Symbol].Invested: # This line test if the stock is in Age dict that is the same that the stock is in portfolio # self.Debug(order.Symbol) # self.Debug(order.Symbol.Value) # self.Debug(self.Age) # self.Debug('Test if the order %s meet conditions to be cancelled at the end of day' % order) #self.Debug('Order Symbol age order Status and order Amount %s %s %s %s #Self.' (order.Symbol.Value , self.Age[order.Symbol.Value], order.Status, order.Quantity)) # if order.Symbol.Value in self.Age and (self.Time.date() - order_time).days >5: #self.Debug(order) #self.Debug('Cancel Open Order to sell stock %s in portfolio as the order is not filled on current day' % order.Symbol.Value) # self.Debug('Cancel open Order to sell %s for stock %s status %s' % (order.Quantity, order.Symbol.Value,order.Status)) #else: # self.Debug('stock is not in Portfolio yet %s as the order is not filled %s ' % (order.Symbol.Value, order.Status)) def reset_parameters(): self.orders_list = [] def OnOrderEvent(self, orderEvent): ''' Event when the order is filled. Debug log the order fill. :OrderEvent:''' order = self.Transactions.GetOrderById(orderEvent.OrderId) self.Log(str(orderEvent)) if OrderStatusCodes[orderEvent.Status] == 'Submitted:#' or OrderStatusCodes[orderEvent.Status] == 'CancelPending': return k = str(orderEvent.Symbol.Value) symbol = str(orderEvent.Symbol) if k in self.profitOrders.keys(): orderData = self.profitOrders[k] profit_order_id = orderData.OrderId order = self.Transactions.GetOrderTicket(profit_order_id) # sometimes order is nonetype due to security price # is equal to zero #------------------------------------------------------# if not order: self.Log('order is nonetype: {}'.format(k)) del self.profitOrders[k] # delete order ticket data return if (OrderStatusCodes[order.Status]=='Filled') and (k in self.lossOrders.keys()): loss_order = self.lossOrders[k] loss_order_id = loss_order.OrderId if loss_order.Status == OrderStatus.Submitted: self.Log('cancelling loss open limitOrder for: {} {} with id {}'.format(self.Time, k,loss_order_id)) #self.Transactions.CancelOrder(loss_order_id) loss_order.Cancel() #self.Transactions.CancelOpenOrders(symbol) del self.lossOrders[k] del self.profitOrders[k] if k in self.lossOrders.keys(): orderData = self.lossOrders[k] loss_order_id = orderData.OrderId order = self.Transactions.GetOrderTicket(loss_order_id) # sometimes order is nonetype due to security price # is equal to zero #------------------------------------------------------# if not order: self.Log('order is nonetype: {}'.format(k)) del self.lossOrders[k] # delete order ticket data return if (OrderStatusCodes[order.Status]=='Filled') and (k in self.profitOrders.keys()): profit_order = self.profitOrders[k] profit_order_id = profit_order.OrderId if profit_order.Status == OrderStatus.Submitted: self.Log('cancelling profit open limitOrder for: {} {} with id'.format(self.Time, k, profit_order_id)) self.Transactions.CancelOpenOrders(symbol) del self.lossOrders[k] del self.profitOrders[k] #if order.Status == OrderStatus.Filled: # if self.Securities[order.Symbol.Value].Invested and self.Securities.ContainsKey(order.Symbol): # self.Debug('llega hasta aca') # if len(self.Transactions.GetOpenOrders(order.Symbol)) > 0: # oo = self.Transactions.GetOpenOrders(order.Symbol) # for order in oo: # order.Cancel() # del self.loss_orders[order.Symbol.Value] #self.Debug("%s: %s: %s: %s: %s: %s" % (self.Time, order.Symbol,order.Type, order.Price, order.Quantity,order.Status)) class SelectionData(object): def __init__(self, symbol): self.symbol = symbol self.ShortAvg = SimpleMovingAverage(3) self.LongAvg = SimpleMovingAverage(45) self.is_ready = False self.percent_difference = 0 self.volume = SimpleMovingAverage(20) self.mean_volume = 0 def update(self, time, price,volume): ### In "A and B and C" statement, when A is False, B and C will not be executed. ### Your strategy wants to update all the three SMA and then check if all of them are ready, ### So, the Update() methods should not be in the "and" statement condition. self.LongAvg.Update(time,price) self.ShortAvg.Update(time,price) self.volume.Update(time,volume) if self.LongAvg.IsReady and self.ShortAvg.IsReady and self.volume.IsReady: # if self.LongAvg.Update(time,price) and self.ShortAvg.Update(time,price) and self.volume.Update(time,volume): shortAvg = self.ShortAvg.Current.Value longAvg = self.LongAvg.Current.Value self.mean_volume = self.volume.Current.Value self.percent_difference = (shortAvg - longAvg) / longAvg self.is_ready = True #self.Debug('se actualizaron los indicadores') #self.is_ready = True #shortAvg = self.ShortAvg.Current.Value #longAvg = self.LongAvg.Current.Value #self.percent_difference = (shortAvg - longAvg) / longAvg