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 riskAdjustedTargets
import 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