| Overall Statistics |
|
Total Orders 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Start Equity 100000 End Equity 100000 Net Profit 0% Sharpe Ratio 0 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -1.081 Tracking Error 0.107 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset Portfolio Turnover 0% |
# region imports
from AlgorithmImports import *
# endregion
from AlgorithmImports import *
class ChainedUniverseAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2023, 2, 2)
# Need to set data normalization mode to raw for options to compare the strike price fairly.
self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
self._contracts = {}
# Filter for equities with fundamental data first.
universe = self.add_universe(self._fundamental_function)
# Based on the filtered equities, request an option universe with them as underlying.
self.add_universe_options(universe, self._option_filter_function)
interval = timedelta(minutes=30)
self.universe_settings.minimum_time_in_universe = interval
symbol = Symbol.create("SPY", SecurityType.EQUITY, Market.USA)
self.schedule.on(
self.date_rules.every_day(symbol),
self.time_rules.every(interval),
self._select_option_contracts)
def _select_option_contracts(self):
def otm_filter(symbol, underlying_price):
sid = symbol.id
if sid.option_right == OptionRight.CALL and sid.strike_price > underlying_price:
return True
if sid.option_right == OptionRight.PUT and sid.strike_price < underlying_price:
return True
return False
for underlying_symbol, contracts in self._contracts.items():
price = self.securities[underlying_symbol].price
to_add = [x for x in contracts if otm_filter(x, price)]
for symbol in to_add:
pass
#self.add_option_contract(symbol)
def _fundamental_function(self, fundamental: List[Fundamental]) -> List[Symbol]:
# Filter for equities with the lowest PE Ratio first.
self._contracts.clear()
filtered = [f for f in fundamental if not np.isnan(f.valuation_ratios.pe_ratio)]
sorted_by_pe_ratio = sorted(filtered, key=lambda f: f.valuation_ratios.pe_ratio)
return [Symbol.create("SPY", SecurityType.EQUITY, Market.USA)] #[f.symbol for f in sorted_by_pe_ratio[:10]]
def _option_filter_function(self, option_filter_universe: OptionFilterUniverse) -> OptionFilterUniverse:
dte_min = 28
dte_max = 91
# We can filter by expiration here, since it doesn't change in a day
option_filter_universe = option_filter_universe.include_weeklys().expiration(dte_min, dte_max)
# Seed the underlying with previous day data
u = option_filter_universe.underlying
security = self.securities[u.symbol]
if not security.has_data:
security.set_market_price(TradeBar(self.time, u.symbol, u.open, u.high, u.low, u.close, u.volume))
# Use underlying symbol as dict key
self._contracts[security.symbol] = [x.symbol for x in option_filter_universe]
# Return empty filter
return option_filter_universe.contracts([])
def on_data(self, data: Slice) -> None:
# Iterate each option chain to assert the contracts being selected.
for symbol, chain in data.option_chains.items():
for contract in chain:
self.debug(f"Found {contract.symbol} option contract for {symbol}")