| 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