From 2e7b48fe859abc9110cc11e6e0d7ac6fbbc44656 Mon Sep 17 00:00:00 2001 From: northern-64bit <75195383+northern-64bit@users.noreply.github.com> Date: Tue, 1 Feb 2022 12:43:30 +0100 Subject: [PATCH] Adding VaR and Adjusted VaR (#1269) * Adding avar command to qa context * Adding documentation and VaR command * Linting * More linting * Somehow discordbot tests didn't like my linting * Fixing tests * Fixing requested suggestions. Fixing jmasleks comments, but I sadly couldn't sort the table by the VaR (if the manually entered percentage is 75 it's currently still displayed below the 99% VaR) * linting * Linting fix * Adding every qa command to hugo Also lint fixing * Fixing previous error Co-authored-by: jmaslek --- .../common/quantitative_analysis/qa_model.py | 118 +++++++++++++++++- .../common/quantitative_analysis/qa_view.py | 44 +++++++ .../quantitative_analysis/qa_controller.py | 53 ++++++++ .../test_qa_controller/test_print_help.txt | 2 + .../quantitative_analysis/var/_index.md | 14 +++ 5 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 website/content/common/quantitative_analysis/var/_index.md diff --git a/gamestonk_terminal/common/quantitative_analysis/qa_model.py b/gamestonk_terminal/common/quantitative_analysis/qa_model.py index d3b557db733..6da7f893311 100644 --- a/gamestonk_terminal/common/quantitative_analysis/qa_model.py +++ b/gamestonk_terminal/common/quantitative_analysis/qa_model.py @@ -4,7 +4,6 @@ __docformat__ = "numpy" import logging import warnings from typing import Any, Tuple - import pandas as pd import statsmodels.api as sm from scipy import stats @@ -31,6 +30,7 @@ def get_summary(df: pd.DataFrame) -> pd.DataFrame: """ df_stats = df.describe(percentiles=[0.1, 0.25, 0.5, 0.75, 0.9]) + print(df_stats.to_string()) df_stats.loc["var"] = df_stats.loc["std"] ** 2 return df_stats @@ -164,3 +164,119 @@ def get_unitroot(df: pd.DataFrame, fuller_reg: str, kpss_reg: str) -> pd.DataFra vals2 = [res2[0], res2[1], res2[2], "", ""] data["KPSS"] = vals2 return data + + +def calculate_adjusted_var( + kurtosis: float, skew: float, ndp: float, std: float, mean: float +): + """Calculates VaR, which is adjusted for skew and kurtosis (Cornish-Fischer-Expansion) + + Parameters + ---------- + kurtosis: float + kurtosis of data + skew: float + skew of data + ndp: float + normal distribution percentage number (99% -> -2.326) + std: float + standard deviation of data + mean: float + mean of data + + Returns + ------- + float + Real adjusted VaR + """ + + # Derived from Cornish-Fisher-Expansion + # Formula for quantile from "Finance Compact Plus" by Zimmerman; Part 1, page 130-131 + # More material/resources: + # "Numerical Methods and Optimization in Finance" by Gilli, Maringer & Schumann; + # https://www.value-at-risk.net/the-cornish-fisher-expansion/; + # "Risk Management and Financial Institutions" by John C. Hull + + skew_component = skew / 6 * (ndp**2 - 1) ** 2 - skew**2 / 36 * ndp * ( + 2 * ndp**2 - 5 + ) + kurtosis_component = (kurtosis - 3) / 24 * ndp * (ndp**2 - 3) + quantile = ndp + skew_component + kurtosis_component + log_return = mean + quantile * std + real_return = 2.7182818**log_return - 1 + return real_return + + +def get_var(data: pd.DataFrame, use_mean: bool, adjusted_var: bool, percentile: int): + """Gets value at risk for specified stock dataframe + + Parameters + ---------- + data: pd.DataFrame + Dataframe of a stock + use_mean: bool + If one should use the stocks mean for calculation + adjusted_var: bool + If one should return VaR adjusted for skew and kurtosis + percentile: int + VaR percentile + + Returns + ------- + list + list of VaR + list + list of historical VaR + """ + data = data[["adjclose"]].copy() + data.loc[:, "return"] = data.adjclose.pct_change() + + # Distribution percentages + percentile_90 = -1.282 + percentile_95 = -1.645 + percentile_99 = -2.326 + percentile_custom = stats.norm.ppf(1 - percentile) + + # Mean + if use_mean: + mean = data["return"].mean() + else: + mean = 0 + + # Standard Deviation + std = data["return"].std(axis=0) + + if adjusted_var: + + # Kurtosis + # Measures height and sharpness of the central peak relative to that of a standard bell curve + k = data["return"].kurtosis(axis=0) + + # Skewness + # Measure of the asymmetry of the probability distribution of a random variable about its mean + s = data["return"].skew(axis=0) + + # Adjusted VaR + var_90 = calculate_adjusted_var(k, s, percentile_90, std, mean) + var_95 = calculate_adjusted_var(k, s, percentile_95, std, mean) + var_99 = calculate_adjusted_var(k, s, percentile_99, std, mean) + var_custom = calculate_adjusted_var(k, s, percentile_custom, std, mean) + + else: + # Regular Var + var_90 = 2.7182818 ** (mean + percentile_90 * std) - 1 + var_95 = 2.7182818 ** (mean + percentile_95 * std) - 1 + var_99 = 2.7182818 ** (mean + percentile_99 * std) - 1 + var_custom = 2.7182818 ** (mean + percentile_custom * std) - 1 + + data.sort_values("return", inplace=True, ascending=True) + + # Historical VaR + hist_var_90 = data["return"].quantile(0.1) + hist_var_95 = data["return"].quantile(0.05) + hist_var_99 = data["return"].quantile(0.01) + hist_var_custom = data["return"].quantile(1 - percentile) + + var_list = [var_90, var_95, var_99, var_custom] + hist_var_list = [hist_var_90, hist_var_95, hist_var_99, hist_var_custom] + return var_list, hist_var_list diff --git a/gamestonk_terminal/common/quantitative_analysis/qa_view.py b/gamestonk_terminal/common/quantitative_analysis/qa_view.py index f24fe401018..8b3ad6d9fba 100644 --- a/gamestonk_terminal/common/quantitative_analysis/qa_view.py +++ b/gamestonk_terminal/common/quantitative_analysis/qa_view.py @@ -635,3 +635,47 @@ def display_line( os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "line", ) + + +def display_var( + data: pd.DataFrame, use_mean: bool, ticker: str, adjusted_var: bool, percentile: int +): + """Displays VaR of dataframe + + Parameters + ---------- + data: pd.Dataframe + stock dataframe + use_mean: bool + if one should use the stocks mean return + ticker: str + ticker of the stock + adjusted_var: bool + if one should have VaR adjusted for skew and kurtosis (Cornish-Fisher-Expansion) + percentile: int + var percentile + """ + var_list, hist_var_list = qa_model.get_var(data, use_mean, adjusted_var, percentile) + + str_hist_label = "Historical VaR:" + + if adjusted_var: + str_var_label = "Adjusted Var:" + str_title = "Adjusted " + else: + str_var_label = "VaR:" + str_title = "" + + data_dictionary = {str_var_label: var_list, str_hist_label: hist_var_list} + data = pd.DataFrame( + data_dictionary, index=["90.0%", "95.0%", "99.0%", f"{percentile*100}%"] + ) + + print_rich_table( + data, + show_index=True, + headers=list(data.columns), + title=f"[bold]{ticker} {str_title}Value at Risk[/bold]", + floatfmt=".4f", + ) + console.print("") diff --git a/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py b/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py index 9d32c048d37..24bfcb22047 100644 --- a/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py +++ b/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py @@ -52,6 +52,7 @@ class QaController(StockController): "goodness", "unitroot", "capm", + "var", ] stock_interval = [1, 5, 15, 30, 60] @@ -125,6 +126,8 @@ class QaController(StockController): quantile rolling median and quantile of prices skew rolling skewness of distribution of prices kurtosis rolling kurtosis of distribution of prices +[info]Risk:[/info] + var value at risk [info]Other:[/info] raw print raw data decompose decomposition in cyclic-trend, season, and residuals of prices @@ -699,3 +702,53 @@ class QaController(StockController): ns_parser = parse_known_args_and_warn(parser, other_args) if ns_parser: capm_view(self.ticker) + + def call_var(self, other_args: List[str]): + """Process var command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="var", + description=""" + Provides value at risk (short: VaR) of the selected stock. + """, + ) + parser.add_argument( + "-m", + "--mean", + action="store_true", + default=False, + dest="use_mean", + help="If one should use the mean of the stocks return", + ) + parser.add_argument( + "-a", + "--adjusted", + action="store_true", + default=False, + dest="adjusted", + help=""" + If the VaR should be adjusted for skew and kurtosis (Cornish-Fisher-Expansion) + """, + ) + parser.add_argument( + "-p", + "--percentile", + action="store", + dest="percentile", + type=float, + default=99.9, + help=""" + Percentile used for VaR calculations, for example input 99.9 equals a 99.9% VaR + """, + ) + + ns_parser = parse_known_args_and_warn(parser, other_args) + if ns_parser: + qa_view.display_var( + self.stock, + ns_parser.use_mean, + self.ticker, + ns_parser.adjusted, + ns_parser.percentile / 100, + ) diff --git a/tests/gamestonk_terminal/stocks/quantitative_analysis/txt/test_qa_controller/test_print_help.txt b/tests/gamestonk_terminal/stocks/quantitative_analysis/txt/test_qa_controller/test_print_help.txt index 7a25863d38f..c3ef1627390 100644 --- a/tests/gamestonk_terminal/stocks/quantitative_analysis/txt/test_qa_controller/test_print_help.txt +++ b/tests/gamestonk_terminal/stocks/quantitative_analysis/txt/test_qa_controller/test_print_help.txt @@ -22,6 +22,8 @@ Rolling Metrics: quantile rolling median and quantile of prices skew rolling skewness of distribution of prices kurtosis rolling kurtosis of distribution of prices +Risk: + var value at risk Other: raw print raw data decompose decomposition in cyclic-trend, season, and residuals of prices diff --git a/website/content/common/quantitative_analysis/var/_index.md b/website/content/common/quantitative_analysis/var/_index.md new file mode 100644 index 00000000000..255f2648345 --- /dev/null +++ b/website/content/common/quantitative_analysis/var/_index.md @@ -0,0 +1,14 @@ +``` +usage: var [-m] [-a] [-p PERCENTILE] [-h] +``` + +Provides value at risk (short: VaR) of the selected stock. + +``` +optional arguments: + -m, --mean If one should use the mean of the stocks return + -a, --adjusted If the VaR should be adjusted for skew and kurtosis (Cornish-Fisher-Expansion) + -p PERCENTILE, --percent PERCENTILE + Percentile used for VaR calculations, for example input 99.9 equals a 99.9% VaR + -h, --help show this help message (default: False) +```