Overall Statistics
Total Trades
11
Average Win
9.75%
Average Loss
-5.53%
Compounding Annual Return
0.388%
Drawdown
3.000%
Expectancy
0.106
Net Profit
0.063%
Sharpe Ratio
0.087
Loss Rate
60%
Win Rate
40%
Profit-Loss Ratio
1.76
Alpha
-0.279
Beta
0.312
Annual Standard Deviation
0.089
Annual Variance
0.008
Information Ratio
-7.296
Tracking Error
0.125
Treynor Ratio
0.025
Total Fees
$4.00
# 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.

class ButterflySpreadAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 4, 1)
        self.SetEndDate(2017, 5, 30)
        self.SetCash(150000)
        equity = self.AddEquity("GOOG", Resolution.Minute)
        self.underlying = equity.Symbol
        self.SetBenchmark(equity.Symbol)
        self.itm_call = str()
        self.otm_call = str()
        self.atm_call = str()

    def OnData(self,slice):

        ''' OptionChainProvider gets the option chain provider,
            used to get the list of option contracts for an underlying symbol.
            Then you can manually filter the contract list returned by GetOptionContractList.
            The manual filtering will be limited to the information
            included in the Symbol (strike, expiration, type, style) and/or prices from a History call '''
        if self.Securities.ContainsKey(self.itm_call) and self.Securities.ContainsKey(self.otm_call) and self.Securities.ContainsKey(self.atm_call) and not self.Portfolio.Invested:
            self.Buy(self.itm_call, 1)
            self.Buy(self.otm_call, 1) 
            self.Sell(self.atm_call, 2)
        
        if self.Portfolio[self.underlying].Invested:
            self.Liquidate(self.underlying)

        if not self.Portfolio.Invested:

            filtered_contracts = self.InitialFilter(-9, 9, 30, 60)
            # sorted the optionchain by expiration date and choose the furthest date
            if len(filtered_contracts) < 1: return
            expiry = sorted(filtered_contracts,key = lambda x: x.ID.Date, reverse=True)[0].ID.Date
            # filter the call options from the contracts expires on that date
            call = [i for i in filtered_contracts if i.ID.Date == expiry and i.ID.OptionRight == 0]
            # sorted the contracts according to their strike prices 
            call_contracts = sorted(call,key = lambda x: x.ID.StrikePrice)    
            # choose OTM call 
            self.otm_call = call_contracts[-1]
            # choose ITM call 
            self.itm_call = call_contracts[0]
            # choose ATM call
            undelyingPrice = self.Securities[self.underlying].Price
            self.atm_call = sorted(call_contracts,key = lambda x: abs(undelyingPrice - x.ID.StrikePrice))[0]
            self.trade_contracts = [self.itm_call, self.otm_call, self.atm_call]
            for i in self.trade_contracts:        
                self.AddOptionContract(i, Resolution.Minute)


    def InitialFilter(self, min_strike_rank, max_strike_rank, min_expiry, max_expiry):
        
        ''' This method is an initial filter of option contracts 
            according to the range of strike price and the expiration date '''
            
        contracts = self.OptionChainProvider.GetOptionContractList(self.underlying, self.Time.date())
        if len(contracts) == 0 : return []
        # fitler the contracts based on the expiry range
        contract_list = [i for i in contracts if min_expiry < (i.ID.Date.date() - self.Time.date()).days < max_expiry]
        # find the strike price of ATM option
        atm_strike = sorted(contract_list,
                            key = lambda x: abs(x.ID.StrikePrice - self.Securities[self.underlying].Price))[0].ID.StrikePrice
        strike_list = sorted(set([i.ID.StrikePrice for i in contract_list]))
        # find the index of ATM strike in the sorted strike list
        atm_strike_rank = strike_list.index(atm_strike)
        try: 
            strikes = strike_list[(atm_strike_rank + min_strike_rank):(atm_strike_rank + max_strike_rank)]
        except:
            strikes = strike_list
        filtered_contracts = [i for i in contract_list if i.ID.StrikePrice in strikes]
        return filtered_contracts 

    def OnOrderEvent(self, orderEvent):
        self.Log(str(orderEvent))