| Overall Statistics |
|
Total Orders 6 Average Win 0.14% Average Loss 0% Compounding Annual Return 17.985% Drawdown 0.200% Expectancy 0 Start Equity 100000 End Equity 100348 Net Profit 0.348% Sharpe Ratio 12.248 Sortino Ratio 0 Probabilistic Sharpe Ratio 100.000% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0.081 Beta 0.004 Annual Standard Deviation 0.006 Annual Variance 0 Information Ratio 2.933 Tracking Error 0.123 Treynor Ratio 21.05 Total Fees $27.00 Estimated Strategy Capacity $0 Lowest Capacity Asset SPY YI9D6FI4Z6YU|SPY R735QTJ8XC9X Portfolio Turnover 0.10% |
# 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 AlgorithmImports import *
from QuantConnect.Algorithm.CSharp import *
### <summary>
### Regression algorithm asserting we can specify a custom option exercise model
### </summary>
class CustomOptionExerciseModelRegressionAlgorithm(OptionAssignmentRegressionAlgorithm):
def Initialize(self):
self.SetSecurityInitializer(self.CustomSecurityInitializer)
super().Initialize()
def CustomSecurityInitializer(self, security):
if Extensions.IsOption(security.Symbol.SecurityType):
security.SetOptionExerciseModel(CustomExerciseModel())
def OnData(self, data):
super().OnData(data)
class CustomExerciseModel(DefaultExerciseModel):
def OptionExercise(self, option: Option, order: OptionExerciseOrder):
order_event = OrderEvent(
order.Id,
option.Symbol,
Extensions.ConvertToUtc(option.LocalTime, option.Exchange.TimeZone),
OrderStatus.Filled,
Extensions.GetOrderDirection(order.Quantity),
0.0,
order.Quantity,
OrderFee.Zero,
"Tag"
)
order_event.IsAssignment = False
return [ order_event ]#region imports
from AlgorithmImports import *
#endregion
import pandas as pd
import random
from AlgorithmImports import *
from QuantConnect.Algorithm.CSharp import *
class CustomExerciseModel(DefaultExerciseModel):
def OptionExercise(self, option: Option, order: OptionExerciseOrder):
order_event = OrderEvent(
order.Id,
option.Symbol,
Extensions.ConvertToUtc(option.LocalTime, option.Exchange.TimeZone),
OrderStatus.Filled,
Extensions.GetOrderDirection(order.Quantity),
0.0,
order.Quantity,
OrderFee.Zero,
"Tag"
)
order_event.IsAssignment = False
return [ order_event ]
class VirtualYellowGiraffe(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2024, 4, 23)
self.SetEndDate(2024, 4, 30)
self.SetCash(100000)
self.equity = self.AddEquity("SPY", Resolution.Minute)
self.symbol = self.equity.Symbol
self.InitOptionsAndGreeks(self.equity)
## Margin model
#self.Portfolio.MarginCallModel = DefaultMarginCallModel(self.Portfolio, self.DefaultOrderProperties)
self.Portfolio.MarginCallModel = MarginCallModel.Null
self.SetSecurityInitializer(self.CustomSecurityInitializer)
## Options to target
self.targetDELTA_STO = 0.03
self.targetExpiryDTE_STO = 14
self.minExpiryDTE = 7
self.contracts = 10
self.call_trade = False
self.put_trade = False
def CustomSecurityInitializer(self, security):
if Extensions.IsOption(security.Symbol.SecurityType):
security.SetOptionExerciseModel(CustomExerciseModel())
## Initialize Options settings, chain filters, pricing models, etc
## ====================================================================
def InitOptionsAndGreeks(self, theEquity ):
## 1. Specify the data normalization mode (must be 'Raw' for options)
theEquity.SetDataNormalizationMode(DataNormalizationMode.Raw)
## 2. Set Warmup period of at leasr 10 days
self.SetWarmup(15, Resolution.Daily)
## 3. Set the security initializer to call SetMarketPrice
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
## 4. Subscribe to the option feed for the symbol
theOptionSubscription = self.AddOption(theEquity.Symbol)
## 5. set the pricing model, to calculate Greeks and volatility
theOptionSubscription.PriceModel = OptionPriceModels.CrankNicolsonFD() # both European & American, automatically
## 6. Set the function to filter out strikes and expiry dates from the option chain
theOptionSubscription.SetFilter(self.OptionsFilterFunction)
def OnData(self, data):
## If we're done warming up, not invested
if (not self.IsWarmingUp) and (not self.Portfolio.Invested) and (data.Bars.ContainsKey(self.symbol)):
if (self.call_trade==False) and (self.put_trade==False) and (self.Time.hour >= 15):
DTE_STO = self.targetExpiryDTE_STO
## Get contracts at delta=targetDELTA and DTE=targetExpiryDTE
self.call_STO = self.SelectContract(self.equity.Symbol, self.targetDELTA_STO, 'delta', DTE_STO, OptionRight.Call)
self.put_STO = self.SelectContract(self.equity.Symbol, self.targetDELTA_STO, 'delta', DTE_STO, OptionRight.Put)
self.order(self.call_STO, self.contracts, 'STO')
self.order(self.put_STO, self.contracts, 'STO')
self.call_trade = True
self.put_trade = True
elif (not self.IsWarmingUp) and (self.Portfolio.Invested) and (data.Bars.ContainsKey(self.symbol)):
if (self.call_trade==True) and (self.put_trade==True):
## Liquidate if expiry of option is close
if ((self.call_STO.Expiry - self.Time).days + 1 <= self.minExpiryDTE) and (self.Time.hour >= 14):
self.order(self.call_STO, self.contracts, 'BTC')
self.order(self.put_STO, self.contracts, 'BTC')
self.call_trade = False
self.put_trade = False
## Underlying symbol, delta, days till expiration, Option right (put or call)
## ============================================================================
def order(self, symbolArg, contracts, ordertype= 'STO'):
#self.current_delta = self.Get_Current_Delta(symbolArg.Symbol)
## construct an order message -- good for debugging and order records
if ordertype == 'STO':
orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} |" + \
f"STO {symbolArg.Symbol} "
elif ordertype == 'BTC':
orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} |" + \
f"BTC {symbolArg.Symbol} "
#self.Debug(f"{self.Time} {orderMessage}")
if ordertype == 'STO':
self.Order(symbolArg.Symbol, -contracts, False, orderMessage)
elif ordertype == 'BTC':
self.Order(symbolArg.Symbol, contracts, False, orderMessage)
## Get an options contract that matches the specified criteria:
## Underlying symbol, delta, days till expiration, Option right (put or call)
## ============================================================================
def SelectContract(self, symbolArg, strikeArg, strikeArgName, expiryDTE, optionRightArg= OptionRight.Call):
canonicalSymbol = self.AddOption(symbolArg)
theOptionChain = self.CurrentSlice.OptionChains[canonicalSymbol.Symbol]
theExpiryDate = self.Time + timedelta(days=expiryDTE)
## Filter the Call/Put options contracts
filteredContracts = [x for x in theOptionChain if x.Right == optionRightArg]
## Sort the contracts according to their closeness to our desired expiry
contractsSortedByExpiration = sorted(filteredContracts, key=lambda p: abs((p.Expiry+timedelta(hours=16)) - theExpiryDate), reverse=False)
closestExpirationDate = contractsSortedByExpiration[0].Expiry
## Get all contracts for selected expiration
contractsMatchingExpiryDTE = [contract for contract in contractsSortedByExpiration if contract.Expiry == closestExpirationDate]
## Get the contract with the contract with the closest delta
if strikeArgName == 'delta':
closestContract = min(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-strikeArg))
return closestContract
## The options filter function.
## Filter the options chain so we only have relevant strikes & expiration dates.
## =============================================================================
def OptionsFilterFunction(self, optionsContractsChain):
strikeCount = 500 # no of strikes around underyling price => for universe selection
min_Expiry_DTE = self.minExpiryDTE # min num of days to expiration => for uni selection
max_Expiry_DTE = self.targetExpiryDTE_STO # max num of days to expiration => for uni selection
## Select options including both monthly and weekly
return optionsContractsChain.IncludeWeeklys()\
.Strikes(-strikeCount, strikeCount)\
.Expiration(timedelta(min_Expiry_DTE), timedelta(max_Expiry_DTE))
#region imports
from AlgorithmImports import *
#endregion
class VirtualYellowGiraffe(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 4, 17)
self.SetEndDate(2021, 2, 17)
self.SetCash(100000)
self.equity = self.AddEquity("SPY", Resolution.Minute)
self.InitOptionsAndGreeks(self.equity)
## Initialize Options settings, chain filters, pricing models, etc
## ====================================================================
def InitOptionsAndGreeks(self, theEquity ):
## 1. Specify the data normalization mode (must be 'Raw' for options)
theEquity.SetDataNormalizationMode(DataNormalizationMode.Raw)
## 2. Set Warmup period of at leasr 30 days
self.SetWarmup(30, Resolution.Daily)
## 3. Set the security initializer to call SetMarketPrice
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
## 4. Subscribe to the option feed for the symbol
theOptionSubscription = self.AddOption(theEquity.Symbol)
## 5. set the pricing model, to calculate Greeks and volatility
theOptionSubscription.PriceModel = OptionPriceModels.CrankNicolsonFD() # both European & American, automatically
## 6. Set the function to filter out strikes and expiry dates from the option chain
theOptionSubscription.SetFilter(self.OptionsFilterFunction)
def OnData(self, data):
## If we're done warming up, and not invested, Sell a put.
if (not self.IsWarmingUp) and (not self.Portfolio.Invested):
self.SellAnOTMPut()
## Sell an OTM Put Option.
## Use Delta to select a put contract to sell
## ==================================================================
def SellAnOTMPut(self):
## Sell a 20 delta put expiring in 2 weeks (14 days)
putContract = self.SelectContractByDelta(self.equity.Symbol, .30, 10, OptionRight.Put)
## construct an order message -- good for debugging and order rrecords
orderMessage = f"Stock @ ${self.CurrentSlice[self.equity.Symbol].Close} |" + \
f"Sell {putContract.Symbol} "+ \
f"({round(putContract.Greeks.Delta,2)} Delta)"
self.Debug(f"{self.Time} {orderMessage}")
self.Order(putContract.Symbol, -1, False, orderMessage )
## Get an options contract that matches the specified criteria:
## Underlying symbol, delta, days till expiration, Option right (put or call)
## ============================================================================
def SelectContractByDelta(self, symbolArg, strikeDeltaArg, expiryDTE, optionRightArg= OptionRight.Call):
canonicalSymbol = self.AddOption(symbolArg)
theOptionChain = self.CurrentSlice.OptionChains[canonicalSymbol.Symbol]
theExpiryDate = self.Time + timedelta(days=expiryDTE)
## Filter the Call/Put options contracts
filteredContracts = [x for x in theOptionChain if x.Right == optionRightArg]
## Sort the contracts according to their closeness to our desired expiry
contractsSortedByExpiration = sorted(filteredContracts, key=lambda p: abs(p.Expiry - theExpiryDate), reverse=False)
closestExpirationDate = contractsSortedByExpiration[0].Expiry
## Get all contracts for selected expiration
contractsMatchingExpiryDTE = [contract for contract in contractsSortedByExpiration if contract.Expiry == closestExpirationDate]
## Get the contract with the contract with the closest delta
closestContract = min(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-strikeDeltaArg))
return closestContract
## The options filter function.
## Filter the options chain so we only have relevant strikes & expiration dates.
## =============================================================================
def OptionsFilterFunction(self, optionsContractsChain):
strikeCount = 100 # no of strikes around underyling price => for universe selection
minExpiryDTE = 10 # min num of days to expiration => for uni selection
maxExpiryDTE = 40 # max num of days to expiration => for uni selection
return optionsContractsChain.IncludeWeeklys()\
.Strikes(-strikeCount, strikeCount)\
.Expiration(timedelta(minExpiryDTE), timedelta(maxExpiryDTE))