OpenBB-finance/OpenBB · error · RuntimeError

Last price must be provided for OTM/ITM options filtering, a

Error message

Last price must be provided for OTM/ITM options filtering, and was not found in the data.

What it means

Raised when filtering option chains by option_type='otm' or 'itm', because classifying strikes as in/out-of-the-money requires the underlying's last price. The code only reaches this raise when `last_price` is None — i.e. no `underlying_price` argument was supplied and the chains data did not carry a usable underlying_price value. It is a RuntimeError (not OpenBBError) from the OTM/ITM branch of the screen/filter router.

Source

Thrown at openbb_platform/extensions/derivatives/openbb_derivatives/options/options_router.py:216

        high = (  # noqa:F841 pylint: disable=unused-variable  # type: ignore
            1 + (moneyness / 100)
        ) * last_price
        low = (  # noqa:F841 pylint: disable=unused-variable  # type: ignore
            1 - (moneyness / 100)
        ) * last_price
        calls = calls.query("@low <= `strike` <= @high")  # type: ignore
        puts = puts.query("@low <= `strike` <= @high")  # type: ignore

    if strike_min is not None:
        calls = calls.query("strike >= @strike_min")  # type: ignore
        puts = puts.query("strike >= @strike_min")  # type: ignore

    if strike_max is not None:
        calls = calls.query("strike <= @strike_max")  # type: ignore
        puts = puts.query("strike <= @strike_max")  # type: ignore

    if option_type in ["otm", "itm"] and last_price is None:
        raise RuntimeError(
            "Last price must be provided for OTM/ITM options filtering, and was not found in the data."
        )

    if option_type is not None and option_type == "otm":
        otm_calls = calls.query("strike > @last_price").set_index(["expiration", "strike", "option_type"])  # type: ignore
        otm_puts = puts.query("strike < @last_price").set_index(["expiration", "strike", "option_type"])  # type: ignore
        df = concat([otm_calls, otm_puts]).sort_index().reset_index()
    elif option_type is not None and option_type == "itm":
        itm_calls = calls.query("strike < @last_price").set_index(["expiration", "strike", "option_type"])  # type: ignore
        itm_puts = puts.query("strike > @last_price").set_index(["expiration", "strike", "option_type"])  # type: ignore
        df = concat([itm_calls, itm_puts]).sort_index().reset_index()
    elif option_type is not None and option_type == "calls":
        df = calls
    elif option_type is not None and option_type == "puts":
        df = puts

    df = DataFrame(
        df[  # type: ignore

View on GitHub (pinned to 3e071fcc2c)

Solutions

  1. Pass the underlying price explicitly: obb.derivatives.options.screen(symbol, option_type='otm', underlying_price=price).
  2. Fetch the price first: px = obb.equity.price.quote(symbol).results[0].last_price, then pass it as underlying_price.
  3. Use a provider whose chains include underlying_price, or filter to ITM/OTM manually with strike comparisons against a known price.
  4. Avoid option_type='otm'/'itm' and use 'calls'/'puts' plus strike_min/strike_max when no price is available.

Example fix

# before
res = obb.derivatives.options.screen('AAPL', option_type='otm', dte=30)  # no price available

# after
last = obb.equity.price.quote('AAPL').results[0].last_price
res = obb.derivatives.options.screen('AAPL', option_type='otm', dte=30, underlying_price=last)
Defensive patterns

Strategy: validation

Validate before calling

from openbb import obb
chains = obb.derivatives.options.chains('AAPL').to_df()
last = None
if 'underlying_price' in chains.columns and chains['underlying_price'].notna().any():
    last = chains['underlying_price'].dropna().iloc[0]
if last is None:
    last = obb.equity.price.quote('AAPL').results[0].last_price

Type guard

def has_underlying_price(df) -> bool:
    """True if the chains frame carries at least one non-null underlying_price."""
    return 'underlying_price' in getattr(df, 'columns', []) and bool(df['underlying_price'].notna().any())

Try / catch

try:
    res = obb.derivatives.options.screen('AAPL', option_type='otm', underlying_price=last)
except RuntimeError as e:
    if 'Last price must be provided' in str(e):
        last = obb.equity.price.quote('AAPL').results[0].last_price
        res = obb.derivatives.options.screen('AAPL', option_type='otm', underlying_price=last)
    else:
        raise

Prevention

When it happens

Trigger: Calling obb.derivatives.options.screen(..., option_type='otm') with a provider or dataset that omits underlying_price; passing underlying_price=None explicitly; calling the internal filter function on a chains DataFrame whose underlying_price column is absent or all-NaN.

Common situations: Providers that return chains without an underlying price field (some free sources), filtering a locally cached/stale chains DataFrame after the price column was dropped, or chaining the output of a custom fetcher into the screener.

Related errors


AI-assisted analysis of OpenBB-finance/OpenBB@3e071fcc2c (2026-08-14). Data as JSON: /api/errors/5881db587a2349f4. Report an issue: GitHub.