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:
northern-64bit
2022-02-01 12:43:30 +01:00
committed by GitHub
parent 90b05d6641
commit 2e7b48fe85
5 changed files with 230 additions and 1 deletions

View File

@@ -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

View File

@@ -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("")

View File

@@ -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,
)

View File

@@ -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

View 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)
```