| Overall Statistics |
|
Total Orders 32 Average Win 4.43% Average Loss -3.11% Compounding Annual Return 11.774% Drawdown 14.200% Expectancy 0.617 Start Equity 100000 End Equity 139671.07 Net Profit 39.671% Sharpe Ratio 0.689 Sortino Ratio 0.56 Probabilistic Sharpe Ratio 38.500% Loss Rate 33% Win Rate 67% Profit-Loss Ratio 1.43 Alpha 0.046 Beta 0.199 Annual Standard Deviation 0.095 Annual Variance 0.009 Information Ratio -0.171 Tracking Error 0.175 Treynor Ratio 0.328 Total Fees $79.94 Estimated Strategy Capacity $73000000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX Portfolio Turnover 2.54% |
# region imports
from AlgorithmImports import *
import datetime
# endregion
class TrailingStopLoss(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2018, 1, 1)
self.SetEndDate(2021, 1, 1)
self.SetCash(100000)
self.qqq = self.AddEquity("QQQ", Resolution.Hour).Symbol
self.entryTicket = None #tracks the ticket of the entry order
self.stopMarketTicket = None #tracks the ticket of the exit order
#tracks the fill TIME of the entry and exit order
self.entryTime = datetime.datetime.min # initialized to current time
self.stopMarketOrderFillTime = datetime.datetime.min # initialized to current time
self.highestPrice = 0 #keep tracks of QQQs highest price
def OnData(self, data):
# wait 30 days since our last exit has passed:
if (self.Time - self.stopMarketOrderFillTime).days < 30:
return
price = self.Securities[self.qqq].Price
# only set limit order if we are not currently invested and there are not any active orders
# send a limit order for as 90% of our portfolio as shares of SPY as possible
if not self.Portfolio.Invested and not self.Transactions.GetOpenOrders(self.qqq):
quantity = self.CalculateOrderQuantity(self.qqq, 0.9) #allocates 90% of our portfolio to qqq
self.entryTicket = self.LimitOrder(self.qqq, quantity, price, "Entry Order")
self.entryTime = self.Time
# if limit order is not filled within a day, we move up the limit price
# to current price to increase chances of getting filled
if (self.Time - self.entryTime).days > 1 and self.entryTicket.Status != OrderStatus.Filled:
self.entryTime = self.Time #update the entry time to the current time
updateFields = UpdateOrderFields()
updateFields.LimitPrice = price #updates the limit price to the current price of QQQ
self.entryTicket.Update(updateFields) #.update() excecutes the change in our limit order
# move up the stop loss based on highest price
if self.stopMarketTicket is not None and self.Portfolio.Invested: #check if there is currently and order AND we are invested
# move up trailing stop price
if price > self.highestPrice:
self.highestPrice = price # update the price to the current price
updateFields = UpdateOrderFields()
updateFields.StopPrice = price * 0.95 # 5% trailing stop loss
self.stopMarketTicket.Update(updateFields)
#self.Debug(updateFields.StopPrice)
def OnOrderEvent(self, orderEvent):
if orderEvent.Status != OrderStatus.Filled:
return #an order can be: submitted, invalid, partially filled, etc. this makes sure we only care about the fully FILLED case
# all stop loss order setup is done in on order event handler
if self.entryTicket is not None and self.entryTicket.OrderId == orderEvent.OrderId:
self.stopMarketTicket = self.StopMarketOrder(self.qqq, -self.entryTicket.Quantity, 0.95 * self.entryTicket.AverageFillPrice)
# only sets stop loss if the entry order in the onData is filled
if self.stopMarketTicket is not None and self.stopMarketTicket.OrderId == orderEvent.OrderId:
self.stopMarketOrderFillTime = self.Time #ensures we will wait 30 days
self.highestPrice = 0 #reset the highest price variable to 0