Overall Statistics
import numpy as np
import datetime
from datetime import timedelta 
import decimal 
import time 
import pandas as pd 
from pandas.tseries.offsets import BDay
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Securities.Option import OptionPriceModels
from QuantConnect.Algorithm.Framework.Alphas import *
import json

#from earnings_dates import dates 

class  MultidimensionalHorizontalShield(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018,7,20)  # Set Start Date
        self.SetEndDate(2018,7,30) # Set End Date
        self.SetCash(30000)  # Set Strategy Cash
        # self.earningsDates is a list with  AAPL earnings dates. The code to get these dates is 
        # in the file earnings_dates
        # Is possible to do this approach with other stocks 
        # Set InteractiveBrokers Brokerage model
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        self.tickers = ['AAPL','NFLX','AMZN','GOOG']
        self.earningDates = {}
        #Select multiple stocks from API
        if self.LiveMode:
            url = 'https://cloud.iexapis.com/stable/stock/market/batch?symbols=aapl,nflx,amzn,goog&types=estimates&token=pk_7fed8485fc2e4539aa40b42e33d2e9eb'
            apiResponse = self.Download(url)
            apiData = json.loads(apiResponse)
            # In this case self.earningDates is a dictionary that would have stock symbols in keys
            # and dates in values.
            self.earningDates = {}
            for map in apiData:
                dates = apiData[map]['estimates']['estimates'][0]['reportDate']
                dates_object = datetime.datetime.strptime(dates, "%Y-%m-%d")
                self.earningDates[map] = dates_object
        url = 'https://cloud.iexapis.com/stable/stock/market/batch?symbols=aapl,nflx,amzn,goog&types=earnings&last=10&token=pk_7fed8485fc2e4539aa40b42e33d2e9eb'
        apiResponse = self.Download(url)
        apiData = json.loads(apiResponse)
        for map in apiData:
            for i in range(0,len(apiData)):
                dates = apiData[map]['earnings']['earnings'][i]['EPSReportDate']
                dates_object = datetime.datetime.strptime(dates, "%Y-%m-%d")
                self.earningDates.setdefault(map, [])
        for ticker in self.tickers:
            equity = self.AddEquity(ticker, Resolution.Minute)
        self.equity = equity.Symbol
        self.openTransaction = None
        self.symbolToTrade = None
        self.closeByExpiration = True
        self.closeByProfitLoss = False
        self.maxOpenPositions = 5
        self.limitOrders = []
        self.symbols = []
        # Add the options 
        for ticker in self.tickers:
            option = self.AddOption(ticker,Resolution.Minute)
            option.SetFilter(lambda universe: universe.WeeklysOnly().Strikes(-10,+10).Expiration(timedelta(0), timedelta(30)))

            #option.SetFilter(-10, +10, timedelta(0), timedelta(30))
        #option = self.AddOption(self.symbol, Resolution.Minute)

        #self.option_symbol = option.Symbol

        # The default setFilter is with monthly expiration. I comment this to use weekly expiration
        #option.SetFilter(-10, +10, timedelta(0), timedelta(30))
        # Select contract by weekly expiration
        #option.SetFilter(lambda universe: universe.WeeklysOnly().Strikes(-10,+10).Expiration(timedelta(0), timedelta(30)))
        # This is the Pricing model to calculate the Delta for each contract
        option.PriceModel = OptionPriceModels.CrankNicolsonFD() # Pricing model to gets delta
        # Warm up period
        # One Schedule function that will run all days at 12:00 to look if the current time is 2 days before earnging date
        self.TimeRules.At(12,0),  self.ActivateTransaction)

        # Schudele function to run the conditions to sell. Will run all days at 9:45 am
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 45),         
    def OnData(self, slice):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
                data: Slice object keyed by symbol containing the stock data


        # At 15:45 each day if self.openTransaction is True, look for options contracts to buy
        if self.Time.hour ==15 and self.Time.minute ==45 and self.openTransaction==True:
            options_info = self.select_options(slice)
            if options_info is not None:
                call , midPriceCall, put, midPricePut = options_info
                self.Debug('call contract is %s' % call)
                CallPrice = round(midPriceCall + 0.04,2)
                PutPrice = round(midPricePut + 0.04,2)
                # Buy Limit Orders for put and call 
                Callticket = self.LimitOrder(call,1,CallPrice)
                Putticket = self.LimitOrder(put,1,PutPrice)
                self.Debug('ticket is %s' % Callticket)
                self.limitOrders.append(Putticket) # append the order ticket in the limitOrders list 
                self.openTransaction = False # Set self.openTransaction to false in order to stop buying in the current day
                self.Debug('Cannot processing options information')
    def ActivateTransaction(self):
        This function look if the current date is 2 business days before the earnings date. 
        If this is the case the flag self.openTransaction is equal to True and the algo is
        ready to make orders
       # if self.Time.date().isoweekday() ==1:
        # Loop over the self.earningDates dictionary and if the earning dates of a stock is two days
        # ahead the current date, allow to trade setting the self.openTransaction to True and grab
        # that stock in self.symbolToTrade
        # If current date is two days before earnings date and if is weekday, set self.openTransaction
        # to True. This means to allow orders.
        two_days_ahead = (self.Time.date() + BDay(2)).date() 
        for stock, dates in self.earningDates.items():
            for date in dates:
                if (two_days_ahead) == date.date() and self.Time.date().isoweekday() <=5:
                    self.Debug('Two days before earning release for stock %s %s %s ' % (two_days_ahead, date.date(),stock))
                    self.openTransaction = True
                    self.symbolToTrade = stock
    def select_options(self,slice):
        if (slice.OptionChains.Count == 0):
            self.Debug('There are not options contracts in this chain at date %s' % self.Time.date())
        for key in self.earningDates:
            if key == self.symbolToTrade:
                self.Debug('key is %s' % key)
                self.Debug('symbolToTrade is %s' % self.symbolToTrade)
                for kvp in slice.OptionChains:
                    self.Debug('kvpkey %s' % kvp.Key)
                    if kvp.Key == key:
                        chain = kvp.Value       # Retrieve option chain only for the stock within 2 days for earnings report
                        self.Debug('symbol to look option chain %s' % key)
                        # Select At the Money calls and puts
                        # Make 2 lists called atm_calls and atm_puts
                        atm_calls = [x for x in chain if x.Strike <= x.UnderlyingLastPrice and  x.Right == 0 and (x.Expiry.date() - self.Time.date()).days >= 7]
                        atm_puts = [x for x in chain if x.Strike >= x.UnderlyingLastPrice and  x.Right == 1 and (x.Expiry.date() - self.Time.date()).days >= 7]
                        # Debugging messages that are comment to prevent flooding the screen
                        deltasCall = [x.Greeks.Delta for x in atm_calls]
                        deltasPut = [x.Greeks.Delta for x in atm_puts]
                        #self.Debug('Deltas for call contracts are %s' % deltasCall)
                        #self.Debug('Deltas for put contracts are %s' % deltasPut)
                        # Find the neares delta to 0.5 for both put and call
                        nearCallDelta = (np.abs(np.array(deltasCall)-0.5)).argmin()
                        nearPutDelta = (np.abs(np.array(deltasPut)+0.5)).argmin()
                        # The method argmin() return the index with the delta nearest to 0.5. Then we use that 
                        # index to lookup atm_calls and atm_puts
                        call = atm_calls[nearCallDelta]
                        put = atm_puts[nearPutDelta]
                        self.Debug('Nearest call delta to 0.5 is %s' % call.Greeks.Delta)
                        self.Debug('Nearest put delta to -0.5 is %s' % put.Greeks.Delta)
                        callbid = call.BidPrice
                        callask = call.AskPrice
                        self.midPriceCall = round((callbid + callask)/2,2)
                        putbid = put.BidPrice
                        putask = put.AskPrice
                        self.midPricePut = round((putbid + putask)/2,2)
                        self.call = call.Symbol
                        self.put = put.Symbol
                        self.Debug('Underlying is %s' % self.Securities[self.equity].Price)
                        self.Debug('Call contract is %s delta %s expiration %s strike %s' % (call.Symbol, call.Greeks.Delta,call.Expiry,call.Strike))
                        self.Debug('Put contract is %s delta %s expiration %s strike %s' % (put.Symbol, put.Greeks.Delta,put.Expiry,put.Strike))
                        return self.call, self.midPriceCall , self.put, self.midPricePut 
    def CloseTrade(self):
        # Get all orders by calling the limitOrders list which have all order tickets
        orders = self.limitOrders # self.Transactions.GetOrders()
        if len(orders) == 0:
        # Use BDay(2) to substract 2 days to current date
        # five_days_ahead = (self.Time.date() - BDay(2)).date()
        # Use BDay(2) to substract 1 days to current date
        five_days_ahead = (self.Time.date() - BDay(1)).date()
        # The logic is the following: is self.closeByExpiration is set to True in Initialize,
        # loop over orders and if current date is 5 days after order date and order status is filled
        # sell the contract using order.Symbol by the QuantityFilled method.
        if self.closeByExpiration:
            for order in orders:
                if (five_days_ahead >= order.Time.date()) and (order.Status == OrderStatus.Filled):
                    self.Debug('sell %s after 5 days of order date' % order.Symbol)
                    self.openTransaction = False
                    self.limitOrders.remove(order)  # remove the order from self.limitOrders after sold
        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
        option_invested_values = [x.Value for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
        # If self.closeByProfitLoss is set to True in Initialize, loop over contract in Portfolio and 
        # get the current profit percentage of each contrct. If this profit percentage is greater than 
        # 50% or lower than -11%, Liquidate(sell) the contract. 
        if self.closeByProfitLoss:
            for contract in option_invested:
                underlying = self.Securities[self.equity].Price
                quantity = self.Portfolio[contract].Quantity
                lastPrice = self.Securities[contract].Price
                profits = round(self.Portfolio[contract].UnrealizedProfit,0)
                profit_percentage = self.Portfolio[contract].UnrealizedProfitPercent
                if (profit_percentage > 0.5) or (profit_percentage < -0.11):
                    self.Debug('Sell contract %s with profit/loss of %s' % (contract, profit_percentage))
                #self.Debug("Contract: " + str(contract)  +   " - Underlying Price: " + str(underlying) +  " - Quantity: "  + str(quantity) + " - Last Price: " + str(lastPrice))
                #self.Debug('Unrealized profits and profit percentage at date %s for contract %s are  %s  and  %s' % (self.Time.date(), contract, profits,"{0:.0%}".format(profit_percentage)))    
    def OnOrderEvent(self, orderEvent):
        ''' Event when the order is filled. Debug log the order fill. :OrderEvent:'''

        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        self.Debug("{0}: {1}: {2}".format(self.Time, order.Type, orderEvent))