mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
Adding risk metrics to portfolio and qa (#1394)
* Adding es to qa and var to portfolio * Fixing mathematics * Adding es command to portfolio menu * Linting * Fixing tests * Lint fixes * lint fixes * Adding hugo, tests and small fixes * Fixes * Fix mypy + portfolio model Co-authored-by: jmaslek <jmaslek11@gmail.com>
This commit is contained in:
@@ -3,10 +3,11 @@ __docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from typing import Any, Tuple
|
||||
from typing import Any, Tuple, Union, List
|
||||
import pandas as pd
|
||||
import statsmodels.api as sm
|
||||
from scipy import stats
|
||||
import numpy as np
|
||||
from statsmodels.tsa.seasonal import seasonal_decompose
|
||||
from statsmodels.tsa.stattools import adfuller, kpss
|
||||
|
||||
@@ -193,9 +194,10 @@ def calculate_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
|
||||
# - "Numerical Methods and Optimization in Finance" by Gilli, Maringer & Schumann;
|
||||
# - https://www.value-at-risk.net/the-cornish-fisher-expansion/;
|
||||
# - https://www.diva-portal.org/smash/get/diva2:442078/FULLTEXT01.pdf, Section 2.4.2, p.18;
|
||||
# - "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
|
||||
@@ -207,19 +209,30 @@ def calculate_adjusted_var(
|
||||
return real_return
|
||||
|
||||
|
||||
def get_var(data: pd.DataFrame, use_mean: bool, adjusted_var: bool, percentile: int):
|
||||
def get_var(
|
||||
data: pd.DataFrame,
|
||||
use_mean: bool,
|
||||
adjusted_var: bool,
|
||||
student_t: bool,
|
||||
percentile: Union[int, float],
|
||||
portfolio: bool,
|
||||
):
|
||||
"""Gets value at risk for specified stock dataframe
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: pd.DataFrame
|
||||
Dataframe of a stock
|
||||
Dataframe of a stock/portfolio
|
||||
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
|
||||
student_t: bool
|
||||
If one should use the student-t distribution
|
||||
percentile: Union[int,float]
|
||||
VaR percentile
|
||||
portfolio: bool
|
||||
If the data is a portfolio
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -228,8 +241,13 @@ def get_var(data: pd.DataFrame, use_mean: bool, adjusted_var: bool, percentile:
|
||||
list
|
||||
list of historical VaR
|
||||
"""
|
||||
data = data[["adjclose"]].copy()
|
||||
data.loc[:, "return"] = data.adjclose.pct_change()
|
||||
if not portfolio:
|
||||
data = data[["adjclose"]].copy()
|
||||
data.loc[:, "return"] = data.adjclose.pct_change()
|
||||
data_return = data["return"]
|
||||
else:
|
||||
data = data[1:].copy()
|
||||
data_return = data
|
||||
|
||||
# Distribution percentages
|
||||
percentile_90 = -1.282
|
||||
@@ -239,22 +257,22 @@ def get_var(data: pd.DataFrame, use_mean: bool, adjusted_var: bool, percentile:
|
||||
|
||||
# Mean
|
||||
if use_mean:
|
||||
mean = data["return"].mean()
|
||||
mean = data_return.mean()
|
||||
else:
|
||||
mean = 0
|
||||
|
||||
# Standard Deviation
|
||||
std = data["return"].std(axis=0)
|
||||
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)
|
||||
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)
|
||||
s = data_return.skew(axis=0)
|
||||
|
||||
# Adjusted VaR
|
||||
var_90 = calculate_adjusted_var(k, s, percentile_90, std, mean)
|
||||
@@ -262,21 +280,192 @@ def get_var(data: pd.DataFrame, use_mean: bool, adjusted_var: bool, percentile:
|
||||
var_99 = calculate_adjusted_var(k, s, percentile_99, std, mean)
|
||||
var_custom = calculate_adjusted_var(k, s, percentile_custom, std, mean)
|
||||
|
||||
elif student_t:
|
||||
# Calculating VaR based on the Student-t distribution
|
||||
|
||||
# Fitting student-t parameters to the data
|
||||
v, _, _ = stats.t.fit(data_return.fillna(0))
|
||||
if not use_mean:
|
||||
mean = 0
|
||||
var_90 = np.sqrt((v - 2) / v) * stats.t.ppf(0.1, v) * std + mean
|
||||
var_95 = np.sqrt((v - 2) / v) * stats.t.ppf(0.05, v) * std + mean
|
||||
var_99 = np.sqrt((v - 2) / v) * stats.t.ppf(0.01, v) * std + mean
|
||||
var_custom = np.sqrt((v - 2) / v) * stats.t.ppf(1 - percentile, v) * 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
|
||||
var_90 = np.exp(mean + percentile_90 * std) - 1
|
||||
var_95 = np.exp(mean + percentile_95 * std) - 1
|
||||
var_99 = np.exp(mean + percentile_99 * std) - 1
|
||||
var_custom = np.exp(mean + percentile_custom * std) - 1
|
||||
|
||||
data.sort_values("return", inplace=True, ascending=True)
|
||||
if not portfolio:
|
||||
data.sort_values("return", inplace=True, ascending=True)
|
||||
data_return = data["return"]
|
||||
else:
|
||||
data.sort_values(inplace=True, ascending=True)
|
||||
data_return = data
|
||||
|
||||
# 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)
|
||||
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
|
||||
|
||||
|
||||
def get_es(
|
||||
data: pd.DataFrame,
|
||||
use_mean: bool,
|
||||
distribution: str,
|
||||
percentile: Union[float, int],
|
||||
portfolio: bool,
|
||||
) -> Tuple[List[float], List[float]]:
|
||||
"""Gets Expected Shortfall for specified stock dataframe
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: pd.DataFrame
|
||||
Dataframe of a stock
|
||||
use_mean: bool
|
||||
If one should use the stocks mean for calculation
|
||||
distribution: str
|
||||
Type of distribution, options: laplace, student_t, normal
|
||||
percentile: Union[float,int]
|
||||
VaR percentile
|
||||
portfolio: bool
|
||||
If the data is a portfolio
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
list of ES
|
||||
list
|
||||
list of historical ES
|
||||
"""
|
||||
if not portfolio:
|
||||
data = data[["adjclose"]].copy()
|
||||
data.loc[:, "return"] = data.adjclose.pct_change()
|
||||
data_return = data["return"]
|
||||
else:
|
||||
data = data[1:].copy()
|
||||
data_return = data
|
||||
|
||||
# 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 distribution == "laplace":
|
||||
# Calculating ES based on Laplace distribution
|
||||
# For formula see: https://en.wikipedia.org/wiki/Expected_shortfall#Laplace_distribution
|
||||
|
||||
# Fitting b (scale parameter) to the variance of the data
|
||||
# Since variance of the Laplace dist.: var = 2*b**2
|
||||
# Thus:
|
||||
b = np.sqrt(std**2 / 2)
|
||||
|
||||
# Calculation
|
||||
es_90 = -b * (1 - np.log(2 * 0.1)) + mean
|
||||
es_95 = -b * (1 - np.log(2 * 0.05)) + mean
|
||||
es_99 = -b * (1 - np.log(2 * 0.01)) + mean
|
||||
|
||||
if (1 - percentile) < 0.5:
|
||||
es_custom = -b * (1 - np.log(2 * (1 - percentile))) + mean
|
||||
else:
|
||||
es_custom = 0
|
||||
|
||||
elif distribution == "student_t":
|
||||
# Calculating ES based on the Student-t distribution
|
||||
|
||||
# Fitting student-t parameters to the data
|
||||
v, _, scale = stats.t.fit(data_return.fillna(0))
|
||||
if not use_mean:
|
||||
mean = 0
|
||||
|
||||
# Student T Distribution percentages
|
||||
percentile_90 = stats.t.ppf(0.1, v)
|
||||
percentile_95 = stats.t.ppf(0.05, v)
|
||||
percentile_99 = stats.t.ppf(0.01, v)
|
||||
percentile_custom = stats.t.ppf(1 - percentile, v)
|
||||
|
||||
# Calculation
|
||||
es_90 = (
|
||||
-scale
|
||||
* (v + percentile_90**2)
|
||||
/ (v - 1)
|
||||
* stats.t.pdf(percentile_90, v)
|
||||
/ 0.1
|
||||
+ mean
|
||||
)
|
||||
es_95 = (
|
||||
-scale
|
||||
* (v + percentile_95**2)
|
||||
/ (v - 1)
|
||||
* stats.t.pdf(percentile_95, v)
|
||||
/ 0.05
|
||||
+ mean
|
||||
)
|
||||
es_99 = (
|
||||
-scale
|
||||
* (v + percentile_99**2)
|
||||
/ (v - 1)
|
||||
* stats.t.pdf(percentile_99, v)
|
||||
/ 0.01
|
||||
+ mean
|
||||
)
|
||||
es_custom = (
|
||||
-scale
|
||||
* (v + percentile_custom**2)
|
||||
/ (v - 1)
|
||||
* stats.t.pdf(percentile_custom, v)
|
||||
/ (1 - percentile)
|
||||
+ mean
|
||||
)
|
||||
|
||||
elif distribution == "logistic":
|
||||
# Logistic distribution
|
||||
# For formula see: https://en.wikipedia.org/wiki/Expected_shortfall#Logistic_distribution
|
||||
|
||||
# Fitting s (scale parameter) to the variance of the data
|
||||
# Since variance of the Logistic dist.: var = s**2*pi**2/3
|
||||
# Thus:
|
||||
s = np.sqrt(3 * std**2 / np.pi**2)
|
||||
|
||||
# Calculation
|
||||
a = 1 - percentile
|
||||
es_90 = -s * np.log((0.9 ** (1 - 1 / 0.1)) / 0.1) + mean
|
||||
es_95 = -s * np.log((0.95 ** (1 - 1 / 0.05)) / 0.05) + mean
|
||||
es_99 = -s * np.log((0.99 ** (1 - 1 / 0.01)) / 0.01) + mean
|
||||
es_custom = -s * np.log((percentile ** (1 - 1 / a)) / a) + mean
|
||||
|
||||
else:
|
||||
# Regular Expected Shortfall
|
||||
es_90 = std * -stats.norm.pdf(percentile_90) / 0.1 + mean
|
||||
es_95 = std * -stats.norm.pdf(percentile_95) / 0.05 + mean
|
||||
es_99 = std * -stats.norm.pdf(percentile_99) / 0.01 + mean
|
||||
es_custom = std * -stats.norm.pdf(percentile_custom) / (1 - percentile) + mean
|
||||
|
||||
# Historical Expected Shortfall
|
||||
_, hist_var_list = get_var(data, use_mean, False, False, percentile, portfolio)
|
||||
hist_es_90 = data_return[data_return <= hist_var_list[0]].mean()
|
||||
hist_es_95 = data_return[data_return <= hist_var_list[1]].mean()
|
||||
hist_es_99 = data_return[data_return <= hist_var_list[2]].mean()
|
||||
hist_es_custom = data_return[data_return <= hist_var_list[3]].mean()
|
||||
|
||||
es_list = [es_90, es_95, es_99, es_custom]
|
||||
hist_es_list = [hist_es_90, hist_es_95, hist_es_99, hist_es_custom]
|
||||
return es_list, hist_es_list
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Quantitative Analysis View"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
@@ -947,7 +950,13 @@ def display_line(
|
||||
|
||||
|
||||
def display_var(
|
||||
data: pd.DataFrame, use_mean: bool, ticker: str, adjusted_var: bool, percentile: int
|
||||
data: pd.DataFrame,
|
||||
ticker: str = "",
|
||||
use_mean: bool = False,
|
||||
adjusted_var: bool = False,
|
||||
student_t: bool = False,
|
||||
percentile: float = 0.999,
|
||||
portfolio: bool = False,
|
||||
):
|
||||
"""Displays VaR of dataframe
|
||||
|
||||
@@ -961,20 +970,32 @@ def display_var(
|
||||
ticker of the stock
|
||||
adjusted_var: bool
|
||||
if one should have VaR adjusted for skew and kurtosis (Cornish-Fisher-Expansion)
|
||||
student_t: bool
|
||||
If one should use the student-t distribution
|
||||
percentile: int
|
||||
var percentile
|
||||
portfolio: bool
|
||||
If the data is a portfolio
|
||||
"""
|
||||
var_list, hist_var_list = qa_model.get_var(data, use_mean, adjusted_var, percentile)
|
||||
var_list, hist_var_list = qa_model.get_var(
|
||||
data, use_mean, adjusted_var, student_t, percentile, portfolio
|
||||
)
|
||||
|
||||
str_hist_label = "Historical VaR:"
|
||||
|
||||
if adjusted_var:
|
||||
str_var_label = "Adjusted Var:"
|
||||
str_var_label = "Adjusted VaR:"
|
||||
str_title = "Adjusted "
|
||||
elif student_t:
|
||||
str_var_label = "Student-t VaR"
|
||||
str_title = "Student-t "
|
||||
else:
|
||||
str_var_label = "VaR:"
|
||||
str_title = ""
|
||||
|
||||
if ticker != "":
|
||||
ticker += " "
|
||||
|
||||
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}%"]
|
||||
@@ -984,7 +1005,69 @@ def display_var(
|
||||
data,
|
||||
show_index=True,
|
||||
headers=list(data.columns),
|
||||
title=f"[bold]{ticker} {str_title}Value at Risk[/bold]",
|
||||
title=f"[bold]{ticker}{str_title}Value at Risk[/bold]",
|
||||
floatfmt=".4f",
|
||||
)
|
||||
console.print("")
|
||||
|
||||
|
||||
def display_es(
|
||||
data: pd.DataFrame,
|
||||
ticker: str = "",
|
||||
use_mean: bool = False,
|
||||
distribution: str = "normal",
|
||||
percentile: float = 0.999,
|
||||
portfolio: bool = False,
|
||||
):
|
||||
"""Displays expected shortfall
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: pd.DataFrame
|
||||
stock dataframe
|
||||
use_mean:
|
||||
if one should use the stocks mean return
|
||||
ticker: str
|
||||
ticker of the stock
|
||||
distribution: str
|
||||
choose distribution to use: logistic, laplace, normal
|
||||
percentile: int
|
||||
es percentile
|
||||
portfolio: bool
|
||||
If the data is a portfolio
|
||||
"""
|
||||
es_list, hist_es_list = qa_model.get_es(
|
||||
data, use_mean, distribution, percentile, portfolio
|
||||
)
|
||||
|
||||
str_hist_label = "Historical ES:"
|
||||
|
||||
if distribution == "laplace":
|
||||
str_es_label = "Laplace ES:"
|
||||
str_title = "Laplace "
|
||||
elif distribution == "student_t":
|
||||
str_es_label = "Student-t ES"
|
||||
str_title = "Student-t "
|
||||
elif distribution == "logistic":
|
||||
str_es_label = "Logistic ES"
|
||||
str_title = "Logistic "
|
||||
else:
|
||||
str_es_label = "ES:"
|
||||
str_title = ""
|
||||
|
||||
if ticker != "":
|
||||
ticker += " "
|
||||
|
||||
data_dictionary = {str_es_label: es_list, str_hist_label: hist_es_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}Expected Shortfall[/bold]",
|
||||
floatfmt=".4f",
|
||||
)
|
||||
console.print("")
|
||||
|
||||
@@ -49,12 +49,15 @@ class PortfolioController(BaseController):
|
||||
"al",
|
||||
"dd",
|
||||
"rolling",
|
||||
"var",
|
||||
"es",
|
||||
]
|
||||
CHOICES_MENUS = [
|
||||
"bro",
|
||||
"po",
|
||||
"pa",
|
||||
]
|
||||
distributions = ["laplace", "student_t", "logistic", "normal"]
|
||||
PATH = "/portfolio/"
|
||||
|
||||
def __init__(self, queue: List[str] = None):
|
||||
@@ -108,6 +111,10 @@ Loaded:[/info] {self.portfolio_name or None}
|
||||
rmr graph your returns versus the market's returns
|
||||
dd display portfolio drawdown
|
||||
al display allocation to given assets over period[/cmds]
|
||||
|
||||
[info]Risk Metrics:[/info][cmds]
|
||||
var display value at risk
|
||||
es display expected shortfall[/cmds]
|
||||
"""
|
||||
# TODO: Clean up the reports inputs
|
||||
# TODO: Edit the allocation to allow the different asset classes
|
||||
@@ -406,6 +413,134 @@ Loaded:[/info] {self.portfolio_name or None}
|
||||
# else:
|
||||
# console.print("Please add items to the portfolio\n")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
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 portfolio.
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--mean",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="use_mean",
|
||||
help="If one should use the mean of the portfolio 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(
|
||||
"-s",
|
||||
"--student",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="student_t",
|
||||
help="""
|
||||
If one should use the student-t distribution
|
||||
""",
|
||||
)
|
||||
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 Percent VaR
|
||||
""",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
if self.portfolio.empty:
|
||||
console.print("[red]No portfolio loaded.[/red]\n")
|
||||
return
|
||||
if ns_parser.adjusted and ns_parser.student_t:
|
||||
console.print("Select the adjusted or the student_t parameter.\n")
|
||||
else:
|
||||
from gamestonk_terminal.common.quantitative_analysis import qa_view
|
||||
|
||||
self.portfolio.generate_holdings_from_trades()
|
||||
qa_view.display_var(
|
||||
self.portfolio.returns,
|
||||
"Portfolio",
|
||||
ns_parser.use_mean,
|
||||
ns_parser.adjusted,
|
||||
ns_parser.student_t,
|
||||
ns_parser.percentile / 100,
|
||||
True,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_es(self, other_args: List[str]):
|
||||
"""Process es command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="es",
|
||||
description="""
|
||||
Provides Expected Shortfall (short: ES) of the selected portfolio.
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--mean",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="use_mean",
|
||||
help="If one should use the mean of the portfolios return",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--dist",
|
||||
"--distributions",
|
||||
dest="distributions",
|
||||
type=str,
|
||||
choices=self.distributions,
|
||||
default="normal",
|
||||
help="Distribution used for the calculations",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--percentile",
|
||||
action="store",
|
||||
dest="percentile",
|
||||
type=float,
|
||||
default=99.9,
|
||||
help="""
|
||||
Percentile used for ES calculations, for example input 99.9 equals a 99.9 Percent Expected Shortfall
|
||||
""",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
from gamestonk_terminal.common.quantitative_analysis import qa_view
|
||||
|
||||
if self.portfolio.empty:
|
||||
console.print("[red]No portfolio loaded.[/red]\n")
|
||||
return
|
||||
self.portfolio.generate_holdings_from_trades()
|
||||
qa_view.display_es(
|
||||
self.portfolio.returns,
|
||||
"Portfolio",
|
||||
ns_parser.use_mean,
|
||||
ns_parser.distributions,
|
||||
ns_parser.percentile / 100,
|
||||
True,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_rmr(self, other_args: List[str]):
|
||||
"""Process rmr command"""
|
||||
|
||||
@@ -449,6 +449,7 @@ class Portfolio:
|
||||
]
|
||||
self.benchmark_returns = self.benchmark.pct_change().dropna()
|
||||
|
||||
# pylint:disable=no-member
|
||||
@classmethod
|
||||
@log_start_end(log=logger)
|
||||
def from_csv(cls, csv_path: str):
|
||||
@@ -468,9 +469,12 @@ class Portfolio:
|
||||
trades = pd.read_csv(csv_path)
|
||||
# Convert the date to what pandas understands
|
||||
trades.Date = pd.to_datetime(trades.Date)
|
||||
# Sort by date to make more sense of trades
|
||||
trades = trades.sort_values(by="Date")
|
||||
# Build the portfolio object
|
||||
return cls(trades)
|
||||
|
||||
# pylint:enable=no-member
|
||||
@classmethod
|
||||
@log_start_end(log=logger)
|
||||
def from_custom_inputs_and_weights(
|
||||
|
||||
@@ -55,10 +55,12 @@ class QaController(StockBaseController):
|
||||
"unitroot",
|
||||
"capm",
|
||||
"var",
|
||||
"es",
|
||||
]
|
||||
|
||||
stock_interval = [1, 5, 15, 30, 60]
|
||||
stock_sources = ["yf", "av", "iex"]
|
||||
distributions = ["laplace", "student_t", "logistic", "normal"]
|
||||
PATH = "/stocks/qa/"
|
||||
|
||||
def __init__(
|
||||
@@ -130,7 +132,8 @@ class QaController(StockBaseController):
|
||||
skew rolling skewness of distribution of prices
|
||||
kurtosis rolling kurtosis of distribution of prices
|
||||
[info]Risk:[/info]
|
||||
var value at risk
|
||||
var display value at risk
|
||||
es display expected shortfall
|
||||
[info]Other:[/info]
|
||||
raw print raw data
|
||||
decompose decomposition in cyclic-trend, season, and residuals of prices
|
||||
@@ -754,6 +757,16 @@ class QaController(StockBaseController):
|
||||
If the VaR should be adjusted for skew and kurtosis (Cornish-Fisher-Expansion)
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--student",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="student_t",
|
||||
help="""
|
||||
If one should use the student-t distribution
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--percentile",
|
||||
@@ -767,10 +780,66 @@ class QaController(StockBaseController):
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
qa_view.display_var(
|
||||
if ns_parser.adjusted and ns_parser.student_t:
|
||||
console.print("Select the adjusted or the student_t parameter.\n")
|
||||
else:
|
||||
qa_view.display_var(
|
||||
self.stock,
|
||||
self.ticker,
|
||||
ns_parser.use_mean,
|
||||
ns_parser.adjusted,
|
||||
ns_parser.student_t,
|
||||
ns_parser.percentile / 100,
|
||||
False,
|
||||
)
|
||||
|
||||
def call_es(self, other_args: List[str]):
|
||||
"""Process es command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="es",
|
||||
description="""
|
||||
Provides Expected Shortfall (short: ES) 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(
|
||||
"-d",
|
||||
"--dist",
|
||||
"--distributions",
|
||||
dest="distributions",
|
||||
type=str,
|
||||
choices=self.distributions,
|
||||
default="normal",
|
||||
help="Distribution used for the calculations",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--percentile",
|
||||
action="store",
|
||||
dest="percentile",
|
||||
type=float,
|
||||
default=99.9,
|
||||
help="""
|
||||
Percentile used for ES calculations, for example input 99.9 equals a 99.9 Percent Expected Shortfall
|
||||
""",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
qa_view.display_es(
|
||||
self.stock,
|
||||
ns_parser.use_mean,
|
||||
self.ticker,
|
||||
ns_parser.adjusted,
|
||||
ns_parser.use_mean,
|
||||
ns_parser.distributions,
|
||||
ns_parser.percentile / 100,
|
||||
False,
|
||||
)
|
||||
|
||||
2
scripts/test_port.gst
vendored
2
scripts/test_port.gst
vendored
@@ -14,4 +14,6 @@ select aapl,msft,sbux,gme
|
||||
add ge,tsla
|
||||
equal
|
||||
mktcap
|
||||
es
|
||||
var
|
||||
exit
|
||||
|
||||
2
scripts/test_stocks_qa.gst
vendored
2
scripts/test_stocks_qa.gst
vendored
@@ -27,4 +27,6 @@ raw
|
||||
decompose
|
||||
cusum
|
||||
capm
|
||||
var
|
||||
es
|
||||
exit
|
||||
@@ -17,4 +17,7 @@ Graphs:
|
||||
rmr graph your returns versus the market's returns
|
||||
dd display portfolio drawdown
|
||||
al display allocation to given assets over period
|
||||
|
||||
|
||||
Risk Metrics:
|
||||
var display value at risk
|
||||
es display expected shortfall
|
||||
@@ -23,7 +23,8 @@ Rolling Metrics:
|
||||
skew rolling skewness of distribution of prices
|
||||
kurtosis rolling kurtosis of distribution of prices
|
||||
Risk:
|
||||
var value at risk
|
||||
var display value at risk
|
||||
es display expected shortfall
|
||||
Other:
|
||||
raw print raw data
|
||||
decompose decomposition in cyclic-trend, season, and residuals of prices
|
||||
|
||||
33
website/content/common/quantitative_analysis/es/_index.md
vendored
Normal file
33
website/content/common/quantitative_analysis/es/_index.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
```
|
||||
usage: es [-m] [-d {laplace,student_t,logistic,normal}] [-p PERCENTILE] [-h]
|
||||
```
|
||||
|
||||
Provides Expected Shortfall (short: ES) of the selected stock.
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-m, --mean If one should use the mean of the stocks return (default: False)
|
||||
-d {laplace,student_t,logistic,normal}, --dist {laplace,student_t,logistic,normal}, --distributions {laplace,student_t,logistic,normal}
|
||||
Distribution used for the calculations (default: normal)
|
||||
-p PERCENTILE, --percentile PERCENTILE
|
||||
Percentile used for ES calculations, for example input 99.9 equals a 99.9 Percent Expected
|
||||
Shortfall (default: 99.9)
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
2022 Feb 25, 06:50 (✨) /stocks/qa/ $ es
|
||||
TSLA Expected Shortfall
|
||||
┏━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
|
||||
┃ ┃ ES: ┃ Historical ES: ┃
|
||||
┡━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
|
||||
│ 90.0% │ -0.0752 │ -0.0705 │
|
||||
├───────┼─────────┼────────────────┤
|
||||
│ 95.0% │ -0.0885 │ -0.0932 │
|
||||
├───────┼─────────┼────────────────┤
|
||||
│ 99.0% │ -0.1144 │ -0.1561 │
|
||||
├───────┼─────────┼────────────────┤
|
||||
│ 99.9% │ -0.1444 │ -0.2106 │
|
||||
└───────┴─────────┴────────────────┘
|
||||
```
|
||||
@@ -1,15 +1,18 @@
|
||||
```
|
||||
usage: var [-m] [-a] [-p PERCENTILE] [-h]
|
||||
usage: var [-m] [-a] [-s] [-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
|
||||
-m, --mean If one should use the mean of the stocks return (default: False)
|
||||
-a, --adjusted If the VaR should be adjusted for skew and kurtosis (Cornish-Fisher-Expansion) (default:
|
||||
False)
|
||||
-s, --student If one should use the student-t distribution (default: False)
|
||||
-p PERCENTILE, --percentile PERCENTILE
|
||||
Percentile used for VaR calculations, for example input 99.9 equals a 99.9 Percent VaR
|
||||
(default: 99.9)
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
|
||||
|
||||
33
website/content/portfolio/es/_index.md
vendored
Normal file
33
website/content/portfolio/es/_index.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
```
|
||||
usage: es [-m] [-d {laplace,student_t,logistic,normal}] [-p PERCENTILE] [-h]
|
||||
```
|
||||
|
||||
Provides Expected Shortfall (short: ES) of the selected portfolio.
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-m, --mean If one should use the mean of the portfolios return (default: False)
|
||||
-d {laplace,student_t,logistic,normal}, --dist {laplace,student_t,logistic,normal}, --distributions {laplace,student_t,logistic,normal}
|
||||
Distribution used for the calculations (default: normal)
|
||||
-p PERCENTILE, --percentile PERCENTILE
|
||||
Percentile used for ES calculations, for example input 99.9 equals a 99.9 Percent Expected
|
||||
Shortfall (default: 99.9)
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
2022 Feb 25, 03:09 (✨) /portfolio/ $ es
|
||||
Portfolio Expected Shortfall
|
||||
┏━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
|
||||
┃ ┃ ES: ┃ Historical ES: ┃
|
||||
┡━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
|
||||
│ 90.0% │ -0.0204 │ -0.0202 │
|
||||
├───────┼─────────┼────────────────┤
|
||||
│ 95.0% │ -0.0240 │ -0.0242 │
|
||||
├───────┼─────────┼────────────────┤
|
||||
│ 99.0% │ -0.0310 │ -0.0270 │
|
||||
├───────┼─────────┼────────────────┤
|
||||
│ 99.9% │ -0.0391 │ -0.0277 │
|
||||
└───────┴─────────┴────────────────┘
|
||||
```
|
||||
32
website/content/portfolio/var/_index.md
vendored
Normal file
32
website/content/portfolio/var/_index.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
```
|
||||
usage: var [-m] [-a] [-s] [-p PERCENTILE] [-h]
|
||||
```
|
||||
|
||||
Provides value at risk (short: VaR) of the selected portfolio.
|
||||
```
|
||||
optional arguments:
|
||||
-m, --mean If one should use the mean of the portfolio return (default: False)
|
||||
-a, --adjusted If the VaR should be adjusted for skew and kurtosis (Cornish-Fisher-Expansion) (default:
|
||||
False)
|
||||
-s, --student If one should use the student-t distribution (default: False)
|
||||
-p PERCENTILE, --percentile PERCENTILE
|
||||
Percentile used for VaR calculations, for example input 99.9 equals a 99.9 Percent VaR
|
||||
(default: 99.9)
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
Example:
|
||||
```
|
||||
2022 Feb 25, 03:09 (✨) /portfolio/ $ var
|
||||
Portfolio Value at Risk
|
||||
┏━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
|
||||
┃ ┃ VaR: ┃ Historical VaR: ┃
|
||||
┡━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
|
||||
│ 90.0% │ -0.0148 │ -0.0135 │
|
||||
├───────┼─────────┼─────────────────┤
|
||||
│ 95.0% │ -0.0189 │ -0.0197 │
|
||||
├───────┼─────────┼─────────────────┤
|
||||
│ 99.0% │ -0.0267 │ -0.0258 │
|
||||
├───────┼─────────┼─────────────────┤
|
||||
│ 99.9% │ -0.0353 │ -0.0276 │
|
||||
└───────┴─────────┴─────────────────┘
|
||||
```
|
||||
Reference in New Issue
Block a user