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: ignoreView on GitHub (pinned to 3e071fcc2c)
Solutions
- Pass the underlying price explicitly: obb.derivatives.options.screen(symbol, option_type='otm', underlying_price=price).
- Fetch the price first: px = obb.equity.price.quote(symbol).results[0].last_price, then pass it as underlying_price.
- Use a provider whose chains include underlying_price, or filter to ITM/OTM manually with strike comparisons against a known price.
- 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
- Always fetch the underlying quote and pass underlying_price when using option_type='otm'/'itm'.
- Check the chains DataFrame for a populated underlying_price column before filtering.
- Fall back to strike_min/strike_max filtering when no price source is available.
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
- Error: Moneyness must be expressed as a percentage between 0
- Error: option_type must be either 'call' or 'put'
- No data to process!
- Error: No {target} field found.
- {symbol} was not found in the Cboe options directory.
AI-assisted analysis of OpenBB-finance/OpenBB@3e071fcc2c (2026-08-14).
Data as JSON: /api/errors/5881db587a2349f4.
Report an issue: GitHub.