mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-07 09:52:13 +00:00
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 <jmaslek11@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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("")
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
14
website/content/common/quantitative_analysis/var/_index.md
vendored
Normal file
14
website/content/common/quantitative_analysis/var/_index.md
vendored
Normal file
@@ -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)
|
||||
```
|
||||
Reference in New Issue
Block a user