Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe Ratio
0
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$0.00
import math
from datetime import timedelta

from QuantConnect.Securities.Option import OptionPriceModels
from QuantConnect.Orders import OrderStatus

class BullCallSpreadAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 1, 1)
        self.SetEndDate(2017, 2, 1)
        self.SetCash(10000)
        self.equity = self.AddEquity("SPY", Resolution.Minute)
        self.option = self.AddOption("SPY", Resolution.Minute)
        self.symbol = self.option.Symbol
    
        # set our strike/expiry filter for this option chain
        self.option.SetFilter(-20, 1, timedelta(15), timedelta(45))
        self.option.PriceModel = OptionPriceModels.CrankNicolsonFD()
        
        self.SetWarmUp(TimeSpan.FromDays(4))
        
        # use the underlying equity SPY as the benchmark
        self.SetBenchmark(self.equity.Symbol)
        self.Schedule.On(self.DateRules.EveryDay(self.symbol),
                         self.TimeRules.BeforeMarketClose(self.symbol, 30),
                         self.TradeOptions)
                         
        self.risk_factor = 0.5
        
    def OnData(self,slice):
        if self.equity.Symbol in self.Portfolio and self.Portfolio[self.equity.Symbol].Quantity != 0:
            self.Sell(self.equity.Symbol, self.Portfolio[self.equity.Symbol].Quantity)
        
        optionchain = slice.OptionChains
        valid = False
        for i in slice.OptionChains:
            if i.Key != self.symbol:
                continue
            if i.Value.Contracts.Count != 0:
                valid = True
                break
            else:
                self.Log("Chain is empty")
        if not valid:
            self.optionchain = None
            return
        self.optionchain = optionchain
 
    def TradeOptions(self):
        if self.IsWarmingUp:
            return
        if self.Portfolio.Invested:
            return
        if not self.optionchain:
            self.Log("No chain")
            return
        chain = None
        for i in self.optionchain:
            if i.Key != self.symbol: 
                continue
            chain = i.Value
            break
        
        if chain is None:
            self.Log("Chain not found")
            return
        
        if chain.Contracts.Count == 0:
            self.Log("Chain empty")
            return
            
        expiry = sorted(chain, key = lambda x: x.Expiry, reverse=True)[0].Expiry
        self.Log("Expiry is {}".format(expiry))
        puts = [i for i in chain if i.Expiry == expiry and i.Right == OptionRight.Put]
        if not puts:
            self.Log("No contracts for given expiry")
            return
        puts_by_delta = sorted(puts, key=lambda x: x.Greeks.Delta, reverse=True)    
        
        low_put = None
        high_put = None
            
        for contract in puts_by_delta:
            if abs(contract.Greeks.Delta) > 0.40 and low_put is None:
                low_put = contract
            if abs(contract.Greeks.Delta) > 0.45 and high_put is None:
                high_put = contract
                break
            
        if high_put and low_put:
            self.Log("Low Put: {}, High Put: {}".format(low_put.Greeks.Delta, high_put.Greeks.Delta))
        else:
            self.Log("Lowest Put: {}, Highest Put: {}".format(puts_by_delta[0].Greeks.Delta, puts_by_delta[-1].Greeks.Delta))
            return
        
        spread_risk = float(high_put.Strike - low_put.Strike) * 100.0
        if spread_risk == 0:
            self.Log("Strikes are the same.")
            return
        amount_to_invest = float(self.Portfolio.Cash) * self.risk_factor
        num_shares = math.floor(amount_to_invest / float(spread_risk))
        self.Log("Spread risk: {}, amount_to_invest: {}, num_shares: {}".format(spread_risk, amount_to_invest, num_shares))
        if num_shares > 0:
            if self.Sell(high_put.Symbol, num_shares).Status != OrderStatus.Filled:
                self.Log("Couldn't fill sell order")
                return
            if self.Buy(low_put.Symbol, num_shares).Status != OrderStatus.Filled:
                self.Log("Couldn't fill buy order, reversing sell order")
                self.Buy(high_put.Symbol, num_shares)
                return
            
        self.Schedule.On(self.DateRules.On(expiry.year, expiry.month, expiry.day),
                         self.TimeRules.BeforeMarketClose(self.symbol, 15),
                         self.CloseIfOTMNearExpiry)
    
    def CloseIfOTMNearExpiry(self):
        if self.Portfolio.TotalUnrealizedProfit < 0:
            for i in self.Portfolio:
                symbol = i.Key
                security = i.Value
                if security.Quantity == 0:
                    continue
                if security.IsLong:
                    self.Sell(symbol, security.Quantity)
                else:
                    self.Buy(symbol, security.Quantity)
            
    
    def OnOrderEvent(self, orderEvent):
        self.Log(str(orderEvent))