Risk-Neutral Density
Extract the market-implied risk-neutral probability density of the underlying’s price at expiration, via the Breeden-Litzenberger (1978) theorem, applied to a volatility smile calibrated to real market option prices (see get_implied_volatility) rather than a single flat assumed volatility.
get_partial_derivative computes the same second-derivative relationship but with one flat volatility value applied at every strike – with a flat volatility input, the second derivative can only ever recover a lognormal density regardless of what the real market smile looks like, which defeats the entire purpose of the theorem. This method instead first calibrates a raw SVI (Gatheral 2004) curve to the actual market smile (see get_volatility_surface) and evaluates the density from that.
The formula is as follows:
\[f(K) = e ^{r \cdot t} \cdot d ^{2} C(K) / \text{dK} ^{2}\]Where C(K) is the Black-Scholes call price at strike K, using the SVI-smoothed implied volatility at that strike, r is the risk-free rate and t is the time to expiration. The second derivative is approximated numerically via the three-point central difference (C(K - h) - 2 * C(K) + C(K + h)) / h^2 on a fine, evenly-spaced strike grid with spacing h, since the smile only gives implied volatility at a sparse set of traded strikes. The first and last grid points have no neighbor on one side and are left out.
The option chains and the days to expiration are live, while the stock price is the close of the last historical date; a warning is logged when that date lies more than five days before today.
See the paper: Breeden, D.T., & Litzenberger, R.H. (1978), “Prices of State-Contingent Claims Implicit in Option Prices”, Journal of Business, 51(4), 621-651. https://www.jstor.org/stable/2352653
Also known as: Breeden-Litzenberger, implied risk-neutral distribution.
Notes: A warning is logged (not raised) for any ticker whose density goes negative at some strike – this indicates a butterfly-arbitrage violation (the call price is not convex in the strike) in the underlying market quotes or the SVI fit, which a well-calibrated, liquid smile should not produce.
Risk-Neutral Density in Python or via the MCP
Install the Finance Toolkit with:
pip install financetoolkit -U
Then call get_risk_neutral_density as shown below.
from financetoolkit import Toolkit
toolkit = Toolkit(["SPY"], api_key="FINANCIAL_MODELING_PREP_KEY", benchmark_ticker=None)
toolkit.options.get_risk_neutral_density(strike_price_range=0.1, number_of_strikes=81)
Or, after setting up the Finance Toolkit MCP, ask:
What probability distribution for the price of SPY at its nearest option expiration do current call prices imply, on 81 strikes within 10% of the price?
Which returns:
| Strike Price | SPY |
|---|---|
| 686.021 | 0 |
| 687.921 | 0 |
| 689.821 | 0 |
| 691.722 | 0 |
| 693.622 | 0.0001 |
| 695.522 | 0.0001 |
| 697.423 | 0.0001 |
| 699.323 | 0.0001 |
| 701.223 | 0.0001 |
| 703.124 | 0.0001 |
| 705.024 | 0.0001 |
| 706.924 | 0.0001 |
| 708.825 | 0.0001 |
| 710.725 | 0.0001 |
| 712.625 | 0.0002 |
| 714.526 | 0.0002 |
| 716.426 | 0.0002 |
| 718.326 | 0.0002 |
| 720.227 | 0.0003 |
| 722.127 | 0.0003 |
| 724.028 | 0.0004 |
| 725.928 | 0.0005 |
| 727.828 | 0.0006 |
| 729.729 | 0.0007 |
| 731.629 | 0.0009 |
| 733.529 | 0.0011 |
| 735.43 | 0.0015 |
| 737.33 | 0.0019 |
| 739.23 | 0.0024 |
| 741.131 | 0.0032 |
| 743.031 | 0.0043 |
| 744.931 | 0.0058 |
| 746.832 | 0.008 |
| 748.732 | 0.011 |
| 750.632 | 0.0151 |
| 752.533 | 0.0209 |
| 754.433 | 0.0284 |
| 756.333 | 0.0377 |
| 758.234 | 0.048 |
| 760.134 | 0.0573 |
| 762.034 | 0.0626 |
| 763.935 | 0.0612 |
| 765.835 | 0.0525 |
| 767.735 | 0.0392 |
| 769.636 | 0.0256 |
| 771.536 | 0.015 |
| 773.436 | 0.0081 |
| 775.337 | 0.0042 |
| 777.237 | 0.0022 |
| 779.137 | 0.0011 |
| 781.038 | 0.0006 |
| 782.938 | 0.0003 |
| 784.838 | 0.0002 |
| 786.739 | 0.0001 |
| 788.639 | 0.0001 |
| 790.539 | 0.0001 |
| 792.44 | 0 |
| 794.34 | 0 |
| 796.24 | 0 |
| 798.141 | 0 |
| 800.041 | 0 |
| 801.941 | 0 |
| 803.842 | 0 |
| 805.742 | 0 |
| 807.642 | 0 |
| 809.543 | 0 |
| 811.443 | 0 |
| 813.343 | 0 |
| 815.244 | 0 |
| 817.144 | 0 |
| 819.044 | 0 |
| 820.945 | 0 |
| 822.845 | 0 |
| 824.745 | 0 |
| 826.646 | 0 |
| 828.546 | 0 |
| 830.446 | 0 |
| 832.347 | 0 |
| 834.247 | 0 |
Parameters
get_risk_neutral_density accepts the following parameters.
| Parameter | Type | Description | Example |
|---|---|---|---|
expiration_date | str | None | The expiration date to use. Defaults to None which means it will use the first available expiration date. | "2028-01-21" |
put_option | bool | Whether to use put options instead of call options. Defaults to False. | True |
risk_free_rate | float | The risk free rate to use for the calculation. Defaults to None, which uses the Toolkit’s risk-free rate on the pricing date: the 10-year Treasury yield unless the Toolkit’s risk_free_rate parameter selects ‘13w’, ‘5y’ or ‘30y’, converted from its semi-annual bond-equivalent quote to continuous compounding. A rate passed here is used as given, as a continuously compounded rate. | 0.04 |
dividend_yield | float | The dividend yield to use for the calculation. Defaults to None, which uses the dividend yield on the pricing date (the dividends of the 365 days up to it relative to its close, as the continuously compounded ln(1 + q)). A given value is used as a continuous yield. | 0.02 |
strike_price_range | float | The range of strikes to evaluate the density over, as a fraction of the forward price in each direction. Defaults to 0.5, i.e. from 50% to 150% of the forward price. | 0.3 |
number_of_strikes | int | The number of strikes in the evaluation grid. Defaults to 200. | 100 |
outlier_threshold | float | The number of median absolute deviations from the median implied volatility beyond which a quote is treated as an outlier and dropped before fitting. Defaults to 5.0. | 3.0 |
rounding | int | None | The number of decimals to round the results to. Defaults to None. | 2 |