| 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)