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)