| Overall Statistics |
|
Total Trades 9 Average Win 1.39% Average Loss 0% Compounding Annual Return 95.755% Drawdown 13.000% Expectancy 0 Net Profit 5.806% Sharpe Ratio 1.994 Probabilistic Sharpe Ratio 58.147% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0.806 Beta 0.219 Annual Standard Deviation 0.351 Annual Variance 0.123 Information Ratio 2.597 Tracking Error 0.455 Treynor Ratio 3.205 Total Fees $118.25 Estimated Strategy Capacity $3000.00 Lowest Capacity Asset AAL 31UH85M5FQ06E|AAL VM9RIYHM8ACL |
# 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.
from QuantConnect.Securities.Option import OptionPriceModels
from datetime import timedelta
class OptionWhellAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage,AccountType.Margin)
self._no_K = 20 # no of strikes around ATM => for uni selection
self.MIN_EXPIRY = 3 # min num of days to expiration => for uni selection
self.MAX_EXPIRY = 12 # max num of days to expiration => for uni selection
self.MAX_DELTA_PUT = 0.4
self.MAX_DELTA_CALL = 0.4
self.TAKE_PROFIT = 0.2
self.MIN_PREMIUM = 0.1
self.ticker = 'AAL'
self.benchmarkTicker =self.ticker
self.SetStartDate(2022, 1, 1)
self.SetEndDate(2022, 1, 31)
self.SetCash(100000)
self.OPTION_COUNT = 5
self.expiry = None
self.resolution = Resolution.Minute
self.call, self.put, self.takeProfitTicket = None, None, None
equity = self.AddEquity(self.ticker, self.resolution)
option = self.AddOption(self.ticker, self.resolution)
self.symbol = option.Symbol
# set our strike/expiry filter for this option chain
option.SetFilter(self.UniverseFunc)
# for greeks and pricer (needs some warmup) - https://github.com/QuantConnect/Lean/blob/21cd972e99f70f007ce689bdaeeafe3cb4ea9c77/Common/Securities/Option/OptionPriceModels.cs#L81
# both European & American, automatically
option.PriceModel = OptionPriceModels.CrankNicolsonFD()
# this is needed for Greeks calcs
self.SetWarmUp(TimeSpan.FromDays(60)) # timedelta(7)
# use the underlying equity as the benchmark
self.SetBenchmark(self.benchmarkTicker)
def OnData(self, slice):
if (self.IsWarmingUp):
return
option_invested = [
x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type == SecurityType.Option]
equity_invested = [
x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type == SecurityType.Equity]
invested = [
x.Key for x in self.Portfolio if x.Value.Invested ]
if len(option_invested) == 1:
return
# If we already have underlying - check if we need to sell covered call
t=self.Portfolio[self.ticker]
#self.Debug(self.Portfolio[self.ticker].ToString())
if self.Portfolio[self.ticker].Quantity > 0:
self.Debug("TradeCallOption "+str(t)+" "+str(invested)+" ")
self.TradeCallOption(slice)
else:
self.Debug("TradePutOption "+str(t)+" "+str(invested)+" "+str(self.Portfolio.Cash))
self.TradePutOption(slice)
def TradePutOption(self, slice):
for i in slice.OptionChains:
if i.Key != self.symbol:
continue
chain = i.Value
# filter the put options contracts
puts = [x for x in chain if x.Right == OptionRight.Put and abs(x.Greeks.Delta) > 0 and abs(
x.Greeks.Delta) < self.MAX_DELTA_PUT and x.BidPrice > self.MIN_PREMIUM]
# sorted the contracts according to their expiration dates and choose the ATM options
contracts = sorted(sorted(puts, key=lambda x: x.BidPrice, reverse=True),
key=lambda x: x.Expiry)
if len(contracts) == 0:
continue
self.put = contracts[0].Symbol
self.expiry = contracts[0].Expiry
c=self.Portfolio.Cash
u=self.Portfolio.UnsettledCash
strike = contracts[0].Strike
self.OPTION_COUNT = int(c / 100 / strike)
# short the put options
ticket = self.MarketOrder(
self.put, -self.OPTION_COUNT, asynchronous=False)
# set Take Profit order
self.takeProfitTicket = self.LimitOrder(
self.put, self.OPTION_COUNT, round(ticket.AverageFillPrice * self.TAKE_PROFIT, 2))
def TradeCallOption(self, slice):
for i in slice.OptionChains:
if i.Key != self.symbol:
continue
chain = i.Value
# filter the put options contracts
calls = [x for x in chain if x.Right == OptionRight.Call and abs(x.Greeks.Delta) > 0 and abs(
x.Greeks.Delta) < self.MAX_DELTA_CALL and x.BidPrice > self.MIN_PREMIUM]
# sorted the contracts according to their expiration dates and choose the ATM options
contracts = sorted(sorted(calls, key=lambda x: x.BidPrice, reverse=True),
key=lambda x: x.Expiry)
if len(contracts) == 0:
continue
self.call = contracts[0].Symbol
self.expiry = contracts[0].Expiry
# short the call options
ticket = self.MarketOrder(
self.call, -self.OPTION_COUNT, asynchronous=False)
# set Take Profit order
self.takeProfitTicket = self.LimitOrder(
self.call, self.OPTION_COUNT, round(ticket.AverageFillPrice * self.TAKE_PROFIT, 2))
def OnOrderEvent(self, orderEvent):
self.Log("OnOrderEvent "+str(self.Portfolio[self.ticker])+" "+str(orderEvent))
def OnAssignmentOrderEvent(self, assignmentEvent):
self.Debug("OnAssignmentOrderEvent "+str(self.Portfolio[self.ticker])+" "+str(assignmentEvent))
if self.takeProfitTicket != None:
self.takeProfitTicket.cancel()
self.takeProfitTicket = None
def UniverseFunc(self, universe):
return universe.IncludeWeeklys()\
.Strikes(-self._no_K, self._no_K)\
.Expiration(timedelta(self.MIN_EXPIRY), timedelta(self.MAX_EXPIRY))
def OnFrameworkData(self):
return