Probability of profit of an options strategy from the Black-Scholes model
EDIT: The library has undergone significant changes, rendering the code from the previous version of this article incompatible. As a result, this article has been updated to reflect the latest version of OptionLab.
Very important in the evaluation of an options strategy, but often neglected or poorly determined, the Probability of Profit (PoP) is the probability that, on a specific target date defined by the options trader, typically the option’s maturity, the price of the underlying asset will be within a range for which the payoff of the strategy is greater than zero. Let’s call this range the Profit Range (PR).
This is because determining the PoP is not exactly a trivial task, as it relies on our knowledge of the actual distribution of returns of the underlying asset on a future date. There are several approaches to achieve this goal, some of which are more sophisticated, such as Machine Learning and Big Data models developed by the army of PhDs in Math, Physics and Engineering hired by hedge funds and proprietary trading firms. Other, more, I would say, accessible approaches are based on traditional and well-established financial models that, despite many approximations and assumptions, allow a crude but still useful estimation of the PoP.
In this short article, I will show how to calculate the PoP of an options strategy using the Black-Scholes model provided that you know the stock price’s PR.
You probably already know that the Black-Scholes model is used to price options. If you are not familiar with it yet but are interested, you can have a first contact with the concepts and mathematics of the model here. It is not my intention to delve too much into the details of the Black-Scholes model, but merely to present the minimum necessary for the reader to understand how to use it to estimate the PoP of an options strategy.
To precify a call option within the Black-Scholes model, we use the following equation:
where C
is the option premium, S
is the spot price of the underlying asset (typically, a stock), X
is the option strike, T
is the time remaining to option expiration in years, r
is the annualized risk-free interest rate, and N()
is the Cumulative Distribution Function (CDF) assuming a normal distribution, which is one of the strong assumptions of the Black-Scholes model.
In the equation above, we are especially interested in the CDF of d2
appearing in the second term. This d2
is given by:
An additional input variable appears in this equation, σ, which represents the annualized volatility of the underlying asset. By the way, both r
and σ are constants within the Black-Scholes model, which are also strong assumptions of the model.
Ok, but where does our interest in the CDF of d2
come from? Or, to put it better, what does the CDF of d2
mean within the Black-Scholes model? In short, the CDF of d2
allows us to estimate the probability that the price of the underlying asset, which is currently S
, will be greater than or equal to the call option’s strike X
at maturity. In other words, it is the probability that the option will be exercised.
For the calculation of the PoP, we can generalize the meaning of CDF of d2
as the probability that the stock price will be equal to or greater than a certain value on the target date, which usually corresponds to the option’s expiration date but can be any date before it.
Therefore, if we know that the PR lies between S1
and S2
, with S1<S2
, the probability of profit P
of this strategy at expiration could be estimated by:
That is, the PoP of this options strategy is the difference in the CDF of Black-Scholes’ d2
for S1
and S2
.
We can calculate the PoP using d2
by using the get_d1_d2()
function from the black_scholes
module implemented in OptionLab. Assuming OptionLab has been installed using pip install optionlab
, the Python code snippet below shows how to obtain the PoP of an options strategy that is profitable in the PR from $100 to $110, with the underlying asset’s current price at $100, risk-free interest rate at 1%, volatility at 20%, and time remaining until option expiration being 60 days.
# Import get_d1_d2() and scipy's statistical module
from optionlab.black_scholes import get_d1_d2
from scipy import stats
# Get d2 for both the lower and upper bounds of the profit range
d2_lower = get_d1_d2(100, 100, 0.01, 0.2, 60 / 365)[1]
d2_upper = get_d1_d2(100, 110, 0.01, 0.2, 60 / 365)[1]
# Calculate the PoP from the difference in the CDFs of d2 for the lower
# and upper bounds of the profit range
pop = stats.norm.cdf(d2_lower) - stats.norm.cdf(d2_upper)
# Print the result
print(f"Probability of Profit (PoP) is {pop:.2%}.")
The final output is “Probability of Profit (PoP) is 37.60%”, which is self-explanatory, on your screen.
The function get_d1_d2()
follows:
def get_d1_d2(
s0: np.ndarray | float,
x: np.ndarray | float,
r: float,
vol: float | np.ndarray,
years_to_maturity: float,
y: float = 0.0,
) -> tuple[float, float]:
d1 = (log(s0 / x) + (r - y + vol * vol / 2.0) * years_to_maturity) / (
vol * sqrt(years_to_maturity)
)
d2 = d1 - vol * sqrt(years_to_maturity)
return d1, d2
where s0
is the current stock price, x
is the strike of the option, r
is the annualized risk-free interest rate, vol
is the annualized volatility, years_to_maturity
is the time remaining to the option expiration in years and y
is the dividend yield of the stock. This function can be easily translated into any other programming language of your choice, in case it is not Python. Moreover, though I am not an Excel fan, I believe this type of calculation can also be done in an Excel spreadsheet, if you have no programming skills at all.
As one can see, it is reasonably simple to estimate the PoP of an options strategy using the Black-Scholes model. But OptionLab makes it even simpler.
In OptionLab, there is a second module called support
, where I implemented a function called get_pop()
. This function is used by the engine
module to calculate the PoP of an options strategy, such as the covered call in my first article here on Medium.
An example of how to use get_pop()
can be seen in the following code snippet:
# Import get_pop() and the appropriate Pydantic model
from optionlab.support import get_pop
from optionlab.models import ProbabilityOfProfitInputs
# Define the input data for get_pop()
pop_inputs = ProbabilityOfProfitInputs(
source="black-scholes",
stock_price=100,
volatility=0.2,
interest_rate=0.01,
years_to_maturity=60 / 365,
)
# Compute the PoP using the same input data as in the previous example
pop = get_pop([[100, 110]], pop_inputs)
# Print the result
print(f"Probability of Profit (PoP) is {pop:.2%}.")
Just like in the first example, this code will end up printing “Probability of Profit (PoP) is 37.60%”.
Put differently, get_pop()
, when taking “black-scholes” as the source, returns the PoP as the difference in the CDF of Black-Scholes’ d2
for the lower and upper bounds of the PR.
OptionLab is provided as is, with no guarantee that its results are accurate. As such, I am not responsible for any losses caused by the use of the code. Bugs can be reported as issues on the project’s GitHub page.
Options are very risky derivatives and, like any other type of financial vehicle, trading options requires due diligence. I am not a financial advisor and the content of this article is not a recommendation.