Skip to content

Prompt One Shot Failure: Chain Universe with Option Incorrect Method Call #2350

@AlexCatarino

Description

@AlexCatarino

Prompt

Build an algorithm that picks the 10 stocks with the lowest valid PE ratio, then adds front-month at-the-money call options on them. Drop NaN PE ratios in the fundamental filter, sort ascending, and take 10. Chain an option universe on top of the equity universe and restrict it to front-month, calls-only, with strikes within roughly two steps of the ATM strike. Use asynchronous selection and raw data normalization, which is required to compare option strikes fairly. In data handling, iterate each option chain and read each contract's last price. Set $100,000 cash. Backtest from September 1, 2024 to December 31, 2024.

Error

The backtest ended with a runtime error.

Runtime Error: 'OfTypeIterator[FineFundamental]' object has no attribute 'front_month'
at _option_filter
.front_month()
^^^^^^^^^^^
in main.py: line 33

'OfTypeIterator[FineFundamental]' object has no attribute 'front_month'
at _option_filter
.front_month()
^^^^^^^^^^^
in main.py: line 33

Code

main.py

# region imports
from AlgorithmImports import *
# endregion

class LowPECallOptionsAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 12, 31)
        self.set_cash(100_000)

        self.settings.seed_initial_prices = True

        # Add equity universe with option chain
        self._universe = self.add_universe(
            self._select_fundamentals,
            self._option_filter
        )
        self.universe_settings.asynchronous = True
        self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW

    def _select_fundamentals(self, fundamentals: List[Fundamental]) -> List[Symbol]:
        # Drop NaN PE ratios, sort ascending, take 10
        filtered = [f for f in fundamentals if f.valuation_ratios.pe_ratio
                    and not float('nan') == f.valuation_ratios.pe_ratio
                    and f.valuation_ratios.pe_ratio > 0]
        sorted_by_pe = sorted(filtered, key=lambda f: f.valuation_ratios.pe_ratio)
        return [f.symbol for f in sorted_by_pe[:10]]

    def _option_filter(self, universe: OptionFilterUniverse) -> OptionFilterUniverse:
        # Front-month calls only, strikes within ~2 steps of ATM
        return (universe
                .front_month()
                .calls_only()
                .strikes(-2, 2))

    def on_data(self, data: Slice) -> None:
        pass

    def on_securities_changed(self, changes: SecurityChanges) -> None:
        # Log added/removed securities
        for security in changes.added_securities:
            self.log(f"Added: {security.symbol}")
        for security in changes.removed_securities:
            self.log(f"Removed: {security.symbol}")

    def on_option_chain(self, option_chain: OptionChain) -> None:
        # Iterate option chain contracts and read last price
        for contract in option_chain:
            last_price = contract.last_price
            self.log(
                f"Contract: {contract.symbol} | "
                f"Strike: {contract.strike} | "
                f"Expiry: {contract.expiry.date()} | "
                f"Last: {last_price}"
            )

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions