Add more assertions to new options filter api regression algorithms

This commit is contained in:
Jhonathan Abreu
2024-07-24 18:39:58 -04:00
parent f930c11a93
commit 27ab68100d
9 changed files with 188 additions and 342 deletions
@@ -1,117 +0,0 @@
# 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 *
### <summary>
### Regression algorithm with new proposed option filter API using new options universe data (greeks, implied volatility, open interest, etc).
### </summary>
class BasicTemplateOptionsFilterAlgorithm(QCAlgorithm):
underlying_ticker = "GOOG"
def initialize(self):
self.set_start_date(2015, 12, 24)
self.set_end_date(2015, 12, 24)
self.set_cash(100000)
equity = self.add_equity(self.underlying_ticker)
option = self.add_option(self.underlying_ticker)
self.option_symbol = option.symbol
# set our strike/expiry filter for this option chain
option.set_filter(lambda u: u.strikes(-2, +2).expiration(0, 180))
# Filter by a single greek:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.delta(0.64, 0.65))
# Filter by multiple greeks:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.delta(0.64, 0.65)
.gamma(0.0008, 0.0010)
.vega(7.5, 10.5)
.theta(-1.10, -0.50)
.rho(4, 10))
# Some syntax sugar:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.d(0.64, 0.65)
.g(0.0008, 0.0010)
.v(7.5, 10.5)
.t(-1.10, -0.50)
.r(4, 10))
# Filter by open interest and/or implied volatility:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.open_interest(100, 1000)
.implied_volatility(0.10, 0.20))
# Some syntax sugar:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.oi(100, 1000)
.iv(0.10, 0.20))
# Having delegate filters with the whole contract data.
# We can reuse the OptionContract class for this. Might need some work on that side
# (new constructors/factor methods, some abstraction to not rely on the option price mode, etc) but it's a good idea.
# EXAMPLES:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.contracts(self.contracts_filter)) # def contracts_filter(self, contracts: list[OptionContract]) -> list[Symbol]:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.select(self.select_contract)) # def select_contract(self, contract: OptionContract) -> Symbol:
option.set_filter(lambda u: u
.strikes(-2, +2)
.expiration(0, 180)
.where(self.where_contract)) # def where_contract(self, contract: OptionContract) -> bool:
def contracts_filter(self, contracts: list[OptionUniverse]) -> list[Symbol]:
for contract in contracts:
# Can access the contract data here:
greeks = contract.greeks
iv = contract.implied_volatility
open_interest = contract.open_interest
yield contract.symbol
def select_contract(self, contract: OptionUniverse) -> Symbol:
# Can access the contract data here:
greeks = contract.greeks
iv = contract.implied_volatility
open_interest = contract.open_interest
return contract.symbol
def where_contract(self, contract: OptionUniverse) -> bool:
# Can access the contract data here:
greeks = contract.greeks
iv = contract.implied_volatility
open_interest = contract.open_interest
return True
@@ -28,31 +28,47 @@ class OptionUniverseFilterGreeksRegressionAlgorithm(QCAlgorithm):
option = self.add_option(underlying_ticker)
self.option_symbol = option.symbol
self._min_delta = 0.5
self._max_delta = 1.5
self._min_gamma = 0.0001
self._max_gamma = 0.0006
self._min_vega = 0.01
self._max_vega = 1.5
self._min_theta = -2.0
self._max_theta = -0.5
self._min_rho = 0.5
self._max_rho = 3.0
self._min_iv = 1.0
self._max_iv = 3.0
self._min_open_interest = 100
self._max_open_interest = 500
self.set_option_filter(option)
self.option_chain_received = False
def set_option_filter(self, security: Option) -> None:
# Contracts can be filtered by greeks, implied volatility, open interest:
security.set_filter(lambda u: u
.delta(0.5, 1.5)
.gamma(0.0001, 0.0006)
.vega(0.01, 1.5)
.theta(-2.0, -0.5)
.rho(0.5, 3.0)
.implied_volatility(1.0, 3.0)
.open_interest(100, 500))
.delta(self._min_delta, self._max_delta)
.gamma(self._min_gamma, self._max_gamma)
.vega(self._min_vega, self._max_vega)
.theta(self._min_theta, self._max_theta)
.rho(self._min_rho, self._max_rho)
.implied_volatility(self._min_iv, self._max_iv)
.open_interest(self._min_open_interest, self._max_open_interest))
# Note: there are also shortcuts for these filter methods:
'''
security.set_filter(lambda u: u
.d(0.5, 1.5)
.g(0.0001, 0.0006)
.v(0.01, 1.5)
.t(-2.0, -0.5)
.r(0.5, 3.0)
.iv(1.0, 3.0)
.oi(100, 500))
.d(self._min_delta, self._max_delta)
.g(self._min_gamma, self._max_gamma)
.v(self._min_vega, self._max_vega)
.t(self._min_theta, self._max_theta)
.r(self._min_rho, self._max_rho)
.iv(self._min_iv, self._max_iv)
.oi(self._min_open_interest, self._max_open_interest))
'''
def on_data(self, slice: Slice) -> None:
@@ -60,6 +76,25 @@ class OptionUniverseFilterGreeksRegressionAlgorithm(QCAlgorithm):
if chain and len(chain.contracts) > 0:
self.option_chain_received = True
for contract in chain:
if contract.Greeks.Delta < self._min_delta or contract.Greeks.Delta > self._max_delta:
raise RegressionTestException(f"Delta {contract.Greeks.Delta} is not within {self._min_delta} and {self._max_delta}")
if contract.Greeks.Gamma < self._min_gamma or contract.Greeks.Gamma > self._max_gamma:
raise RegressionTestException(f"Gamma {contract.Greeks.Gamma} is not within {self._min_gamma} and {self._max_gamma}")
if contract.Greeks.Vega < self._min_vega or contract.Greeks.Vega > self._max_vega:
raise RegressionTestException(f"Vega {contract.Greeks.Vega} is not within {self._min_vega} and {self._max_vega}")
if contract.Greeks.Theta < self._min_theta or contract.Greeks.Theta > self._max_theta:
raise RegressionTestException(f"Theta {contract.Greeks.Theta} is not within {self._min_theta} and {self._max_theta}")
if contract.Greeks.Rho < self._min_rho or contract.Greeks.Rho > self._max_rho:
raise RegressionTestException(f"Rho {contract.Greeks.Rho} is not within {self._min_rho} and {self._max_rho}")
if contract.ImpliedVolatility < self._min_iv or contract.ImpliedVolatility > self._max_iv:
raise RegressionTestException(f"Implied volatility {contract.ImpliedVolatility} is not within {self._min_iv} and {self._max_iv}")
def on_end_of_algorithm(self) -> None:
if not self.option_chain_received:
raise RegressionTestException("Option chain was not received.")
@@ -23,10 +23,10 @@ class OptionUniverseFilterGreeksShortcutsRegressionAlgorithm(OptionUniverseFilte
def set_option_filter(self, security: Option) -> None:
# Contracts can be filtered by greeks, implied volatility, open interest:
security.set_filter(lambda u: u
.d(0.5, 1.5)
.g(0.0001, 0.0006)
.v(0.01, 1.5)
.t(-2.0, -0.5)
.r(0.5, 3.0)
.iv(1.0, 3.0)
.oi(100, 500))
.d(self._min_delta, self._max_delta)
.g(self._min_gamma, self._max_gamma)
.v(self._min_vega, self._max_vega)
.t(self._min_theta, self._max_theta)
.r(self._min_rho, self._max_rho)
.iv(self._min_iv, self._max_iv)
.oi(self._min_open_interest, self._max_open_interest))
@@ -27,13 +27,13 @@ class OptionUniverseFilterOptionsDataRegressionAlgorithm(OptionUniverseFilterGre
'''
security.set_filter(lambda u: u
.delta(0.5, 1.5)
.gamma(0.0001, 0.0006)
.vega(0.01, 1.5)
.theta(-2.0, -0.5)
.rho(0.5, 3.0)
.implied_volatility(1.0, 3.0)
.open_interest(100, 500))
.delta(self._min_delta, self._max_delta)
.gamma(self._min_gamma, self._max_gamma)
.vega(self._min_vega, self._max_vega)
.theta(self._min_theta, self._max_theta)
.rho(self._min_rho, self._max_rho)
.implied_volatility(self._min_iv, self._max_iv)
.open_interest(self._min_open_interest, self._max_open_interest))
'''
security.set_filter(
@@ -44,11 +44,11 @@ class OptionUniverseFilterOptionsDataRegressionAlgorithm(OptionUniverseFilterGre
contract for contract in contracts
# Can access the contract data here and do some filtering based on it is needed.
# More complex math can be done here for filtering, but will be simple here for demonstration sake:
if (contract.Greeks.Delta > 0.5 and contract.Greeks.Delta < 1.5 and
contract.Greeks.Gamma > 0.0001 and contract.Greeks.Gamma < 0.0006 and
contract.Greeks.Vega > 0.01 and contract.Greeks.Vega < 1.5 and
contract.Greeks.Theta > -2.0 and contract.Greeks.Theta < -0.5 and
contract.Greeks.Rho > 0.5 and contract.Greeks.Rho < 3.0 and
contract.ImpliedVolatility > 1.0 and contract.ImpliedVolatility < 3.0 and
contract.OpenInterest > 100 and contract.OpenInterest < 500)
if (contract.Greeks.Delta > self._min_delta and contract.Greeks.Delta < self._max_delta and
contract.Greeks.Gamma > self._min_gamma and contract.Greeks.Gamma < self._max_gamma and
contract.Greeks.Vega > self._min_vega and contract.Greeks.Vega < self._max_vega and
contract.Greeks.Theta > self._min_theta and contract.Greeks.Theta < self._max_theta and
contract.Greeks.Rho > self._min_rho and contract.Greeks.Rho < self._max_rho and
contract.ImpliedVolatility > self._min_iv and contract.ImpliedVolatility < self._max_iv and
contract.OpenInterest > self._min_open_interest and contract.OpenInterest < self._max_open_interest)
]))