| Overall Statistics |
|
Total Trades 29 Average Win 4.10% Average Loss -8.33% Compounding Annual Return 47.597% Drawdown 9.200% Expectancy 0.279 Net Profit 33.950% Sharpe Ratio 1.866 Probabilistic Sharpe Ratio 71.900% Loss Rate 14% Win Rate 86% Profit-Loss Ratio 0.49 Alpha -0.015 Beta 1.772 Annual Standard Deviation 0.177 Annual Variance 0.031 Information Ratio 0.895 Tracking Error 0.152 Treynor Ratio 0.187 Total Fees $1450.00 Estimated Strategy Capacity $7000.00 Lowest Capacity Asset SPY 30R89NH8XNNTY|SPY R735QTJ8XC9X |
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from AlgorithmImports import *
class TrailingStopRiskManagementModel(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the maximum possible loss
measured from the highest unrealized profit'''
def __init__(self, maximumDrawdownPercent = 0.05):
'''Initializes a new instance of the TrailingStopRiskManagementModel class
Args:
maximumDrawdownPercent: The maximum percentage drawdown allowed for algorithm portfolio compared with the highest unrealized profit, defaults to 5% drawdown'''
self.maximumDrawdownPercent = abs(maximumDrawdownPercent)
self.trailing = dict()
def ManageRisk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
riskAdjustedTargets = list()
for kvp in algorithm.Securities:
symbol = kvp.Key
security = kvp.Value
# Remove if not invested
if not security.Invested:
self.trailing.pop(symbol, None)
continue
profitPercent = security.Holdings.UnrealizedProfitPercent
# Add newly invested securities
value = self.trailing.get(symbol)
if value == None:
newValue = profitPercent if profitPercent > 0 else 0
self.trailing[symbol] = newValue
continue
# Check for new high and update
if value < profitPercent:
self.trailing[symbol] = profitPercent
continue
# If unrealized profit percent deviates from local max for more than affordable percentage
if profitPercent < value - self.maximumDrawdownPercent:
# liquidate
riskAdjustedTargets.append(PortfolioTarget(symbol, 0))
return riskAdjustedTargetsimport TrailingStopRiskManagementModel as mr
class UpgradedRedOrangeParrot(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2017, 5, 1) # Set Start Date
self.SetEndDate(2018, 1, 29) # Set Start Date
self.SetCash(100000) # Set Strategy Cash
self.equity = self.AddEquity("SPY", Resolution.Minute)
self.AddOption("SPY", Resolution.Minute)
self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
self.symbol = self.equity.Symbol
self.numEntryPosition = 0.5 # Number of short selling call option
self.ToleranceOfLoss = 2 # 2 represents for stop loss in 100% loss
self.CostOfContract = 0
self.profitPercent=0
self.vix = self.AddData(CBOE, "VIX").Symbol
self.rank = 0
self.contract = str()
self.contractsAdded = set()
self.LatestAskPrice=1
self.DaysBeforeExp = 2
self.DTE = 45
self.OTM = 0.10
self.lookbackIV = 252
self.IVlvl = 0.25
self.percentage=0.5
self.security = self.Securities["SPY"]
self.SetBrokerageModel(MinimumAccountBalanceBrokerageModel(self,500.00))
self.Schedule.On(self.DateRules.EveryDay(self.symbol), \
self.TimeRules.AfterMarketOpen(self.symbol, 30), \
self.Plotting)
self.Schedule.On(self.DateRules.EveryDay(self.symbol),\
self.TimeRules.AfterMarketOpen(self.symbol, 30), \
self.VIXRank)
def VIXRank(self):
history = self.History(CBOE, self.vix, self.lookbackIV, Resolution.Daily)
self.rank = ((self.Securities[self.vix].Price - min(history[:-1]["low"])) / (max(history[:-1]["high"]) - min(history[:-1]["low"])) )
def OnData(self, data):
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
'''
if self.IsWarmingUp:
return
if self.rank > self.IVlvl:
self.SellPut(data)
#Only do this if profitable or hit stoploss, management code needed
if self.contract:
self.profitPercent = self.Securities[self.contract].Holdings.UnrealizedProfitPercent
if self.CostOfContract!=0 and self.Portfolio[self.contract].Price >= 2 * self.CostOfContract:
self.sellAll()
self.Log("Closed: Met 100% stop loss")
# elif self.CostOfContract!=0 and self.Portfolio[self.contract].Price <= self.CostOfContract/3:
elif self.profitPercent >0.50:
self.sellAll()
self.Log("Closed: took 50% profit")
elif self.contract != str() and(self.contract.ID.Date - self.Time) <= timedelta(self.DaysBeforeExp):
self.sellAll()
self.Log("Closed: too close to expiration")
def sellAll(self):
totalCost = -(self.CostOfContract*self.Portfolio[self.contract].Quantity)*100
pricePerContract=self.Portfolio[self.contract].Price*100
qty = -self.Portfolio[self.contract].Quantity
profit = totalCost-(pricePerContract*qty)
self.Log("Closed and took: " + str(profit) + "$ in profit on selling:" + str(self.contract) +" for:" + str(totalCost))
self.Liquidate(self.contract)
self.contractsAdded.remove(self.contract)
self.contract = str()
def SellPut(self, data):
if self.contract == str():
self.contract = self.OptionsFilter(data)
return
if not self.Portfolio[self.contract].Invested and data.ContainsKey(self.contract):
bpBefore = self.Portfolio.GetBuyingPower(self.symbol)
# self.Log("Starting BP: " + bp)
self.CostOfContract = self.MarketOrder(self.contract, -200).AverageFillPrice
# self.Log("Cost of contract: " + str(self.CostOfContract))
bpAfter = self.Portfolio.GetBuyingPower(self.symbol)
costOfThisTrade = bpBefore-bpAfter
self.Log("Cost of Trade " + str(costOfThisTrade))
def OptionsFilter(self, data):
contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, data.Time)
self.underlyingPrice = self.Securities[self.symbol].Price
otm_puts = [i for i in contracts if i.ID.OptionRight== OptionRight.Put and
self.underlyingPrice - i.ID.StrikePrice > self.OTM * self.underlyingPrice and
self.DTE - 8 < (i.ID.Date - data.Time).days < self.DTE + 8]
if len(otm_puts) > 0:
contract = sorted(sorted(otm_puts, key = lambda x: abs((x.ID.Date - self.Time).days - self.DTE)),
key = lambda x: self.underlyingPrice - x.ID.StrikePrice)[0]
if contract not in self.contractsAdded:
self.contractsAdded.add(contract)
self.AddOptionContract(contract, Resolution.Minute)
return contract
else:
return str()
# def OnOrderEvent(self, orderEvent):
# self.Log(self.profitPercent)
def Plotting(self):
self.Plot("Vol Chart", "Rank", self.rank)
self.Plot("Vol Chart", "lvl", self.IVlvl)
self.Plot("Data Chart", self.symbol, self.Securities[self.symbol].Close)
option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type == SecurityType.Option]
if option_invested:
self.Plot("Data Chart", "Strike", option_invested[0].ID.StrikePrice)
class MinimumAccountBalanceBrokerageModel(DefaultBrokerageModel):
'''Custom brokerage model that requires clients to maintain a minimum cash balance'''
def __init__(self, algorithm, minimumAccountBalance):
self.algorithm = algorithm
self.minimumAccountBalance = minimumAccountBalance
def CanSubmitOrder(self,security, order, message):
'''Prevent orders which would bring the account below a minimum cash balance'''
message = None
# we want to model brokerage requirement of minimumAccountBalance cash value in account
orderCost = order.GetValue(security)
cash = self.algorithm.Portfolio.Cash
cashAfterOrder = cash - orderCost
if cashAfterOrder < self.minimumAccountBalance:
# return a message describing why we're not allowing this order
message = BrokerageMessageEvent(BrokerageMessageType.Warning, "InsufficientRemainingCapital", "Account must maintain a minimum of ${0} USD at all times. Order ID: {1}".format(self.minimumAccountBalance, order.Id))
self.algorithm.Error(str(message))
return False
return True