{"record":{"id":"5881db587a2349f4","repo":"OpenBB-finance/OpenBB","slug":"last-price-must-be-provided-for-otm-itm-options-fi","errorCode":null,"errorMessage":"Last price must be provided for OTM/ITM options filtering, and was not found in the data.","messagePattern":"Last price must be provided for OTM/ITM options filtering, and was not found in the data\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"openbb_platform/extensions/derivatives/openbb_derivatives/options/options_router.py","lineNumber":216,"sourceCode":"        high = (  # noqa:F841 pylint: disable=unused-variable  # type: ignore\n            1 + (moneyness / 100)\n        ) * last_price\n        low = (  # noqa:F841 pylint: disable=unused-variable  # type: ignore\n            1 - (moneyness / 100)\n        ) * last_price\n        calls = calls.query(\"@low <= `strike` <= @high\")  # type: ignore\n        puts = puts.query(\"@low <= `strike` <= @high\")  # type: ignore\n\n    if strike_min is not None:\n        calls = calls.query(\"strike >= @strike_min\")  # type: ignore\n        puts = puts.query(\"strike >= @strike_min\")  # type: ignore\n\n    if strike_max is not None:\n        calls = calls.query(\"strike <= @strike_max\")  # type: ignore\n        puts = puts.query(\"strike <= @strike_max\")  # type: ignore\n\n    if option_type in [\"otm\", \"itm\"] and last_price is None:\n        raise RuntimeError(\n            \"Last price must be provided for OTM/ITM options filtering, and was not found in the data.\"\n        )\n\n    if option_type is not None and option_type == \"otm\":\n        otm_calls = calls.query(\"strike > @last_price\").set_index([\"expiration\", \"strike\", \"option_type\"])  # type: ignore\n        otm_puts = puts.query(\"strike < @last_price\").set_index([\"expiration\", \"strike\", \"option_type\"])  # type: ignore\n        df = concat([otm_calls, otm_puts]).sort_index().reset_index()\n    elif option_type is not None and option_type == \"itm\":\n        itm_calls = calls.query(\"strike < @last_price\").set_index([\"expiration\", \"strike\", \"option_type\"])  # type: ignore\n        itm_puts = puts.query(\"strike > @last_price\").set_index([\"expiration\", \"strike\", \"option_type\"])  # type: ignore\n        df = concat([itm_calls, itm_puts]).sort_index().reset_index()\n    elif option_type is not None and option_type == \"calls\":\n        df = calls\n    elif option_type is not None and option_type == \"puts\":\n        df = puts\n\n    df = DataFrame(\n        df[  # type: ignore","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/OpenBB-finance/OpenBB/blob/3e071fcc2cd9f891cac6040ae60296dba76dab46/openbb_platform/extensions/derivatives/openbb_derivatives/options/options_router.py#L198-L234","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nres = obb.derivatives.options.screen('AAPL', option_type='otm', dte=30)  # no price available\n\n# after\nlast = obb.equity.price.quote('AAPL').results[0].last_price\nres = obb.derivatives.options.screen('AAPL', option_type='otm', dte=30, underlying_price=last)","handlingStrategy":"validation","validationCode":"from openbb import obb\nchains = obb.derivatives.options.chains('AAPL').to_df()\nlast = None\nif 'underlying_price' in chains.columns and chains['underlying_price'].notna().any():\n    last = chains['underlying_price'].dropna().iloc[0]\nif last is None:\n    last = obb.equity.price.quote('AAPL').results[0].last_price","typeGuard":"def has_underlying_price(df) -> bool:\n    \"\"\"True if the chains frame carries at least one non-null underlying_price.\"\"\"\n    return 'underlying_price' in getattr(df, 'columns', []) and bool(df['underlying_price'].notna().any())","tryCatchPattern":"try:\n    res = obb.derivatives.options.screen('AAPL', option_type='otm', underlying_price=last)\nexcept RuntimeError as e:\n    if 'Last price must be provided' in str(e):\n        last = obb.equity.price.quote('AAPL').results[0].last_price\n        res = obb.derivatives.options.screen('AAPL', option_type='otm', underlying_price=last)\n    else:\n        raise","preventionTips":["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."],"tags":["options","missing-price","otm-itm","validation"],"backgroundTag":null,"analyzedSha":"3e071fcc2cd9f891cac6040ae60296dba76dab46","analyzedAt":"2026-08-14T23:40:48.960Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}