From 933370b41bccce301167b11821a02a12c83836de Mon Sep 17 00:00:00 2001 From: jeroen Date: Mon, 5 Feb 2024 15:03:31 +0100 Subject: [PATCH 1/6] Add in Implied Volatility Calculation --- README.md | 6 + financetoolkit/options/options_controller.py | 220 +++++++++++++++++-- financetoolkit/options/options_model.py | 9 +- 3 files changed, 218 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6b31959..d892c4e 100644 --- a/README.md +++ b/README.md @@ -1923,6 +1923,12 @@ The Black Scholes Model is based on several assumptions, including the following Find the documentation [here](https://www.jeroenbouma.com/projects/financetoolkit/docs/options#get_black_scholes_model). +> **Implied Volatility** + +The Implied Volatility (IV) is based on the Black Scholes Model and the actual option prices for any of the available expiration dates. Implied Volatility (IV) is a measure of how much the market expects the price of the underlying asset to fluctuate in the future. It is a key component of options pricing and can also be used to calculate the theoretical value of an option. It makes it possible to plot the Volatility Smile for each company and each expiration date as seen below. Find the documentation [here](https://www.jeroenbouma.com/projects/financetoolkit/docs/options#get_implied_volatility). + +Volatility Smile + > **Binomial Option Pricing Model** The Binomial Option Pricing Model is a mathematical model used to estimate the price of European and American style options. It does so by creating a binomial tree of price paths for the underlying asset, and then working backwards through the tree to determine the price of the option at each node. diff --git a/financetoolkit/options/options_controller.py b/financetoolkit/options/options_controller.py index 1c8bf49..d3fc188 100644 --- a/financetoolkit/options/options_controller.py +++ b/financetoolkit/options/options_controller.py @@ -1,8 +1,11 @@ """Options Module""" __docformat__ = "google" +from datetime import datetime + import numpy as np import pandas as pd +from scipy.optimize import minimize from financetoolkit.options import ( binomial_trees_model, @@ -13,7 +16,7 @@ ) from financetoolkit.ratios import valuation_model -# pylint: disable=too-many-instance-attributes,too-few-public-methods,too-many-lines,too-many-locals +# pylint: disable=too-many-instance-attributes,too-few-public-methods,too-many-lines,too-many-locals,cell-var-from-loop # pylint: disable=line-too-long,too-many-public-methods # ruff: noqa: E501 @@ -120,6 +123,8 @@ def __init__( def get_option_chains( self, expiration_date: str | None = None, + put_option: bool = False, + show_expiration_dates: bool = False, rounding: int | None = None, ): """ @@ -175,11 +180,15 @@ def get_option_chains( """ expiry_dates = options_model.get_option_expiry_dates(ticker=self._tickers[0]) + if show_expiration_dates: + return list(expiry_dates.index) + if expiry_dates.empty: raise ValueError( "The expiration dates are not available. This is usually because no valid tickers were provided." ) if expiration_date is None: + expiration_date = expiry_dates.index[0] expiration_date_value = expiry_dates.loc[expiry_dates.index[0]].iloc[0] elif expiration_date not in expiry_dates.index: raise ValueError( @@ -189,7 +198,9 @@ def get_option_chains( expiration_date_value = expiry_dates.loc[expiration_date].iloc[0] option_chains = options_model.get_option_chains( - tickers=self._tickers, expiration_date=expiration_date_value + tickers=self._tickers, + expiration_date=expiration_date_value, + put_option=put_option, ) option_chains["Change"] = option_chains["Change"].round( @@ -202,10 +213,7 @@ def get_option_chains( rounding if rounding else self._rounding ) - tickers = option_chains.index.get_level_values(0).unique() - - if len(tickers) == 1: - option_chains = option_chains.loc[tickers[0]] + option_chains.name = expiration_date return option_chains @@ -235,10 +243,10 @@ def get_black_scholes_model( The Black Scholes Model is based on several assumptions, including the following: - - The option is European and can only be exercised at expiration. - - The underlying stock follows a lognormal distribution. - - The risk—free rate and volatility of the underlying stock are known and constant. - - The returns on the underlying stock are normally distributed. + - The option is European and can only be exercised at expiration. + - The underlying stock follows a lognormal distribution. + - The risk—free rate and volatility of the underlying stock are known and constant. + - The returns on the underlying stock are normally distributed. By default the most recent risk free rate, dividend yield and stock price is used, you can alter this by changing the start date. The volatility is calculated based on the daily returns of the stock price and the selected @@ -246,10 +254,10 @@ def get_black_scholes_model( The formulas are as follows: - - d1 = (ln(S / K) + (r — q + (σ^2) / 2) * t) / (σ * sqrt(t)) - - d2 = d1 — σ * sqrt(t) - - Call Option Price = S * e^(—q * t) * N(d1) — K * e^(—r * t) * N(d2) - - Put Option Price = K * e^(—r * t) * N(—d2) — S * e^(—q * t) * N(—d1) + - d1 = (ln(S / K) + (r — q + (σ^2) / 2) * t) / (σ * sqrt(t)) + - d2 = d1 — σ * sqrt(t) + - Call Option Price = S * e^(—q * t) * N(d1) — K * e^(—r * t) * N(d2) + - Put Option Price = K * e^(—r * t) * N(—d2) — S * e^(—q * t) * N(—d1) Where S is the stock price, K is the strike price, r is the risk free rate, q is the dividend yield, σ is the volatility, t is the time to expiration, N(d1) is the cumulative normal distribution of d1 and N(d2) is the @@ -379,6 +387,188 @@ def get_black_scholes_model( return black_scholes_df + def get_implied_volatility( + self, + expiration_date: str | None = None, + put_option: bool = False, + risk_free_rate: float | None = None, + dividend_yield: float | None = None, + show_expiration_dates: bool = False, + show_input_info: bool = False, + rounding: int | None = None, + ): + """ + Calculate the Implied Volatility (IV) based on the Black Scholes Model and the actual option prices for + any of the available expiration dates. + + Implied Volatility (IV) is a measure of how much the market expects the price of the underlying asset to + fluctuate in the future. It is a key component of options pricing and can also be used to calculate the + theoretical value of an option. + + By default the most recent risk free rate, dividend yield and stock price is used, you can alter this by changing + the start date. The volatility is calculated based on the daily returns of the stock price and the selected + period (this can be altered by defining this accordingly when defining the Toolkit class, start_date and end_date). + + The formulas are as follows: + + - d1 = (ln(S / K) + (r — q + (σ^2) / 2) * t) / (σ * sqrt(t)) + - d2 = d1 — σ * sqrt(t) + - Call Option Price = S * e^(—q * t) * N(d1) — K * e^(—r * t) * N(d2) + - Put Option Price = K * e^(—r * t) * N(—d2) — S * e^(—q * t) * N(—d1) + + Where S is the stock price, K is the strike price, r is the risk free rate, q is the dividend yield, σ is the + volatility, t is the time to expiration, N(d1) is the cumulative normal distribution of d1 and N(d2) is the + the cumulative normal distribution of d2. + + In which the Implied Volatility is then calculated as follows: + + - Implied Volatility = MINIMIZE(Black Scholes Theoretical Price — Actual Option Price) + + To determine the Implied Volatility, the Black Scholes Model is used to calculate the theoretical option price in + which sigma (σ) is the only unknown variable. The actual option price is then used to determine the implied + volatility by minimizing the difference between the theoretical and actual option price. + + Args: + expiration_date (str | None, optional): The expiration date to use for the calculation. Defaults to None + which means it will use the most recent expiration date. + put_option (bool, optional): Whether to calculate the put option price. Defaults to False which means + it will calculate the call option price. + risk_free_rate (float, optional): The risk free rate to use for the calculation. Defaults to None which + means it will use the current risk free rate. + dividend_yield (float, optional): The dividend yield to use for the calculation. Defaults to None which + means it will use the dividend yield as obtained through annual historical data. + show_expiration_dates (bool, optional): Whether to show the expiration dates. Defaults to False. + show_input_info (bool, optional): Whether to show the input information. Defaults to False. + rounding (int | None, optional): The number of decimals to round the results to. Defaults to 4. + + Returns: + pd.Series | list[str]: Implied Volatility values containing the tickers as the index and the expiration + dates as the columns. If show_expiration_dates is True, it will return a list of expiration dates. + + As an example: + + ```python + from financetoolkit import Toolkit + + toolkit = Toolkit(["MSFT", "AAPL"], api_key="FINANCIAL_MODELING_PREP_KEY") + + implied_volatility = toolkit.options.get_implied_volatility() + + implied_volatility.loc['AAPL'] + ``` + + Which returns: + + | | 2024-02-09 | + |------:|-------------:| + | 162.5 | 0.2828 | + | 165 | 1.0238 | + | 167.5 | 0.7867 | + | 170 | 0.6984 | + | 172.5 | 0.6796 | + | 175 | 0.4611 | + | 177.5 | 0.4423 | + | 180 | 0.4154 | + | 182.5 | 0.3541 | + | 185 | 0.3506 | + | 187.5 | 0.3331 | + | 190 | 0.3329 | + | 192.5 | 0.3411 | + | 195 | 0.361 | + | 197.5 | 0.3833 | + | 200 | 0.4033 | + | 202.5 | 0.4477 | + | 205 | 0.4452 | + | 207.5 | 0.518 | + """ + option_chains = self.get_option_chains( + expiration_date=expiration_date if expiration_date is not None else None, + show_expiration_dates=show_expiration_dates, + put_option=put_option, + ) + + if show_expiration_dates: + # While the name implies option chains, it actually returns the expiration dates + return option_chains + + current_period = self._daily_historical.index[-1] + stock_price = self._prices.loc[current_period] + volatility = self._volatility.loc[current_period] + + risk_free_rate = ( + risk_free_rate + if risk_free_rate is not None + else self._risk_free_rate.loc[current_period] + ) + + tickers = option_chains.index.get_level_values(0).unique() + today = datetime.today() + dividend_yield_value: dict[str, float] = {} + implied_volatility: dict[str, dict[float, float]] = {} + + for ticker in tickers: + implied_volatility[ticker] = {} + option_chain = option_chains.loc[ticker] + dividend_yield_value[ticker] = ( + dividend_yield + if dividend_yield is not None + else self._dividend_yield[ticker].loc[: str(current_period)].iloc[-1] + ) + + for strike_price, row in option_chain.iterrows(): + # That expiration date is used to calculate the days to expiration + # which serves as input for the time to expiration parameter in the Black Scholes Model. + days_to_expiration = (pd.to_datetime(row["Expiration"]) - today).days + + def objective_function(sigma: float): + return ( + black_scholes_model.get_black_scholes( + stock_price=stock_price.loc[ticker], + strike_price=strike_price, + risk_free_rate=risk_free_rate, + time_to_expiration=days_to_expiration / 365, + volatility=sigma, + dividend_yield=dividend_yield_value[ticker], + put_option=put_option, + ) + - row["Last Price"] + ) ** 2 + + # The minimize function is used to find the implied volatility value that minimizes + # the objective function. This means that the difference between the output of the + # Black Scholes Model and the market option price is minimized. + implied_volatility_value = minimize( + objective_function, volatility.loc[ticker] + ).x[0] + + # Values that are equal to the current volatility refer to not being able to resolve + # and thus are not added to the implied volatility dictionary. + if round(implied_volatility_value, 4) != round( + volatility.loc[ticker], 4 + ): + implied_volatility[ticker][strike_price] = implied_volatility_value + + implied_volatility_df = pd.DataFrame(implied_volatility).unstack().dropna() + + implied_volatility_df = implied_volatility_df.round( + rounding if rounding else self._rounding + ) + + # The Expiration date is used as the name of the DataFrame + implied_volatility_df.name = option_chains.name + + if show_input_info: + helpers.show_input_info( + start_date=self._daily_historical.index[0], + end_date=self._daily_historical.index[-1], + stock_prices=stock_price, + volatility=volatility, + risk_free_rate=risk_free_rate, + dividend_yield=dividend_yield_value, + ) + + return implied_volatility_df + def get_binomial_model( self, start_date: str | None = None, @@ -461,7 +651,7 @@ def get_binomial_model( ```python from financetoolkit import Toolkit - toolkit = Toolkit(["AAPL", "MSFT"], api_key=API_KEY) + toolkit = Toolkit(["AAPL", "MSFT"], api_key="FINANCIAL_MODELING_PREP_KEY") binomial_trees_model = toolkit.options.get_binomial_trees_model( start_date='2024-02-02' diff --git a/financetoolkit/options/options_model.py b/financetoolkit/options/options_model.py index e63e9cb..ddaa990 100644 --- a/financetoolkit/options/options_model.py +++ b/financetoolkit/options/options_model.py @@ -53,13 +53,16 @@ def get_option_expiry_dates(ticker: str): return ticker_result -def get_option_chains(tickers: list[str], expiration_date: str): +def get_option_chains( + tickers: list[str], expiration_date: str, put_option: bool = False +): """ Get the option chains for a given list of tickers and expiration date. Args: tickers (list[str]): List of ticker symbols. expiration_date (str): Option expiration date. + put_option (bool, optional): Whether to get put options. Defaults to False. Returns: pd.DataFrame: Option chains. @@ -95,7 +98,9 @@ def get_option_chains(tickers: list[str], expiration_date: str): result = response.json() ticker_result = pd.DataFrame( - result["optionChain"]["result"][0]["options"][0]["calls"] + result["optionChain"]["result"][0]["options"][0][ + "puts" if put_option else "calls" + ] ) if ticker_result.empty: From 94863eefcdea2a84a0374995c4692652ad21bbcc Mon Sep 17 00:00:00 2001 From: jeroen Date: Sun, 11 Feb 2024 10:19:32 +0100 Subject: [PATCH 2/6] update tests --- .../test_toolkit_historical.csv | 2 +- .../test_get_sharpe_ratio.csv | 6 +++--- tests/performance/test_performance_controller.py | 2 +- .../test_cvar_model/test_get_cvar_gaussian.json | 2 +- .../test_cvar_model/test_get_cvar_logistic.json | 2 +- .../json/test_var_model/test_get_var_gaussian.json | 2 +- tests/risk/test_cvar_model.py | 14 ++++++++++---- tests/risk/test_risk_controller.py | 2 +- tests/risk/test_var_model.py | 9 +++++++-- 9 files changed, 26 insertions(+), 15 deletions(-) diff --git a/tests/csv/test_toolkit_controller/test_toolkit_historical.csv b/tests/csv/test_toolkit_controller/test_toolkit_historical.csv index 93324f3..49d9c8b 100644 --- a/tests/csv/test_toolkit_controller/test_toolkit_historical.csv +++ b/tests/csv/test_toolkit_controller/test_toolkit_historical.csv @@ -10,5 +10,5 @@ Date,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2016,29.0,63.0,225.0,29.0,63.0,225.0,29.0,62.0,223.0,29.0,62.0,224.0,27.0,57.0,198.0,122345200,25579900,108998300,1.0,1.0,5.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,3.0,3.0,2.0 2017,43.0,86.0,269.0,43.0,86.0,269.0,42.0,86.0,267.0,42.0,86.0,267.0,40.0,80.0,241.0,103999600,18717400,96007400,1.0,2.0,5.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,4.0,4.0,2.0 2018,40.0,101.0,250.0,40.0,102.0,250.0,39.0,100.0,247.0,39.0,102.0,250.0,38.0,96.0,230.0,140014000,33173800,144299400,1.0,2.0,5.0,-0.0,0.0,-0.0,0.0,0.0,0.0,-0.0,0.0,-0.0,0.0,0.0,0.0,4.0,4.0,2.0 -2019,72.0,157.0,321.0,73.0,158.0,322.0,72.0,156.0,320.0,73.0,158.0,322.0,72.0,152.0,302.0,100805600,18369400,57077300,1.0,2.0,6.0,1.0,1.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,7.0,7.0,3.0 +2019,72.0,157.0,321.0,73.0,158.0,322.0,72.0,156.0,320.0,73.0,158.0,322.0,71.0,152.0,302.0,100805600,18369400,57077300,1.0,2.0,6.0,1.0,1.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,7.0,7.0,3.0 2020,136.0,225.0,372.0,136.0,226.0,373.0,133.0,221.0,372.0,134.0,222.0,372.0,131.0,216.0,356.0,96452100,20272300,49455300,1.0,2.0,6.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,13.0,10.0,4.0 diff --git a/tests/performance/csv/test_performance_controller/test_get_sharpe_ratio.csv b/tests/performance/csv/test_performance_controller/test_get_sharpe_ratio.csv index 6c997f7..1a9367d 100644 --- a/tests/performance/csv/test_performance_controller/test_get_sharpe_ratio.csv +++ b/tests/performance/csv/test_performance_controller/test_get_sharpe_ratio.csv @@ -1,4 +1,4 @@ Date,AAPL,MSFT -2020,-0.2034,-0.2538 -2021,-0.8273,-0.9433 -2022,-1.285,-1.3188 +2020,-0.2,-0.25 +2021,-0.83,-0.94 +2022,-1.28,-1.32 diff --git a/tests/performance/test_performance_controller.py b/tests/performance/test_performance_controller.py index b70e16a..4d06958 100644 --- a/tests/performance/test_performance_controller.py +++ b/tests/performance/test_performance_controller.py @@ -111,7 +111,7 @@ def test_get_treynor_ratio(recorder): def test_get_sharpe_ratio(recorder): - recorder.capture(performance_module.get_sharpe_ratio()) + recorder.capture(round(performance_module.get_sharpe_ratio(), 2)) recorder.capture(performance_module.get_sharpe_ratio(period="quarterly")) recorder.capture(performance_module.get_sharpe_ratio(rolling=10)) recorder.capture(performance_module.get_sharpe_ratio(growth=True)) diff --git a/tests/risk/json/test_cvar_model/test_get_cvar_gaussian.json b/tests/risk/json/test_cvar_model/test_get_cvar_gaussian.json index 8298be9..04c0262 100644 --- a/tests/risk/json/test_cvar_model/test_get_cvar_gaussian.json +++ b/tests/risk/json/test_cvar_model/test_get_cvar_gaussian.json @@ -1 +1 @@ --0.0872305669613121 \ No newline at end of file +-0.0872 \ No newline at end of file diff --git a/tests/risk/json/test_cvar_model/test_get_cvar_logistic.json b/tests/risk/json/test_cvar_model/test_get_cvar_logistic.json index 2af2793..86807be 100644 --- a/tests/risk/json/test_cvar_model/test_get_cvar_logistic.json +++ b/tests/risk/json/test_cvar_model/test_get_cvar_logistic.json @@ -1 +1 @@ --0.10064673000756086 \ No newline at end of file +-0.1006 \ No newline at end of file diff --git a/tests/risk/json/test_var_model/test_get_var_gaussian.json b/tests/risk/json/test_var_model/test_get_var_gaussian.json index 0cbb74a..2a4f6bd 100644 --- a/tests/risk/json/test_var_model/test_get_var_gaussian.json +++ b/tests/risk/json/test_var_model/test_get_var_gaussian.json @@ -1 +1 @@ --0.03264353001936268 \ No newline at end of file +-0.0326 \ No newline at end of file diff --git a/tests/risk/test_cvar_model.py b/tests/risk/test_cvar_model.py index d11759d..247a6ce 100644 --- a/tests/risk/test_cvar_model.py +++ b/tests/risk/test_cvar_model.py @@ -16,8 +16,11 @@ def test_get_cvar_historic(recorder): def test_get_cvar_gaussian(recorder): recorder.capture( - cvar_model.get_cvar_gaussian( - returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), alpha=0.05 + round( + cvar_model.get_cvar_gaussian( + returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), alpha=0.05 + ), + 4, ) ) @@ -40,7 +43,10 @@ def test_get_cvar_laplace(recorder): def test_get_cvar_logistic(recorder): recorder.capture( - cvar_model.get_cvar_logistic( - returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), alpha=0.05 + round( + cvar_model.get_cvar_logistic( + returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), alpha=0.05 + ), + 4, ) ) diff --git a/tests/risk/test_risk_controller.py b/tests/risk/test_risk_controller.py index 0f3f285..e54adc6 100644 --- a/tests/risk/test_risk_controller.py +++ b/tests/risk/test_risk_controller.py @@ -80,7 +80,7 @@ def test_get_skewness(recorder): def test_get_kurtosis(recorder): recorder.capture(risk_module.get_kurtosis()) - recorder.capture(risk_module.get_kurtosis(within_period=False)) + recorder.capture(round(risk_module.get_kurtosis(within_period=False), 4)) recorder.capture(risk_module.get_kurtosis(period="monthly")) recorder.capture(risk_module.get_kurtosis(growth=True)) recorder.capture(risk_module.get_kurtosis(growth=True, lag=[1, 2, 3])) diff --git a/tests/risk/test_var_model.py b/tests/risk/test_var_model.py index 03cec09..cab5f75 100644 --- a/tests/risk/test_var_model.py +++ b/tests/risk/test_var_model.py @@ -16,8 +16,13 @@ def test_get_var_historic(recorder): def test_get_var_gaussian(recorder): recorder.capture( - var_model.get_var_gaussian( - returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), alpha=0.05, cornish_fisher=True + round( + var_model.get_var_gaussian( + returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), + alpha=0.05, + cornish_fisher=True, + ), + 4, ) ) recorder.capture( From 6e461f2dae957d56dd98029c8e3366129e34029e Mon Sep 17 00:00:00 2001 From: jeroen Date: Sun, 11 Feb 2024 10:20:25 +0100 Subject: [PATCH 3/6] Some formatting --- financetoolkit/models/altman_model.py | 8 +++----- financetoolkit/models/models_controller.py | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/financetoolkit/models/altman_model.py b/financetoolkit/models/altman_model.py index 9bada3d..f904d5a 100644 --- a/financetoolkit/models/altman_model.py +++ b/financetoolkit/models/altman_model.py @@ -154,11 +154,9 @@ def get_altman_z_score( The formula is as follows: - - Altman Z-Score = 1.2 * Working Capital to Total Assets Ratio - + 1.4 * Retained Earnings to Total Assets Ratio - + 3.3 * Earnings Before Interest and Taxes to Total Assets Ratio - + 0.6 * Market Value of Equity to Book Value of Total Liabilities Ratio - + 1.0 * Sales to Total Assets Ratio + Altman Z-Score = 1.2 * Working Capital to Total Assets Ratio + 1.4 * Retained Earnings to Total Assets Ratio + + 3.3 * Earnings Before Interest and Taxes to Total Assets Ratio + + 0.6 * Market Value of Equity to Book Value of Total Liabilities Ratio + 1.0 * Sales to Total Assets Ratio Args: working_capital_to_total_assets_ratio (float | pd.Series | pd.DataFrame): The Working Capital to Total diff --git a/financetoolkit/models/models_controller.py b/financetoolkit/models/models_controller.py index 8089985..99f4c8c 100644 --- a/financetoolkit/models/models_controller.py +++ b/financetoolkit/models/models_controller.py @@ -408,7 +408,7 @@ def get_weighted_average_cost_of_capital( - Market Value of Equity = Share Price * Total Shares Outstanding - Market Value of Debt = Total Debt - Total Market Value = Market Value of Equity + Market Value of Debt - - Cost of Equity = Risk Free Rate + Beta * (Benchmark Return - Risk Free Rate) + - Cost of Equity = Risk Free Rate + Beta * (Benchmark Return — Risk Free Rate) - Cost of Debt = Interest Expense / Total Debt - WACC = (Market Value of Equity / Total Market Value) * Cost of Equity + (Market Value of Debt / Total Market Value) * Cost of Debt * (1 — Corporate Tax Rate) @@ -729,7 +729,7 @@ def get_gorden_growth_model( The formula is as follows: - - Intrinsic Value = (Dividends Per Share * (1 + Growth Rate)) / (Rate of Return - Growth Rate) + - Intrinsic Value = (Dividends Per Share * (1 + Growth Rate)) / (Rate of Return — Growth Rate) The formula essentially discounts the future expected dividends to their present value, taking into account the required rate of return and the growth rate. The numerator represents the expected dividend in the From 0a6f5525e3f51dcdd3277f7082194888495919a2 Mon Sep 17 00:00:00 2001 From: jeroen Date: Sun, 11 Feb 2024 10:26:38 +0100 Subject: [PATCH 4/6] Update tests rounding --- tests/performance/test_performance_model.py | 7 +++++-- tests/risk/test_risk_model.py | 14 ++++++++++---- tests/risk/test_var_model.py | 11 +++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/performance/test_performance_model.py b/tests/performance/test_performance_model.py index d9fb7a7..ae52cf5 100644 --- a/tests/performance/test_performance_model.py +++ b/tests/performance/test_performance_model.py @@ -120,8 +120,11 @@ def test_get_treynor_ratio(recorder): def test_get_sharpe_ratio(recorder): recorder.capture( - performance_model.get_sharpe_ratio( - excess_returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]) + round( + performance_model.get_sharpe_ratio( + excess_returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]) + ), + 4, ) ) diff --git a/tests/risk/test_risk_model.py b/tests/risk/test_risk_model.py index b6daf45..f9621ce 100644 --- a/tests/risk/test_risk_model.py +++ b/tests/risk/test_risk_model.py @@ -33,13 +33,19 @@ def test_get_skewness(recorder): def test_get_kurtosis(recorder): recorder.capture( - risk_model.get_kurtosis( - returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), fisher=True + round( + risk_model.get_kurtosis( + returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), fisher=True + ), + 4, ) ) recorder.capture( - risk_model.get_kurtosis( - returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), fisher=False + round( + risk_model.get_kurtosis( + returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), fisher=False + ), + 4, ) ) diff --git a/tests/risk/test_var_model.py b/tests/risk/test_var_model.py index cab5f75..d876889 100644 --- a/tests/risk/test_var_model.py +++ b/tests/risk/test_var_model.py @@ -26,10 +26,13 @@ def test_get_var_gaussian(recorder): ) ) recorder.capture( - var_model.get_var_gaussian( - returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), - alpha=0.03, - cornish_fisher=False, + round( + var_model.get_var_gaussian( + returns=pd.Series([0.3, 0.2, 0.1, 0, 0.06]), + alpha=0.03, + cornish_fisher=False, + ), + 4, ) ) From fa1bf6b31bdce8a65bb6d67678aa7d7b6940c0bb Mon Sep 17 00:00:00 2001 From: jeroen Date: Sun, 11 Feb 2024 10:28:32 +0100 Subject: [PATCH 5/6] Update tests, merely rounding --- .../csv/test_performance_model/test_get_sharpe_ratio.csv | 8 ++++---- tests/risk/json/test_risk_model/test_get_kurtosis.json | 2 +- tests/risk/json/test_risk_model/test_get_kurtosis_1.json | 2 +- .../risk/json/test_var_model/test_get_var_gaussian_1.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/performance/csv/test_performance_model/test_get_sharpe_ratio.csv b/tests/performance/csv/test_performance_model/test_get_sharpe_ratio.csv index 65e6879..cd3b09d 100644 --- a/tests/performance/csv/test_performance_model/test_get_sharpe_ratio.csv +++ b/tests/performance/csv/test_performance_model/test_get_sharpe_ratio.csv @@ -1,6 +1,6 @@ ,0 -0,2.5246658545347787 -1,1.6831105696898527 -2,0.8415552848449264 +0,2.5247 +1,1.6831 +2,0.8416 3,0.0 -4,0.5049331709069558 +4,0.5049 diff --git a/tests/risk/json/test_risk_model/test_get_kurtosis.json b/tests/risk/json/test_risk_model/test_get_kurtosis.json index d0eb12d..44bdc03 100644 --- a/tests/risk/json/test_risk_model/test_get_kurtosis.json +++ b/tests/risk/json/test_risk_model/test_get_kurtosis.json @@ -1 +1 @@ --0.7931208821192701 \ No newline at end of file +-0.7931 \ No newline at end of file diff --git a/tests/risk/json/test_risk_model/test_get_kurtosis_1.json b/tests/risk/json/test_risk_model/test_get_kurtosis_1.json index 0e61570..59e2262 100644 --- a/tests/risk/json/test_risk_model/test_get_kurtosis_1.json +++ b/tests/risk/json/test_risk_model/test_get_kurtosis_1.json @@ -1 +1 @@ -1.8017197794701834 \ No newline at end of file +1.8017 \ No newline at end of file diff --git a/tests/risk/json/test_var_model/test_get_var_gaussian_1.json b/tests/risk/json/test_var_model/test_get_var_gaussian_1.json index 943c652..e121b10 100644 --- a/tests/risk/json/test_var_model/test_get_var_gaussian_1.json +++ b/tests/risk/json/test_var_model/test_get_var_gaussian_1.json @@ -1 +1 @@ --0.06789571381508297 \ No newline at end of file +-0.0679 \ No newline at end of file From ceb8fa7e5cd6a049215b7ef94e35d0197a4d05ee Mon Sep 17 00:00:00 2001 From: jeroen Date: Sun, 11 Feb 2024 10:34:33 +0100 Subject: [PATCH 6/6] Release of v1.8.4 --- financetoolkit/options/options_controller.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/financetoolkit/options/options_controller.py b/financetoolkit/options/options_controller.py index d3fc188..8be8a14 100644 --- a/financetoolkit/options/options_controller.py +++ b/financetoolkit/options/options_controller.py @@ -516,7 +516,7 @@ def get_implied_volatility( ) for strike_price, row in option_chain.iterrows(): - # That expiration date is used to calculate the days to expiration + # The expiration date is used to calculate the days to expiration # which serves as input for the time to expiration parameter in the Black Scholes Model. days_to_expiration = (pd.to_datetime(row["Expiration"]) - today).days diff --git a/pyproject.toml b/pyproject.toml index 03f83a3..0f568f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "financetoolkit" -version = "1.8.3" +version = "1.8.4" description = "Transparent and Efficient Financial Analysis" license = "MIT" authors = ["Jeroen Bouma"]