Overall Statistics
Total Trades
4
Average Win
0.10%
Average Loss
0%
Compounding Annual Return
87.019%
Drawdown
0.400%
Expectancy
0
Net Profit
0.573%
Sharpe Ratio
7.937
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0.046
Annual Variance
0.002
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$6.84
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")

from System import *
from copy import deepcopy
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Securities.Option import OptionPriceModels
from QuantConnect.Data import BaseData
from datetime import timedelta


class SlightlyRefined(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2019, 6, 1)
        self.SetEndDate(2019, 6, 3)
        self.SetCash(3000000)
        self.date = None
        
        # add the underlying asset
        self.stock = "GOOG"
        # the option
        self.option = self.AddOption(self.stock, Resolution.Minute)
        # the equity (not sure if this is needed? maybe for buying it is)
        self.equity = self.AddEquity(self.stock, Resolution.Minute)
        self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        
        # set the pricing model for Greeks and volatility
        # find more pricing models https://www.quantconnect.com/lean/documentation/topic27704.html
        self.option.PriceModel = OptionPriceModels.CrankNicolsonFD()
        # set the warm-up period for the pricing model
        # https://github.com/QuantConnect/Lean/blob/21cd972e99f70f007ce689bdaeeafe3cb4ea9c77/Common/Securities/Option/OptionPriceModels.cs
        self.SetWarmUp(TimeSpan.FromDays(4))
        # set our strike/expiry filter for this option chain
        self.option.SetFilter(-2, +2, timedelta(0), timedelta(90))
        
        # use the underlying equity as the benchmark
        self.SetBenchmark("GOOG")

        self.hedging_orders = []
        self.delta_threshold = 100
        self.num_threshold_orders = 1
        self.price_increment = 0.5
        self.initial_option_order = None
        self.purchased_option = None
        self.has_checked = False
        self.num_options_to_buy = 10
        # self.contract = str()
        # self.contractsAdded = set()
        # self.delta_threshold = 1
        # self.delta = 0


    def OnData(self, slice):
        '''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.hedging_orders and any(order.Status == OrderStatus.Filled for order in self.hedging_orders):
            # cancel all open orders, eventually we could just clear a list instead
            net_delta = self.purchased_option.Greeks.Delta * 100 * self.num_options_to_buy - self.Portfolio[self.stock].Quantity
            self.Log("num shares: {} current price: {}, delta {}".format(str(self.Portfolio[self.stock].Quantity), str(self.equity.Price), str(net_delta)))
            cancelledOrders = self.Transactions.CancelOpenOrders(self.stock)
            del self.hedging_orders[:]
            self.Log("here we go!")
            # There's really only one option contract in all of this nonsense but it's not possible
            # to look up the options contract more simply than this?
            for kvp in slice.OptionChains:
                chain = kvp.Value   # option contracts for each 'subscribed' symbol/key 
                traded_contracts = filter(lambda x: x.Symbol in [self.purchased_option.Symbol], chain)
            for this_option in traded_contracts:
                # start the delta neutral hedging
                self.delta_neutral_hedging(slice, this_option, is_buy=True)
                self.delta_neutral_hedging(slice, this_option, is_buy=False)
        
        if self.purchased_option: return

        # TODO we tried to get access to the options chain through more normal means
        # but this failed in vain. What the hell kind of objects do these even return.
        # if not self.stock in slice.OptionChains:
        #     if not self.has_checked:
        #         for this in slice.OptionChains:
        #             self.Log(type(this))
        #             # self.Log(type(that))
        #             self.Log(str(this))
        #             # self.Log(str(that))
        #         self.has_checked = True
        #     return

        for kvp in slice.OptionChains:
            if kvp.Key != self.option.Symbol: continue
            chain = kvp.Value
            # this_option_chain =   slice.OptionChains[self.stock]

            # we sort the contracts to find at the money (ATM) contract with farthest expiration
            contracts = sorted(sorted(sorted(chain, \
                key = lambda x: abs(chain.Underlying.Price - x.Strike)), \
                key = lambda x: x.Expiry, reverse=True), \
                key = lambda x: x.Right, reverse=True)
    
            # if found, trade it
            if contracts:
                self.purchased_option = contracts[0]
                self.initial_option_order = self.MarketOrder(self.purchased_option.Symbol, self.num_options_to_buy)
                # self.MarketOnCloseOrder(symbol, -1)
                # Start by hedging immediately  
                initial_delta = self.purchased_option.Greeks.Delta
                num_shares_to_hedge = int(round(initial_delta * 100 * self.num_options_to_buy))
                self.hedging_orders.append(self.MarketOrder(self.stock, num_shares_to_hedge))

    def OnOrderEvent(self, orderEvent):
        # # TODO if order filled event then start hedging
        if orderEvent.Status == OrderStatus.Filled:
            self.Log(str(orderEvent))
        #     self.Log("yeh ok here we go")
    
    def delta_neutral_hedging(self, slice, this_option, is_buy):
        sign = -1 if is_buy else 1
        thresholds_so_far = 0
        self.Log("in hedging the current price: {}".format(str(self.equity.Price)))
        this_option_object = self.option
        original_price = self.equity.Price
        new_price = original_price + self.price_increment * sign
        # TODO how to get this_option?
        # We're going to have a fail safe in case things get weird, eventually we
        # will have many such common sense fail safes for testing purposes.
        num_loops = 0
        while True:
            this_trade_bar = self.equity.GetLastData()
            # update needs a bunch of dummy variables which I hope don't affect the calculation
            this_trade_bar.Update(new_price, 1.0, 1.0, 1.0, 1.0, 1.0)
            # We're not sure yet what lasting effects setting market price has on the option object
            self.option.Underlying.SetMarketPrice(this_trade_bar)
            this_option_model_result = self.option.PriceModel.Evaluate(self.option, slice, this_option)
            this_delta = this_option_model_result.Greeks.Delta
            # delta * num options * num stocks per contract - num stocks already - thresholds covered
            # probably will take a couple of goes to make sure we've gotten the signs all correct
            # TODO how do we get the number of shares per options contract? It must be in the contract somewhere
            num_shares_to_hedge = this_delta * self.Portfolio[this_option.Symbol].Quantity * 100 \
                                    - self.Portfolio[self.stock].Quantity \
                                    - thresholds_so_far * self.delta_threshold * sign
            if self.delta_threshold <= abs(num_shares_to_hedge):
                thresholds_so_far += 1
                self.Log("ok hedging at price {} and num shares {}".format(new_price, -1 * sign * self.delta_threshold))
                this_order = self.LimitOrder(self.stock, -1 * sign * self.delta_threshold, new_price)
                self.hedging_orders.append(this_order)
            if self.num_threshold_orders <= thresholds_so_far :
                break
            new_price += self.price_increment * sign
            # just a fail safe
            num_loops += 1
            if 3000 < num_loops:
                self.Log("breaking the loop with price {} and num shares {}".format(new_price, num_shares_to_hedge))
                break
        this_trade_bar = self.equity.GetLastData()
        # update needs a bunch of dummy variables which I hope don't affect the calculation
        this_trade_bar.Update(original_price, 1.0, 1.0, 1.0, 1.0, 1.0)
        # We're not sure yet what lasting effects setting market price has on the option object
        self.option.Underlying.SetMarketPrice(this_trade_bar)