class ImpliedRateModel(BaseModel):
"""
Calculates the risk-free rate implied by put-call parity for European options.
This model solves for the risk-free rate `r` that satisfies the equation:
C - P = S*exp(-qT) - K*exp(-rT)
"""
name: str = "Implied Rate"
has_closed_form: bool = True
# Define inputs for the closed-form solver.
cf_kwargs = ("call_price", "put_price")
def _validate_params(self) -> None:
"""This model has no intrinsic parameters to validate."""
pass
def _closed_form_impl(
self,
*,
call_price: float,
put_price: float,
spot: float,
strike: float,
t: float,
q: float = 0.0,
**_: Any,
) -> float:
"""
Solves for the implied risk-free rate using a root-finding algorithm.
Parameters
----------
call_price : float
The market price of the call option.
put_price : float
The market price of the put option.
spot : float
The current price of the underlying asset.
strike : float
The strike price of the options.
t : float
The time to maturity of the options.
q : float, optional
The continuously compounded dividend yield, by default 0.0.
Returns
-------
float
The implied continuously compounded risk-free rate.
Raises
------
ValueError
If a root cannot be bracketed within a reasonable range.
"""
discounted_spot = spot * math.exp(-q * t)
price_difference = call_price - put_price
def objective_func(
r: float,
) -> float:
"""The put-call parity equation rearranged to equal zero."""
return discounted_spot - strike * math.exp(-r * t) - price_difference
# Attempt to find a bracket for the root
low, high = -0.5, 0.5 # reasonable range for interest rates
f_low, f_high = objective_func(low), objective_func(high)
for _ in range(10): # Try up to 10 times to expand the bracket
if f_low * f_high < 0:
break
low -= 0.5
high += 0.5
f_low, f_high = objective_func(low), objective_func(high)
else:
raise ValueError(
"Unable to bracket a root for implied rate. Check input for arbitrage."
)
return brentq(objective_func, low, high, xtol=1e-9, maxiter=100)
# Abstract Method Implementations
def _cf_impl(
self,
*args: Any,
**kwargs: Any,
) -> Any:
raise NotImplementedError
def _sde_impl(self) -> Any:
raise NotImplementedError
def _pde_impl(self) -> Any:
raise NotImplementedError