New-Portfolio-Optimization-Menu

This commit is contained in:
Dany
2022-04-13 01:48:40 -05:00
parent def003d50f
commit b4b7169cfb
14 changed files with 13746 additions and 20 deletions

View File

@@ -9,11 +9,15 @@ from typing import List, Tuple
import numpy as np
import pandas as pd
import yfinance as yf
import riskfolio as rp
from openbb_terminal.decorators import log_start_end
from openbb_terminal.portfolio.portfolio_optimization import yahoo_finance_model
from openbb_terminal.rich_config import console
from openbb_terminal.portfolio.portfolio_optimization.riskfolio import (
Portfolio,
HCPortfolio,
AuxFunctions,
)
logger = logging.getLogger(__name__)
@@ -330,7 +334,7 @@ def get_mean_risk_portfolio(
risk_free_rate = risk_free_rate / time_factor[freq.upper()]
# Building the portfolio object
port = rp.Portfolio(returns=stock_returns, alpha=alpha)
port = Portfolio.Portfolio(returns=stock_returns, alpha=alpha)
# Estimate input parameters:
port.assets_stats(method_mu=mean, method_cov=covariance, d=d_ewma)
@@ -462,7 +466,7 @@ def get_max_diversification_portfolio(
)
# Building the portfolio object
port = rp.Portfolio(returns=stock_returns)
port = Portfolio.Portfolio(returns=stock_returns)
# Estimate input parameters:
port.assets_stats(method_mu="hist", method_cov=covariance, d=d_ewma)
@@ -572,11 +576,11 @@ def get_max_decorrelation_portfolio(
)
# Building the portfolio object
port = rp.Portfolio(returns=stock_returns)
port = Portfolio.Portfolio(returns=stock_returns)
# Estimate input parameters:
port.assets_stats(method_mu="hist", method_cov=covariance, d=d_ewma)
port.cov = rp.cov2corr(port.cov)
port.cov = AuxFunctions.cov2corr(port.cov)
# Budget constraints
port.upperlng = value
@@ -726,7 +730,7 @@ def get_risk_parity_portfolio(
risk_free_rate = risk_free_rate / time_factor[freq.upper()]
# Building the portfolio object
port = rp.Portfolio(returns=stock_returns, alpha=alpha)
port = Portfolio.Portfolio(returns=stock_returns, alpha=alpha)
# Calculating optimal portfolio
port.assets_stats(method_mu=mean, method_cov=covariance, d=d_ewma)
@@ -872,7 +876,7 @@ def get_rel_risk_parity_portfolio(
)
# Building the portfolio object
port = rp.Portfolio(returns=stock_returns)
port = Portfolio.Portfolio(returns=stock_returns)
# Calculating optimal portfolio
port.assets_stats(method_mu=mean, method_cov=covariance, d=d_ewma)
@@ -1129,7 +1133,7 @@ def get_hcp_portfolio(
risk_free_rate = risk_free_rate / time_factor[freq.upper()]
# Building the portfolio object
port = rp.HCPortfolio(
port = HCPortfolio.HCPortfolio(
returns=stock_returns,
alpha=alpha,
a_sim=a_sim,

View File

@@ -12,7 +12,6 @@ from datetime import date
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import riskfolio as rp
from matplotlib.lines import Line2D
from matplotlib.gridspec import GridSpec
@@ -26,6 +25,12 @@ from openbb_terminal.portfolio.portfolio_optimization import (
optimizer_model,
yahoo_finance_model,
)
from openbb_terminal.portfolio.portfolio_optimization.riskfolio import (
Portfolio,
PlotFunctions,
RiskFunctions,
)
from openbb_terminal.rich_config import console
from openbb_terminal.config_terminal import theme
@@ -105,6 +110,7 @@ time_factor = {
"M": 12.0,
}
@log_start_end(log=logger)
def d_period(period: str, start: str, end: str):
"""
@@ -149,6 +155,7 @@ def d_period(period: str, start: str, end: str):
return p
@log_start_end(log=logger)
def portfolio_performance(
weights: dict,
@@ -236,7 +243,7 @@ def portfolio_performance(
print(f"Sharpe ratio: {sharpe:.4f}")
if risk_measure != "MV":
risk = rp.Sharpe_Risk(
risk = RiskFunctions.Sharpe_Risk(
weights,
cov=stock_returns.cov(),
returns=stock_returns,
@@ -285,6 +292,7 @@ def portfolio_performance(
"Return / " + risk_names[risk_measure.lower()] + f" ratio: {sharpe_2:.4f}"
)
@log_start_end(log=logger)
def display_weights(weights: dict, market_neutral: bool = False):
"""
@@ -320,6 +328,7 @@ def display_weights(weights: dict, market_neutral: bool = False):
header = "Value ($)" if tot_value > 1.01 else "Value (%)"
print_rich_table(weight_df, headers=[header], show_index=True, title="Weights")
@log_start_end(log=logger)
def display_equal_weight(
stocks: List[str],
@@ -425,6 +434,7 @@ def display_equal_weight(
console.print("")
return weights
@log_start_end(log=logger)
def display_property_weighting(
stocks: List[str],
@@ -533,6 +543,7 @@ def display_property_weighting(
console.print("")
return weights
@log_start_end(log=logger)
def display_mean_risk(
stocks: List[str],
@@ -719,6 +730,7 @@ def display_mean_risk(
console.print("")
return weights
@log_start_end(log=logger)
def display_max_sharpe(
stocks: List[str],
@@ -860,6 +872,7 @@ def display_max_sharpe(
)
return weights
@log_start_end(log=logger)
def display_min_risk(
stocks: List[str],
@@ -1001,6 +1014,7 @@ def display_min_risk(
)
return weights
@log_start_end(log=logger)
def display_max_util(
stocks: List[str],
@@ -1147,6 +1161,7 @@ def display_max_util(
)
return weights
@log_start_end(log=logger)
def display_max_ret(
stocks: List[str],
@@ -1288,6 +1303,7 @@ def display_max_ret(
)
return weights
@log_start_end(log=logger)
def display_max_div(
stocks: List[str],
@@ -1400,6 +1416,7 @@ def display_max_div(
console.print("")
return weights
@log_start_end(log=logger)
def display_max_decorr(
stocks: List[str],
@@ -1513,6 +1530,7 @@ def display_max_decorr(
console.print("")
return weights
@log_start_end(log=logger)
def display_ef(
stocks: List[str],
@@ -1616,7 +1634,7 @@ def display_ef(
risk_free_rate = risk_free_rate / time_factor[freq.upper()]
# Building the portfolio object
port = rp.Portfolio(returns=stock_returns, alpha=alpha)
port = Portfolio.Portfolio(returns=stock_returns, alpha=alpha)
# Estimate input parameters:
port.assets_stats(method_mu="hist", method_cov="hist")
@@ -1662,7 +1680,7 @@ def display_ef(
for i in range(frontier.shape[1]):
w = np.array(frontier.iloc[:, i], ndmin=2).T
risk = rp.Sharpe_Risk(
risk = RiskFunctions.Sharpe_Risk(
w,
cov=cov,
returns=stock_returns,
@@ -1687,7 +1705,7 @@ def display_ef(
ax = external_axes[0]
frontier = pd.concat([frontier, random_weights], axis=1)
ax = rp.plot_frontier(
ax = PlotFunctions.plot_frontier(
w_frontier=frontier,
mu=mu,
cov=cov,
@@ -1708,7 +1726,7 @@ def display_ef(
# Add risk free line
if tangency:
ret_sharpe = (mu @ weights).to_numpy().item() * time_factor[freq.upper()]
risk_sharpe = rp.Sharpe_Risk(
risk_sharpe = RiskFunctions.Sharpe_Risk(
weights,
cov=cov,
returns=stock_returns,
@@ -1745,6 +1763,7 @@ def display_ef(
if external_axes is None:
theme.visualize_output(force_tight_layout=False)
@log_start_end(log=logger)
def display_risk_parity(
stocks: List[str],
@@ -1898,6 +1917,7 @@ def display_risk_parity(
console.print("")
return weights
@log_start_end(log=logger)
def display_rel_risk_parity(
stocks: List[str],
@@ -2038,6 +2058,7 @@ def display_rel_risk_parity(
console.print("")
return weights
@log_start_end(log=logger)
def display_hcp(
stocks: List[str],
@@ -2311,6 +2332,7 @@ def display_hcp(
console.print("")
return weights
@log_start_end(log=logger)
def display_hrp(
stocks: List[str],
@@ -2528,6 +2550,7 @@ def display_hrp(
)
return weights
@log_start_end(log=logger)
def display_herc(
stocks: List[str],
@@ -2753,6 +2776,7 @@ def display_herc(
)
return weights
@log_start_end(log=logger)
def display_nco(
stocks: List[str],
@@ -3092,6 +3116,7 @@ def pie_chart_weights(
if external_axes is None:
theme.visualize_output()
@log_start_end(log=logger)
def additional_plots(
weights: Dict,
@@ -3196,7 +3221,7 @@ def additional_plots(
else:
ax = external_axes[0]
ax = rp.plot_hist(
ax = PlotFunctions.plot_hist(
stock_returns, w=pd.Series(weights).to_frame(), alpha=alpha, ax=ax
)
ax.legend(fontsize="x-small", loc="best")
@@ -3215,7 +3240,7 @@ def additional_plots(
ax = external_axes[0]
nav = stock_returns.cumsum()
ax = rp.plot_drawdown(
ax = PlotFunctions.plot_drawdown(
nav=nav, w=pd.Series(weights).to_frame(), alpha=alpha, ax=ax
)
@@ -3239,7 +3264,7 @@ def additional_plots(
else:
ax = external_axes[0]
ax = rp.plot_risk_con(
ax = PlotFunctions.plot_risk_con(
w=pd.Series(weights).to_frame(),
cov=stock_returns.cov(),
returns=stock_returns,
@@ -3267,7 +3292,7 @@ def additional_plots(
else:
ax = external_axes[0]
ax = rp.plot_clusters(
ax = PlotFunctions.plot_clusters(
returns=stock_returns,
codependence="pearson",
linkage="ward",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,964 @@
"""""" #
"""
Copyright (c) 2020-2022, Dany Cajas
All rights reserved.
This work is licensed under BSD 3-Clause "New" or "Revised" License.
License available at https://github.com/dcajasn/Riskfolio-Lib/blob/master/LICENSE.txt
"""
import numpy as np
import pandas as pd
import scipy.cluster.hierarchy as hr
from scipy.spatial.distance import squareform
import openbb_terminal.portfolio.portfolio_optimization.riskfolio.AuxFunctions as af
import openbb_terminal.portfolio.portfolio_optimization.riskfolio.DBHT as db
def assets_constraints(constraints, asset_classes):
r"""
Create the linear constraints matrixes A and B of the constraint
:math:`Aw \geq B`.
Parameters
----------
constraints : DataFrame of shape (n_constraints, n_fields)
Constraints matrix, where n_constraints is the number of constraints
and n_fields is the number of fields of constraints matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Type: (str) can be: 'Assets', 'Classes', 'All Assets', 'Each asset in a class' and 'All Classes'.
- Set: (str) if Type is 'Classes', 'Each asset in a class' or 'All Classes'specified the name of the asset's classes set.
- Position: (str) the name of the asset or asset class of the constraint.
- Sign: (str) can be '>=' or '<='.
- Weight: (scalar) is the maximum or minimum weight of the absolute constraint.
- Type Relative: (str) can be: 'Assets' or 'Classes'.
- Relative Set: (str) if Type Relative is 'Classes' specified the name of the set of asset classes.
- Relative: (str) the name of the asset or asset class of the relative constraint.
- Factor: (scalar) is the factor of the relative constraint.
asset_classes : DataFrame of shape (n_assets, n_cols)
Asset's classes matrix, where n_assets is the number of assets and
n_cols is the number of columns of the matrix where the first column
is the asset list and the next columns are the different asset's
classes sets.
Returns
-------
A : nd-array
The matrix A of :math:`Aw \geq B`.
B : nd-array
The matrix B of :math:`Aw \geq B`.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
import riskfolio as rp
asset_classes = {'Assets': ['FB', 'GOOGL', 'NTFX', 'BAC', 'WFC', 'TLT', 'SHV'],
'Class 1': ['Equity', 'Equity', 'Equity', 'Equity', 'Equity',
'Fixed Income', 'Fixed Income'],
'Class 2': ['Technology', 'Technology', 'Technology',
'Financial', 'Financial', 'Treasury', 'Treasury'],}
asset_classes = pd.DataFrame(asset_classes)
asset_classes = asset_classes.sort_values(by=['Assets'])
constraints = {'Disabled': [False, False, False, False, False, False, False],
'Type': ['Classes', 'Classes', 'Assets', 'Assets', 'Classes',
'All Assets', 'Each asset in a class'],
'Set': ['Class 1', 'Class 1', '', '', 'Class 2', '', 'Class 1'],
'Position': ['Equity', 'Fixed Income', 'BAC', 'WFC', 'Financial',
'', 'Equity'],
'Sign': ['<=', '<=', '<=', '<=', '>=', '>=', '>='],
'Weight': [0.6, 0.5, 0.1, '', '', 0.02, ''],
'Type Relative': ['', '', '', 'Assets', 'Classes', '', 'Assets'],
'Relative Set': ['', '', '', '', 'Class 1', '', ''],
'Relative': ['', '', '', 'FB', 'Fixed Income', '', 'TLT'],
'Factor': ['', '', '', 1.2, 0.5, '', 0.4]}
constraints = pd.DataFrame(constraints)
The constraint looks like this:
.. image:: images/Constraints.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the matrixes A and B we use the following command:
::
A, B = rp.assets_constraints(constraints, asset_classes)
The matrixes A and B looks like this (all constraints were converted to a linear
constraint):
.. image:: images/AxB.png
"""
if not isinstance(constraints, pd.DataFrame) and not isinstance(
asset_classes, pd.DataFrame
):
raise ValueError("constraints and asset_classes must be DataFrames")
if constraints.shape[1] != 10:
raise ValueError("constraints must have ten columns")
n = len(constraints)
m = len(asset_classes)
data = constraints.fillna("")
data = data.values.tolist()
assetslist = asset_classes.iloc[:, 0].values.tolist()
A = []
B = []
for i in range(0, n):
if data[i][0] == False:
if data[i][1] == "Assets":
item = assetslist.index(data[i][3])
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = [0] * m
A1[item] = d
A.append(A1)
B.append([data[i][5] * d])
else:
A1 = [0] * m
A1[item] = 1
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = [0] * m
A2[item2] = 1
elif data[i][6] == "Classes":
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A1 = ((np.array(A1) + np.array(A2) * data[i][9] * -1) * d).tolist()
A.append(A1)
B.append([0])
elif data[i][1] == "All Assets":
item = len(assetslist)
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = np.identity(item) * d
A1 = A1.tolist()
B1 = np.ones((item, 1)) * d * data[i][5]
for i in range(0, item):
A.append(A1[i])
B.append(B1.tolist()[0])
else:
A1 = np.identity(item)
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = np.zeros((item, item - 1))
A2 = np.insert(A2, item2 - 1, 1, axis=1)
elif data[i][6] == "Classes":
A1 = np.identity(item)
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A2 = np.ones((item, item)) * np.array(A2)
A1 = ((np.array(A1) + np.array(A2) * data[i][9] * -1) * d).tolist()
for i in range(0, item):
A.append(A1[i])
B.append([0])
elif data[i][1] == "Classes":
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
A1 = np.array(A1) * d
A1 = A1.tolist()
A.append(A1)
B.append([data[i][5] * d])
else:
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = [0] * m
A2[item2] = 1
elif data[i][6] == "Classes":
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A1 = ((np.array(A1) + np.array(A2) * data[i][9] * -1) * d).tolist()
A.append(A1)
B.append([0])
elif data[i][1] == "Each asset in a class":
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
l = 0
for k in A1:
if k == 1:
A3 = [0] * m
A3[l] = 1 * d
A.append(A3)
B.append([data[i][5] * d])
l = l + 1
else:
A1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
l = 0
for k in A1:
if k == 1:
A3 = [0] * m
A3[l] = 1
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = [0] * m
A2[item2] = 1
elif data[i][6] == "Classes":
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A3 = (
(np.array(A3) + np.array(A2) * data[i][9] * -1) * d
).tolist()
A.append(A3)
B.append([0])
l = l + 1
elif data[i][1] == "All Classes":
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
for k in np.unique(asset_classes[data[i][2]].values):
A1 = np.where(asset_classes[data[i][2]].values == k, 1, 0) * d
A1 = A1.tolist()
A.append(A1)
B.append([data[i][5] * d])
else:
for k in np.unique(asset_classes[data[i][2]].values):
A1 = np.where(asset_classes[data[i][2]].values == k, 1, 0)
if data[i][6] == "Assets":
item2 = assetslist.index(data[i][8])
A2 = [0] * m
A2[item2] = 1
elif data[i][6] == "Classes":
A2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
A3 = (
(np.array(A1) + np.array(A2) * data[i][9] * -1) * d
).tolist()
A.append(A3)
B.append([0])
A = np.array(A, ndmin=2)
B = np.array(B, ndmin=2)
return A, B
def factors_constraints(constraints, loadings):
r"""
Create the factors constraints matrixes C and D of the constraint
:math:`Cw \geq D`.
Parameters
----------
constraints : DataFrame of shape (n_constraints, n_fields)
Constraints matrix, where n_constraints is the number of constraints
and n_fields is the number of fields of constraints matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Factor: (str) the name of the factor of the constraint.
- Sign: (str) can be '>=' or '<='.
- Value: (scalar) is the maximum or minimum value of the factor.
loadings : DataFrame of shape (n_assets, n_features)
The loadings matrix.
Returns
-------
C : nd-array
The matrix C of :math:`Cw \geq D`.
D : nd-array
The matrix D of :math:`Cw \geq D`.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
loadings = {'const': [0.0004, 0.0002, 0.0000, 0.0006, 0.0001, 0.0003, -0.0003],
'MTUM': [0.1916, 1.0061, 0.8695, 1.9996, 0.0000, 0.0000, 0.0000],
'QUAL': [0.0000, 2.0129, 1.4301, 0.0000, 0.0000, 0.0000, 0.0000],
'SIZE': [0.0000, 0.0000, 0.0000, 0.4717, 0.0000, -0.1857, 0.0000],
'USMV': [-0.7838, -1.6439, -1.0176, -1.4407, 0.0055, 0.5781, 0.0000],
'VLUE': [1.4772, -0.7590, -0.4090, 0.0000, -0.0054, -0.4844, 0.9435]}
loadings = pd.DataFrame(loadings)
constraints = {'Disabled': [False, False, False],
'Factor': ['MTUM', 'USMV', 'VLUE'],
'Sign': ['<=', '<=', '>='],
'Value': [0.9, -1.2, 0.3],
'Relative Factor': ['USMV', '', '']}
constraints = pd.DataFrame(constraints)
The constraint looks like this:
.. image:: images/Constraints2.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the matrixes C and D we use the following command:
::
C, D = rp.factors_constraints(constraints, loadings)
The matrixes C and D looks like this (all constraints were converted to a linear
constraint):
.. image:: images/CxD.png
"""
if not isinstance(constraints, pd.DataFrame) and not isinstance(
loadings, pd.DataFrame
):
raise ValueError("constraints and loadings must be DataFrames")
if constraints.shape[1] != 5:
raise ValueError("constraints must have five columns")
n = len(constraints)
data = constraints.fillna("")
data = data.values.tolist()
C = []
D = []
for i in range(0, n):
if data[i][0] == False:
if data[i][2] == ">=":
d = 1
elif data[i][2] == "<=":
d = -1
C1 = loadings[data[i][1]].values
if data[i][4] != "":
C2 = loadings[data[i][4]].values
C1 = C1 - C2
C.append(C1 * d)
D.append([data[i][3] * d])
C = np.array(C, ndmin=2)
D = np.array(D, ndmin=2)
return C, D
def assets_views(views, asset_classes):
r"""
Create the assets views matrixes P and Q of the views :math:`Pw = Q`.
Parameters
----------
views : DataFrame of shape (n_views, n_fields)
Constraints matrix, where n_views is the number of views
and n_fields is the number of fields of views matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Type: (str) can be: 'Assets' or 'Classes'.
- Set: (str) if Type is 'Classes' specified the name of the set of asset classes.
- Position: (str) the name of the asset or asset class of the view.
- Sign: (str) can be '>=' or '<='.
- Return: (scalar) is the return of the view.
- Type Relative: (str) can be: 'Assets' or 'Classes'.
- Relative Set: (str) if Type Relative is 'Classes' specified the name of the set of asset classes.
- Relative: (str) the name of the asset or asset class of the relative view.
asset_classes : DataFrame of shape (n_assets, n_cols)
Asset's classes matrix, where n_assets is the number of assets and
n_cols is the number of columns of the matrix where the first column
is the asset list and the next columns are the different asset's
classes sets.
Returns
-------
P : nd-array
The matrix P that shows the relation among assets in each view.
Q : nd-array
The matrix Q that shows the expected return of each view.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
asset_classes = {'Assets': ['FB', 'GOOGL', 'NTFX', 'BAC', 'WFC', 'TLT', 'SHV'],
'Class 1': ['Equity', 'Equity', 'Equity', 'Equity', 'Equity',
'Fixed Income', 'Fixed Income'],
'Class 2': ['Technology', 'Technology', 'Technology',
'Financial', 'Financial', 'Treasury', 'Treasury'],}
asset_classes = pd.DataFrame(asset_classes)
asset_classes = asset_classes.sort_values(by=['Assets'])
views = {'Disabled': [False, False, False, False],
'Type': ['Assets', 'Classes', 'Classes', 'Assets'],
'Set': ['', 'Class 2','Class 1', ''],
'Position': ['WFC', 'Financial', 'Equity', 'FB'],
'Sign': ['<=', '>=', '>=', '>='],
'Return': [ 0.3, 0.1, 0.05, 0.03 ],
'Type Relative': [ 'Assets', 'Classes', 'Assets', ''],
'Relative Set': [ '', 'Class 1', '', ''],
'Relative': ['FB', 'Fixed Income', 'TLT', '']}
views = pd.DataFrame(views)
The constraint looks like this:
.. image:: images/Views.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the matrixes P and Q we use the following command:
::
P, Q = rp.assets_views(views, asset_classes)
The matrixes P and Q looks like this:
.. image:: images/PxQ.png
"""
if not isinstance(views, pd.DataFrame) and not isinstance(
asset_classes, pd.DataFrame
):
raise ValueError("constraints and asset_classes must be DataFrames")
if views.shape[1] != 9:
raise ValueError("constraints must have nine columns")
n = len(views)
m = len(asset_classes)
data = views.fillna("")
data = data.values.tolist()
assetslist = asset_classes.iloc[:, 0].values.tolist()
P = []
Q = []
for i in range(0, n):
valid = False
if data[i][0] == False:
if data[i][1] == "Assets":
item = assetslist.index(data[i][3])
if data[i][4] == ">=":
d = 1
elif data[i][4] == "<=":
d = -1
if data[i][5] != "":
P1 = [0] * m
P1[item] = 1
if data[i][6] == "Assets" and data[i][8] != "":
item2 = assetslist.index(data[i][8])
P2 = [0] * m
P2[item2] = 1
valid = True
elif (
data[i][6] == "Classes"
and data[i][7] != ""
and data[i][8] != ""
):
P2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
P2 = P2 / np.sum(P2)
valid = True
elif data[i][6] == "" and data[i][7] == "" and data[i][8] == "":
P2 = [0] * m
valid = True
if valid == True:
P1 = ((np.array(P1) - np.array(P2)) * d).tolist()
P.append(P1)
Q.append([data[i][5] * d])
elif data[i][1] == "Classes":
if data[i][4] == ">=":
d = 1
else:
d = -1
if data[i][5] != "":
P1 = np.where(asset_classes[data[i][2]].values == data[i][3], 1, 0)
P1 = P1 / np.sum(P1)
if data[i][6] == "Assets" and data[i][8] != "":
item2 = assetslist.index(data[i][8])
P2 = [0] * m
P2[item2] = 1
valid = True
elif (
data[i][6] == "Classes"
and data[i][7] != ""
and data[i][8] != ""
):
P2 = np.where(
asset_classes[data[i][7]].values == data[i][8], 1, 0
)
P2 = P2 / np.sum(P2)
valid = True
elif data[i][6] == "" and data[i][7] == "" and data[i][8] == "":
P2 = [0] * m
valid = True
if valid == True:
P1 = ((np.array(P1) - np.array(P2)) * d).tolist()
P.append(P1)
Q.append([data[i][5] * d])
P = np.array(P, ndmin=2)
Q = np.array(Q, ndmin=2)
for i in range(len(Q)):
if Q[i, 0] < 0:
P[i, :] = -1 * P[i, :]
Q[i, :] = -1 * Q[i, :]
return P, Q
def factors_views(views, loadings, const=True):
r"""
Create the factors constraints matrixes C and D of the constraint
:math:`Cw \geq D`.
Parameters
----------
constraints : DataFrame of shape (n_constraints, n_fields)
Constraints matrix, where n_constraints is the number of constraints
and n_fields is the number of fields of constraints matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Factor: (str) the name of the factor of the constraint.
- Sign: (str) can be '>=' or '<='.
- Value: (scalar) is the maximum or minimum value of the factor.
loadings : DataFrame of shape (n_assets, n_features)
The loadings matrix.
Returns
-------
P : nd-array
The matrix P that shows the relation among factors in each factor view.
Q : nd-array
The matrix Q that shows the expected return of each factor view.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
loadings = {'const': [0.0004, 0.0002, 0.0000, 0.0006, 0.0001, 0.0003, -0.0003],
'MTUM': [0.1916, 1.0061, 0.8695, 1.9996, 0.0000, 0.0000, 0.0000],
'QUAL': [0.0000, 2.0129, 1.4301, 0.0000, 0.0000, 0.0000, 0.0000],
'SIZE': [0.0000, 0.0000, 0.0000, 0.4717, 0.0000, -0.1857, 0.0000],
'USMV': [-0.7838, -1.6439, -1.0176, -1.4407, 0.0055, 0.5781, 0.0000],
'VLUE': [1.4772, -0.7590, -0.4090, 0.0000, -0.0054, -0.4844, 0.9435]}
loadings = pd.DataFrame(loadings)
factorsviews = {'Disabled': [False, False, False],
'Factor': ['MTUM', 'USMV', 'VLUE'],
'Sign': ['<=', '<=', '>='],
'Value': [0.9, -1.2, 0.3],
'Relative Factor': ['USMV', '', '']}
factorsviews = pd.DataFrame(factorsviews)
The constraint looks like this:
.. image:: images/factorsviews.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the matrixes P and Q we use the following command:
::
P, Q = rp.factors_views(factorsviews, loadings, const=True)
The matrixes P and Q looks like this:
.. image:: images/P_fxQ_f.png
"""
if not isinstance(views, pd.DataFrame) and not isinstance(loadings, pd.DataFrame):
raise ValueError("constraints and loadings must be DataFrames")
if views.shape[1] != 5:
raise ValueError("constraints must have five columns")
n = len(views)
data = views.fillna("")
data = data.values.tolist()
factorslist = loadings.columns.tolist()
if const == True:
factorslist = factorslist[1:]
m = len(factorslist)
P = []
Q = []
for i in range(0, n):
if data[i][0] == False:
item = factorslist.index(data[i][1])
if data[i][2] == ">=":
d = 1
elif data[i][2] == "<=":
d = -1
P1 = [0] * m
P1[item] = d
if data[i][4] != "":
item = factorslist.index(data[i][4])
P1[item] = -d
P.append(P1)
Q.append([data[i][3] * d])
P = np.array(P, ndmin=2)
Q = np.array(Q, ndmin=2)
return P, Q
def assets_clusters(
returns,
codependence="pearson",
linkage="ward",
k=None,
max_k=10,
bins_info="KN",
alpha_tail=0.05,
leaf_order=True,
):
r"""
Create asset classes based on hierarchical clustering.
Parameters
----------
returns : DataFrame
Assets returns.
codependence : str, can be {'pearson', 'spearman', 'abs_pearson', 'abs_spearman', 'distance', 'mutual_info' or 'tail'}
The codependence or similarity matrix used to build the distance
metric and clusters. The default is 'pearson'. Posible values are:
- 'pearson': pearson correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{0.5(1-\rho^{pearson}_{i,j})}`.
- 'spearman': spearman correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{0.5(1-\rho^{spearman}_{i,j})}`.
- 'abs_pearson': absolute value pearson correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{(1-|\rho^{pearson}_{i,j}|)}`.
- 'abs_spearman': absolute value spearman correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{(1-|\rho^{spearman}_{i,j}|)}`.
- 'distance': distance correlation matrix. Distance formula :math:`D_{i,j} = \sqrt{(1-\rho^{distance}_{i,j})}`.
- 'mutual_info': mutual information matrix. Distance used is variation information matrix.
- 'tail': lower tail dependence index matrix. Dissimilarity formula :math:`D_{i,j} = -\log{\lambda_{i,j}}`.
linkage : string, optional
Linkage method of hierarchical clustering, see `linkage <https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html?highlight=linkage#scipy.cluster.hierarchy.linkage>`_ for more details.
The default is 'ward'. Posible values are:
- 'single'.
- 'complete'.
- 'average'.
- 'weighted'.
- 'centroid'.
- 'median'.
- 'ward'.
- 'DBHT'. Direct Bubble Hierarchical Tree.
k : int, optional
Number of clusters. This value is took instead of the optimal number
of clusters calculated with the two difference gap statistic.
The default is None.
max_k : int, optional
Max number of clusters used by the two difference gap statistic
to find the optimal number of clusters. The default is 10.
bins_info: int or str
Number of bins used to calculate variation of information. The default
value is 'KN'. Posible values are:
- 'KN': Knuth's choice method. See more in `knuth_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.knuth_bin_width.html>`_.
- 'FD': Freedman–Diaconis' choice method. See more in `freedman_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.freedman_bin_width.html>`_.
- 'SC': Scotts' choice method. See more in `scott_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.scott_bin_width.html>`_.
- 'HGR': Hacine-Gharbi and Ravier' choice method.
- int: integer value choice by user.
alpha_tail : float, optional
Significance level for lower tail dependence index. The default is 0.05.
leaf_order : bool, optional
Indicates if the cluster are ordered so that the distance between
successive leaves is minimal. The default is True.
Returns
-------
clusters : DataFrame
A dataframe with asset classes based on hierarchical clustering.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
clusters = rp.assets_clusters(returns, codependence='pearson',
linkage='ward', k=None, max_k=10,
alpha_tail=0.05, leaf_order=True)
The clusters dataframe looks like this:
.. image:: images/clusters_df.png
"""
if not isinstance(returns, pd.DataFrame):
raise ValueError("returns must be a DataFrame")
# Calculating codependence matrix and distance metric
if codependence in {"pearson", "spearman"}:
codep = returns.corr(method=codependence)
dist = np.sqrt(np.clip((1 - codep) / 2, a_min=0.0, a_max=1.0))
elif codependence in {"abs_pearson", "abs_spearman"}:
codep = np.abs(returns.corr(method=codependence[4:]))
dist = np.sqrt(np.clip((1 - codep), a_min=0.0, a_max=1.0))
elif codependence in {"distance"}:
codep = af.dcorr_matrix(returns).astype(float)
dist = np.sqrt(np.clip((1 - codep), a_min=0.0, a_max=1.0))
elif codependence in {"mutual_info"}:
codep = af.mutual_info_matrix(returns, bins_info).astype(float)
dist = af.var_info_matrix(returns, bins_info).astype(float)
elif codependence in {"tail"}:
codep = af.ltdi_matrix(returns, alpha_tail).astype(float)
dist = -np.log(codep)
# Hierarchcial clustering
dist = dist.to_numpy()
dist = pd.DataFrame(dist, columns=codep.columns, index=codep.index)
if linkage == "DBHT":
# different choices for D, S give different outputs!
D = dist.to_numpy() # dissimilatity matrix
if codependence in {"pearson", "spearman"}:
S = (1 - dist**2).to_numpy()
else:
S = codep.copy().to_numpy() # similarity matrix
(_, _, _, _, _, clustering) = db.DBHTs(
D, S, leaf_order=leaf_order
) # DBHT clustering
else:
p_dist = squareform(dist, checks=False)
clustering = hr.linkage(p_dist, method=linkage, optimal_ordering=leaf_order)
# Optimal number of clusters
if k is None:
k = af.two_diff_gap_stat(codep, dist, clustering, max_k)
# Building clusters
clusters_inds = hr.fcluster(clustering, k, criterion="maxclust")
labels = np.array(returns.columns.tolist())
clusters = {"Assets": [], "Clusters": []}
for i, v in enumerate(clusters_inds):
clusters["Assets"].append(labels[i])
clusters["Clusters"].append("Cluster " + str(v))
clusters = pd.DataFrame(clusters)
clusters = clusters.sort_values(by=["Assets"])
return clusters
def hrp_constraints(constraints, asset_classes):
r"""
Create the upper and lower bounds constraints for hierarchical risk parity
model.
Parameters
----------
constraints : DataFrame of shape (n_constraints, n_fields)
Constraints matrix, where n_constraints is the number of constraints
and n_fields is the number of fields of constraints matrix, the fields
are:
- Disabled: (bool) indicates if the constraint is enable.
- Type: (str) can be: 'Assets', All Assets' and 'Each asset in a class'.
- Position: (str) the name of the asset or asset class of the constraint.
- Sign: (str) can be '>=' or '<='.
- Weight: (scalar) is the maximum or minimum weight of the absolute constraint.
asset_classes : DataFrame of shape (n_assets, n_cols)
Asset's classes matrix, where n_assets is the number of assets and
n_cols is the number of columns of the matrix where the first column
is the asset list and the next columns are the different asset's
classes sets.
Returns
-------
w_max : pd.Series
The upper bound of hierarchical risk parity weights constraints.
w_min : pd.Series
The lower bound of hierarchical risk parity weights constraints.
Raises
------
ValueError when the value cannot be calculated.
Examples
--------
::
asset_classes = {'Assets': ['FB', 'GOOGL', 'NTFX', 'BAC', 'WFC', 'TLT', 'SHV'],
'Class 1': ['Equity', 'Equity', 'Equity', 'Equity', 'Equity',
'Fixed Income', 'Fixed Income'],
'Class 2': ['Technology', 'Technology', 'Technology',
'Financial', 'Financial', 'Treasury', 'Treasury'],}
asset_classes = pd.DataFrame(asset_classes)
asset_classes = asset_classes.sort_values(by=['Assets'])
constraints = {'Disabled': [False, False, False, False, False, False],
'Type': ['Assets', 'Assets', 'All Assets', 'All Assets',
'Each asset in a class', 'Each asset in a class'],
'Set': ['', '', '', '','Class 1', 'Class 2'],
'Position': ['BAC', 'FB', '', '', 'Equity', 'Treasury'],
'Sign': ['>=', '<=', '<=', '>=', '<=', '<='],
'Weight': [0.02, 0.085, 0.09, 0.01, 0.07, 0.06]}
constraints = pd.DataFrame(constraints)
The constraint looks like this:
.. image:: images/HRPConstraints.png
It is easier to construct the constraints in excel and then upload to a
dataframe.
To create the pd.Series w_max and w_min we use the following command:
::
w_max, w_min = rp.hrp_constraints(constraints, asset_classes)
The pd.Series w_max and w_min looks like this (all constraints were
merged to a single upper bound for each asset):
.. image:: images/HRP_Bounds.png
"""
if not isinstance(constraints, pd.DataFrame) and not isinstance(
asset_classes, pd.DataFrame
):
raise ValueError("constraints and asset_classes must be DataFrames")
if constraints.shape[1] != 6:
raise ValueError("constraints must have ten columns")
n = len(constraints)
data = constraints.fillna("").copy()
assetslist = asset_classes.iloc[:, 0].values.tolist()
w_max = pd.Series(1, index=assetslist)
w_min = pd.Series(0, index=assetslist)
for i in range(0, n):
if data.loc[i, "Disabled"] == False:
if data.loc[i, "Type"] == "Assets":
assets = data.loc[i, "Position"]
if data.loc[i, "Sign"] == ">=":
if w_min.loc[assets] <= data.loc[i, "Weight"]:
w_min.loc[assets] = data.loc[i, "Weight"]
elif data.loc[i, "Sign"] == "<=":
if w_max.loc[assets] >= data.loc[i, "Weight"]:
w_max.loc[assets] = data.loc[i, "Weight"]
elif data.loc[i, "Type"] == "All Assets":
if data.loc[i, "Sign"] == ">=":
if w_min[w_min <= data.loc[i, "Weight"]].shape[0] != 0:
w_min[w_min <= data.loc[i, "Weight"]] = data.loc[i, "Weight"]
elif data.loc[i, "Sign"] == "<=":
if w_max[w_max >= data.loc[i, "Weight"]].shape[0] != 0:
w_max[w_max >= data.loc[i, "Weight"]] = data.loc[i, "Weight"]
elif data.loc[i, "Type"] == "Each asset in a class":
label_0 = asset_classes.columns.tolist()[0]
label_1 = data.loc[i, "Set"]
label_2 = data.loc[i, "Position"]
assets = asset_classes[[label_0, label_1]][
asset_classes[label_1] == label_2
]
assets = assets["Assets"].tolist()
if data.loc[i, "Sign"] == ">=":
if (
w_min.loc[assets][
w_min.loc[assets] <= data.loc[i, "Weight"]
].shape[0]
!= 0
):
w_min.loc[assets] = np.where(
w_min.loc[assets] <= data.loc[i, "Weight"],
data.loc[i, "Weight"],
w_min.loc[assets],
)
elif data.loc[i, "Sign"] == "<=":
if (
w_max.loc[assets][
w_max.loc[assets] >= data.loc[i, "Weight"]
].shape[0]
!= 0
):
w_max.loc[assets] = np.where(
w_max.loc[assets] >= data.loc[i, "Weight"],
data.loc[i, "Weight"],
w_max.loc[assets],
)
return w_max, w_min

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,799 @@
"""""" #
"""
Copyright (c) 2020-2022, Dany Cajas
All rights reserved.
This work is licensed under BSD 3-Clause "New" or "Revised" License.
License available at https://github.com/dcajasn/Riskfolio-Lib/blob/master/LICENSE.txt
"""
import numpy as np
import pandas as pd
import scipy.cluster.hierarchy as hr
from scipy.spatial.distance import squareform
import openbb_terminal.portfolio.portfolio_optimization.riskfolio.Portfolio as pf
import openbb_terminal.portfolio.portfolio_optimization.riskfolio.RiskFunctions as rk
import openbb_terminal.portfolio.portfolio_optimization.riskfolio.AuxFunctions as af
import openbb_terminal.portfolio.portfolio_optimization.riskfolio.ParamsEstimation as pe
import openbb_terminal.portfolio.portfolio_optimization.riskfolio.DBHT as db
class HCPortfolio(object):
r"""
Class that creates a portfolio object with all properties needed to
calculate optimal portfolios.
Parameters
----------
returns : DataFrame, optional
A dataframe that containts the returns of the assets.
The default is None.
alpha : float, optional
Significance level of VaR, CVaR, EVaR, DaR, CDaR, EDaR and Tail Gini of losses.
The default is 0.05.
a_sim : float, optional
Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
beta : float, optional
Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value.
The default is None.
b_sim : float, optional
Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
The default is None.
w_max : Series, optional
Upper bound constraint for hierarchical risk parity weights :cite:`c-Pfitzinger`.
w_min : Series, optional
Lower bound constraint for hierarchical risk parity weights :cite:`c-Pfitzinger`.
"""
def __init__(
self,
returns=None,
alpha=0.05,
a_sim=100,
beta=None,
b_sim=None,
w_max=None,
w_min=None,
alpha_tail=0.05,
bins_info=None,
):
self._returns = returns
self.alpha = alpha
self.a_sim = a_sim
self.beta = beta
self.b_sim = b_sim
self.alpha_tail = alpha_tail
self.bins_info = bins_info
self.asset_order = None
self.clusters = None
self.cov = None
self.mu = None
self.codep = None
self.codep_sorted = None
self.w_max = w_max
self.w_min = w_min
@property
def returns(self):
if self._returns is not None and isinstance(self._returns, pd.DataFrame):
return self._returns
else:
raise NameError("returns must be a DataFrame")
@returns.setter
def returns(self, value):
if value is not None and isinstance(value, pd.DataFrame):
self._returns = value
else:
raise NameError("returns must be a DataFrame")
@property
def assetslist(self):
if self._returns is not None and isinstance(self._returns, pd.DataFrame):
return self._returns.columns.tolist()
# get naive-risk weights
def _naive_risk(self, returns, cov, rm="MV", rf=0):
assets = returns.columns.tolist()
n = len(assets)
if rm == "equal":
weight = np.ones((n, 1)) * 1 / n
else:
inv_risk = np.zeros((n, 1))
for i in assets:
k = assets.index(i)
w = np.zeros((n, 1))
w[k, 0] = 1
w = pd.DataFrame(w, columns=["weights"], index=assets)
if rm == "vol":
risk = rk.Sharpe_Risk(
w,
cov=cov,
returns=returns,
rm="MV",
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
else:
risk = rk.Sharpe_Risk(
w,
cov=cov,
returns=returns,
rm=rm,
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
inv_risk[k, 0] = risk
if rm == "MV":
inv_risk = 1 / np.power(inv_risk, 2)
else:
inv_risk = 1 / inv_risk
weight = inv_risk * (1 / np.sum(inv_risk))
weight = weight.reshape(-1, 1)
return weight
# get optimal weights
def _opt_w(self, returns, mu, cov, obj="MinRisk", rm="MV", rf=0, l=2):
if returns.shape[1] == 1:
weight = np.array([1]).reshape(-1, 1)
else:
if obj in {"MinRisk", "Utility", "Sharpe"}:
port = pf.Portfolio(returns=returns)
port.assets_stats(method_mu="hist", method_cov="hist", d=0.94)
port.cov = cov
if mu is not None:
port.mu = mu
weight = port.optimization(
model="Classic", rm=rm, obj=obj, rf=rf, l=l, hist=True
).to_numpy()
elif obj in {"ERC"}:
port = pf.Portfolio(returns=returns)
port.assets_stats(method_mu="hist", method_cov="hist", d=0.94)
port.cov = cov
weight = port.rp_optimization(
model="Classic", rm=rm, rf=rf, b=None, hist=True
).to_numpy()
weight = weight.reshape(-1, 1)
return weight
# Create hierarchical clustering
def _hierarchical_clustering(
self,
model="HRP",
linkage="ward",
codependence="pearson",
max_k=10,
leaf_order=True,
):
# Calculating distance
if codependence in {"pearson", "spearman", "custom_cov"}:
dist = np.sqrt(np.clip((1 - self.codep) / 2, a_min=0.0, a_max=1.0))
elif codependence in {"abs_pearson", "abs_spearman", "distance"}:
dist = np.sqrt(np.clip((1 - self.codep), a_min=0.0, a_max=1.0))
elif codependence in {"mutual_info"}:
dist = af.var_info_matrix(self.returns, self.bins_info).astype(float)
elif codependence in {"tail"}:
dist = -np.log(self.codep).astype(float)
# Hierarchcial clustering
dist = dist.to_numpy()
dist = pd.DataFrame(dist, columns=self.codep.columns, index=self.codep.index)
if linkage == "DBHT":
# different choices for D, S give different outputs!
D = dist.to_numpy() # dissimilatity matrix
if codependence in {"pearson", "spearman", "custom_cov"}:
codep = 1 - dist**2
S = codep.to_numpy() # similarity matrix
else:
S = self.codep.to_numpy() # similarity matrix
(_, _, _, _, _, clustering) = db.DBHTs(
D, S, leaf_order=leaf_order
) # DBHT clustering
else:
p_dist = squareform(dist, checks=False)
clustering = hr.linkage(p_dist, method=linkage, optimal_ordering=leaf_order)
if model in {"HERC", "HERC2", "NCO"}:
# optimal number of clusters
k = af.two_diff_gap_stat(self.codep, dist, clustering, max_k)
else:
k = None
return clustering, k
# sort clustered items by distance
def _seriation(self, clusters):
return hr.leaves_list(clusters)
# compute HRP weight allocation through recursive bisection
def _recursive_bisection(self, sort_order, rm="MV", rf=0):
if isinstance(self.w_max, pd.Series) and isinstance(self.w_min, pd.Series):
if (self.w_max.all() >= self.w_min.all()).item():
flag = True
else:
raise NameError("All upper bounds must be higher than lower bounds")
else:
flag = False
weight = pd.Series(1, index=sort_order) # set initial weights to 1
items = [sort_order]
while len(items) > 0: # loop while weights is under 100%
items = [
i[j:k]
for i in items
for j, k in (
(0, len(i) // 2),
(len(i) // 2, len(i)),
) # get cluster indi
if len(i) > 1
]
# allocate weight to left and right cluster
for i in range(0, len(items), 2):
left_cluster = items[i]
right_cluster = items[i + 1]
# Left cluster
left_cov = self.cov.iloc[left_cluster, left_cluster]
left_returns = self.returns.iloc[:, left_cluster]
left_weight = self._naive_risk(left_returns, left_cov, rm=rm, rf=rf)
if rm == "vol":
left_risk = rk.Sharpe_Risk(
left_weight,
cov=left_cov,
returns=left_returns,
rm="MV",
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
else:
left_risk = rk.Sharpe_Risk(
left_weight,
cov=left_cov,
returns=left_returns,
rm=rm,
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
if rm == "MV":
left_risk = np.power(left_risk, 2)
# Right cluster
right_cov = self.cov.iloc[right_cluster, right_cluster]
right_returns = self.returns.iloc[:, right_cluster]
right_weight = self._naive_risk(right_returns, right_cov, rm=rm, rf=rf)
if rm == "vol":
right_risk = rk.Sharpe_Risk(
right_weight,
cov=right_cov,
returns=right_returns,
rm="MV",
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
else:
right_risk = rk.Sharpe_Risk(
right_weight,
cov=right_cov,
returns=right_returns,
rm=rm,
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
if rm == "MV":
right_risk = np.power(right_risk, 2)
# Allocate weight to clusters
alpha_1 = 1 - left_risk / (left_risk + right_risk)
# Weights constraints
if flag:
a1 = np.sum(self.w_max[left_cluster]) / weight[left_cluster[0]]
a2 = np.max(
[
np.sum(self.w_min[left_cluster]) / weight[left_cluster[0]],
alpha_1,
]
)
alpha_1 = np.min([a1, a2])
a1 = np.sum(self.w_max[right_cluster]) / weight[right_cluster[0]]
a2 = np.max(
[
np.sum(self.w_min[right_cluster])
/ weight[right_cluster[0]],
1 - alpha_1,
]
)
alpha_1 = 1 - np.min([a1, a2])
weight[left_cluster] *= alpha_1 # weight 1
weight[right_cluster] *= 1 - alpha_1 # weight 2
weight.index = self.asset_order
return weight
# compute HRP weight allocation through cluster-based bisection
def _hierarchical_recursive_bisection(
self, Z, rm="MV", rf=0, linkage="ward", model="HERC"
):
# Transform linkage to tree and reverse order
root, nodes = hr.to_tree(Z, rd=True)
nodes = np.array(nodes)
nodes_1 = np.array([i.dist for i in nodes])
idx = np.argsort(nodes_1)
nodes = nodes[idx][::-1].tolist()
weight = pd.Series(1, index=self.cov.index) # Set initial weights to 1
clustering_inds = hr.fcluster(Z, self.k, criterion="maxclust")
clusters = {
i: [] for i in range(min(clustering_inds), max(clustering_inds) + 1)
}
for i, v in enumerate(clustering_inds):
clusters[v].append(i)
# Loop through k clusters
for i in nodes[: self.k - 1]:
if i.is_leaf() == False: # skip leaf-nodes
left = i.get_left().pre_order() # lambda i: i.id) # get left cluster
right = i.get_right().pre_order() # lambda i: i.id) # get right cluster
left_set = set(left)
right_set = set(right)
left_risk = 0
right_risk = 0
# Allocate weight to clusters
if rm == "equal":
alpha_1 = 0.5
else:
for j in clusters.keys():
if set(clusters[j]).issubset(left_set):
# Left cluster
left_cov = self.cov.iloc[clusters[j], clusters[j]]
left_returns = self.returns.iloc[:, clusters[j]]
left_weight = self._naive_risk(
left_returns, left_cov, rm=rm, rf=rf
)
if rm == "vol":
left_risk_ = rk.Sharpe_Risk(
left_weight,
cov=left_cov,
returns=left_returns,
rm="MV",
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
else:
left_risk_ = rk.Sharpe_Risk(
left_weight,
cov=left_cov,
returns=left_returns,
rm=rm,
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
if rm == "MV":
left_risk_ = np.power(left_risk_, 2)
left_risk += left_risk_
elif set(clusters[j]).issubset(right_set):
# Right cluster
right_cov = self.cov.iloc[clusters[j], clusters[j]]
right_returns = self.returns.iloc[:, clusters[j]]
right_weight = self._naive_risk(
right_returns, right_cov, rm=rm, rf=rf
)
if rm == "vol":
right_risk_ = rk.Sharpe_Risk(
right_weight,
cov=right_cov,
returns=right_returns,
rm="MV",
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
else:
right_risk_ = rk.Sharpe_Risk(
right_weight,
cov=right_cov,
returns=right_returns,
rm=rm,
rf=rf,
alpha=self.alpha,
a_sim=self.a_sim,
beta=self.beta,
b_sim=self.b_sim,
)
if rm == "MV":
right_risk_ = np.power(right_risk_, 2)
right_risk += right_risk_
alpha_1 = 1 - left_risk / (left_risk + right_risk)
weight[left] *= alpha_1 # weight 1
weight[right] *= 1 - alpha_1 # weight 2
# Get constituents of k clusters
clustered_assets = pd.Series(
hr.cut_tree(Z, n_clusters=self.k).flatten(), index=self.cov.index
)
# Multiply within-cluster weight with inter-cluster weight
for i in range(self.k):
cluster = clustered_assets.loc[clustered_assets == i]
cluster_cov = self.cov.loc[cluster.index, cluster.index]
cluster_returns = self.returns.loc[:, cluster.index]
if model == "HERC":
cluster_weights = pd.Series(
self._naive_risk(
cluster_returns, cluster_cov, rm=rm, rf=rf
).flatten(),
index=cluster_cov.index,
)
elif model == "HERC2":
cluster_weights = pd.Series(
self._naive_risk(
cluster_returns, cluster_cov, rm="equal", rf=rf
).flatten(),
index=cluster_cov.index,
)
weight.loc[cluster_weights.index] *= cluster_weights
return weight
# compute intra-cluster weights
def _intra_weights(self, Z, obj="MinRisk", rm="MV", rf=0, l=2):
# Get constituents of k clusters
clustered_assets = pd.Series(
hr.cut_tree(Z, n_clusters=self.k).flatten(), index=self.cov.index
)
# get covariance matrices for each cluster
intra_weights = pd.DataFrame(index=clustered_assets.index)
for i in range(self.k):
cluster = clustered_assets.loc[clustered_assets == i]
if self.mu is not None:
cluster_mu = self.mu.loc[:, cluster.index]
else:
cluster_mu = None
cluster_cov = self.cov.loc[cluster.index, cluster.index]
cluster_returns = self.returns.loc[:, cluster.index]
weights = pd.Series(
self._opt_w(
cluster_returns, cluster_mu, cluster_cov, obj=obj, rm=rm, rf=rf, l=l
).flatten(),
index=cluster_cov.index,
)
intra_weights[i] = weights
intra_weights = intra_weights.fillna(0)
return intra_weights
def _inter_weights(self, intra_weights, obj="MinRisk", rm="MV", rf=0, l=2):
# inter-cluster mean vector
if self.mu is not None:
tot_mu = self.mu @ intra_weights
else:
tot_mu = None
# inter-cluster covariance matrix
tot_cov = intra_weights.T.dot(np.dot(self.cov, intra_weights))
# inter-cluster returns matrix
tot_ret = self.returns @ intra_weights
# inter-cluster weights
inter_weights = pd.Series(
self._opt_w(tot_ret, tot_mu, tot_cov, obj=obj, rm=rm, rf=rf, l=l).flatten(),
index=intra_weights.columns,
)
# determine the weight on each cluster by multiplying the intra-cluster weight with the inter-cluster weight
weights = intra_weights.mul(inter_weights, axis=1).sum(axis=1).sort_index()
return weights
# Allocate weights
def optimization(
self,
model="HRP",
codependence="pearson",
covariance="hist",
obj="MinRisk",
rm="MV",
rf=0,
l=2,
custom_cov=None,
custom_mu=None,
linkage="single",
k=None,
max_k=10,
bins_info="KN",
alpha_tail=0.05,
leaf_order=True,
d=0.94,
**kwargs,
):
r"""
This method calculates the optimal portfolio according to the
optimization model selected by the user.
Parameters
----------
model : str, can be {'HRP', 'HERC' or 'HERC2'}
The hierarchical cluster portfolio model used for optimize the
portfolio. The default is 'HRP'. Posible values are:
- 'HRP': Hierarchical Risk Parity.
- 'HERC': Hierarchical Equal Risk Contribution.
- 'HERC2': HERC but splitting weights equally within clusters.
- 'NCO': Nested Clustered Optimization.
codependence : str, optional
The codependence or similarity matrix used to build the distance
metric and clusters. The default is 'pearson'. Posible values are:
- 'pearson': pearson correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{0.5(1-\rho^{pearson}_{i,j})}`.
- 'spearman': spearman correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{0.5(1-\rho^{spearman}_{i,j})}`.
- 'abs_pearson': absolute value pearson correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{(1-|\rho^{pearson}_{i,j}|)}`.
- 'abs_spearman': absolute value spearman correlation matrix. Distance formula: :math:`D_{i,j} = \sqrt{(1-|\rho^{spearman}_{i,j}|)}`.
- 'distance': distance correlation matrix. Distance formula :math:`D_{i,j} = \sqrt{(1-\rho^{distance}_{i,j})}`.
- 'mutual_info': mutual information matrix. Distance used is variation information matrix.
- 'tail': lower tail dependence index matrix. Dissimilarity formula :math:`D_{i,j} = -\log{\lambda_{i,j}}`.
- 'custom_cov': use custom correlation matrix based on the custom_cov parameter. Distance formula: :math:`D_{i,j} = \sqrt{0.5(1-\rho^{pearson}_{i,j})}`.
covariance : str, optional
The method used to estimate the covariance matrix:
The default is 'hist'. Posible values are:
- 'hist': use historical estimates.
- 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`_.
- 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`_.
- 'ledoit': use the Ledoit and Wolf Shrinkage method.
- 'oas': use the Oracle Approximation Shrinkage method.
- 'shrunk': use the basic Shrunk Covariance method.
- 'gl': use the basic Graphical Lasso Covariance method.
- 'jlogo': use the j-LoGo Covariance method. For more information see: :cite:`c-jLogo`.
- 'fixed': denoise using fixed method. For more information see chapter 2 of :cite:`c-MLforAM`.
- 'spectral': denoise using spectral method. For more information see chapter 2 of :cite:`c-MLforAM`.
- 'shrink': denoise using shrink method. For more information see chapter 2 of :cite:`c-MLforAM`.
- 'custom_cov': use custom covariance matrix.
obj : str can be {'MinRisk', 'Utility', 'Sharpe' or 'ERC'}.
Objective function used by the NCO model.
The default is 'MinRisk'. Posible values are:
- 'MinRisk': Minimize the selected risk measure.
- 'Utility': Maximize the Utility function :math:`\mu w - l \phi_{i}(w)`.
- 'Sharpe': Maximize the risk adjusted return ratio based on the selected risk measure.
- 'ERC': Equally risk contribution portfolio of the selected risk measure.
rm : str, optional
The risk measure used to optimze the portfolio. If model is 'NCO',
the risk measures available depends on the objective functon.
The default is 'MV'. Posible values are:
- 'equal': Equally weighted.
- 'vol': Standard Deviation.
- 'MV': Variance.
- 'MAD': Mean Absolute Deviation.
- 'MSV': Semi Standard Deviation.
- 'FLPM': First Lower Partial Moment (Omega Ratio).
- 'SLPM': Second Lower Partial Moment (Sortino Ratio).
- 'VaR': Value at Risk.
- 'CVaR': Conditional Value at Risk.
- 'TG': Tail Gini.
- 'EVaR': Entropic Value at Risk.
- 'WR': Worst Realization (Minimax).
- 'RG': Range of returns.
- 'CVRG': CVaR range of returns.
- 'TGRG': Tail Gini range of returns.
- 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
- 'ADD': Average Drawdown of uncompounded cumulative returns.
- 'DaR': Drawdown at Risk of uncompounded cumulative returns.
- 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
- 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
- 'UCI': Ulcer Index of uncompounded cumulative returns.
- 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio).
- 'ADD_Rel': Average Drawdown of compounded cumulative returns.
- 'DaR_Rel': Drawdown at Risk of compounded cumulative returns.
- 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns.
- 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns.
- 'UCI_Rel': Ulcer Index of compounded cumulative returns.
rf : float, optional
Risk free rate, must be in the same period of assets returns.
The default is 0.
l : scalar, optional
Risk aversion factor of the 'Utility' objective function.
The default is 2.
custom_cov : DataFrame or None, optional
Custom covariance matrix, used when codependence or covariance
parameters have value 'custom_cov'. The default is None.
custom_mu : DataFrame or None, optional
Custom mean vector when NCO objective is 'Utility' or 'Sharpe'.
The default is None.
linkage : string, optional
Linkage method of hierarchical clustering. For more information see `linkage <https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html>`_.
The default is 'single'. Posible values are:
- 'single'.
- 'complete'.
- 'average'.
- 'weighted'.
- 'centroid'.
- 'median'.
- 'ward'.
- 'DBHT': Direct Bubble Hierarchical Tree.
k : int, optional
Number of clusters. This value is took instead of the optimal number
of clusters calculated with the two difference gap statistic.
The default is None.
max_k : int, optional
Max number of clusters used by the two difference gap statistic
to find the optimal number of clusters. The default is 10.
bins_info: int or str
Number of bins used to calculate variation of information. The default
value is 'KN'. Posible values are:
- 'KN': Knuth's choice method. See more in `knuth_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.knuth_bin_width.html>`_.
- 'FD': Freedman–Diaconis' choice method. See more in `freedman_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.freedman_bin_width.html>`_.
- 'SC': Scotts' choice method. See more in `scott_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.scott_bin_width.html>`_.
- 'HGR': Hacine-Gharbi and Ravier' choice method.
- int: integer value choice by user.
alpha_tail : float, optional
Significance level for lower tail dependence index. The default is 0.05.
leaf_order : bool, optional
Indicates if the cluster are ordered so that the distance between
successive leaves is minimal. The default is True.
d : scalar
The smoothing factor of ewma methods.
The default is 0.94.
**kwargs:
Other variables related to covariance estimation. See
`Scikit Learn <https://scikit-learn.org/stable/modules/covariance.html>`_
and chapter 2 of :cite:`d-MLforAM` for more details.
Returns
-------
w : DataFrame
The weights of optimal portfolio.
"""
# Covariance matrix
if covariance == "custom_cov":
self.cov = custom_cov.copy()
else:
self.cov = pe.covar_matrix(
self.returns, method=covariance, d=0.94, **kwargs
)
# Custom mean vector
if custom_mu is not None:
if isinstance(custom_mu, pd.Series) == True:
self.mu = custom_mu.to_frame().T
elif isinstance(custom_mu, pd.DataFrame) == True:
if custom_mu.shape[0] > 1 and custom_mu.shape[1] == 1:
self.mu = custom_mu.T
elif custom_mu.shape[0] == 1 and custom_mu.shape[1] > 1:
self.mu = custom_mu
else:
raise NameError("custom_mu must be a column DataFrame")
else:
raise NameError("custom_mu must be a column DataFrame or Series")
self.alpha_tail = alpha_tail
self.bins_info = bins_info
# Codependence matrix
if codependence in {"pearson", "spearman"}:
self.codep = self.returns.corr(method=codependence).astype(float)
elif codependence in {"abs_pearson", "abs_spearman"}:
self.codep = np.abs(self.returns.corr(method=codependence[4:])).astype(
float
)
elif codependence in {"distance"}:
self.codep = af.dcorr_matrix(self.returns).astype(float)
elif codependence in {"mutual_info"}:
self.codep = af.mutual_info_matrix(self.returns, self.bins_info).astype(
float
)
elif codependence in {"tail"}:
self.codep = af.ltdi_matrix(self.returns, alpha=self.alpha_tail).astype(
float
)
elif codependence in {"custom_cov"}:
self.codep = af.cov2corr(custom_cov).astype(float)
# Step-1: Tree clustering
self.clusters, self.k = self._hierarchical_clustering(
model, linkage, codependence, max_k, leaf_order=leaf_order
)
if k is not None:
self.k = int(k)
# Step-2: Seriation (Quasi-Diagnalization)
self.sort_order = self._seriation(self.clusters)
asset_order = self.assetslist
asset_order[:] = [self.assetslist[i] for i in self.sort_order]
self.asset_order = asset_order.copy()
self.codep_sorted = self.codep.reindex(
index=self.asset_order, columns=self.asset_order
)
if isinstance(self.w_max, pd.Series) and isinstance(self.w_min, pd.Series):
self.w_max = self.w_max.reindex(index=self.asset_order)
self.w_max.index = self.sort_order
self.w_min = self.w_min.reindex(index=self.asset_order)
self.w_min.index = self.sort_order
# Step-3: Recursive bisection
if model == "HRP":
# Recursive bisection
weights = self._recursive_bisection(self.sort_order, rm=rm, rf=rf)
elif model in ["HERC", "HERC2"]:
# Cluster-based Recursive bisection
weights = self._hierarchical_recursive_bisection(
self.clusters, rm=rm, rf=rf, linkage=linkage, model=model
)
elif model == "NCO":
# Step-3.1: Determine intra-cluster weights
intra_weights = self._intra_weights(
self.clusters, obj=obj, rm=rm, rf=rf, l=l
)
# Step-3.2: Determine inter-cluster weights and multiply with 􏰁→ intra-cluster weights
weights = self._inter_weights(intra_weights, obj=obj, rm=rm, rf=rf, l=l)
if isinstance(self.w_max, pd.Series) and isinstance(self.w_min, pd.Series):
self.w_max = self.w_max.sort_index()
self.w_max.index = self.assetslist
self.w_min = self.w_min.sort_index()
self.w_max.index = self.assetslist
weights = weights.loc[self.assetslist].to_frame()
weights.columns = ["weights"]
return weights

View File

@@ -0,0 +1,257 @@
"""""" #
"""
Copyright (c) 2020-2022, Dany Cajas
All rights reserved.
This work is licensed under BSD 3-Clause "New" or "Revised" License.
License available at https://github.com/dcajasn/Riskfolio-Lib/blob/master/LICENSE.txt
"""
import numpy as np
def owa_gmd(T):
r"""
Calculate the OWA weights to calculate the Gini mean difference (GMD)
of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
Returns
-------
value : 1d-array
An OWA weights vector of size Tx1.
"""
w_ = []
for i in range(1, T + 1):
w_.append(2 * i - 1 - T)
w_ = 2 * np.array(w_) / (T * (T - 1))
w_ = w_.reshape(-1, 1)
return w_
def owa_cvar(T, alpha=0.05):
r"""
Calculate the OWA weights to calculate the Conditional Value at Risk (CVaR)
of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
alpha : float, optional
Significance level of CVaR. The default is 0.05.
Returns
-------
value : 1d-array
An OWA weights vector of size Tx1.
"""
k = int(np.ceil(T * alpha)) - 1
w_ = np.zeros((T, 1))
w_[:k, :] = -1 / (T * alpha)
w_[k, :] = -1 - np.sum(w_[:k, :])
return w_
def owa_wcvar(T, alphas, weights):
r"""
Calculate the OWA weights to calculate the Weighted Conditional Value at
Risk (WCVaR) of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
alphas : list
List of significance levels of each CVaR model.
weights : list
List of weights of each CVaR model.
Returns
-------
value : 1d-array
An OWA weights vector of size Tx1.
"""
w_ = 0
for i, j in zip(alphas, weights):
w_ += owa_cvar(T, i) * j
return w_
def owa_tg(T, alpha=0.05, a_sim=100):
r"""
Calculate the OWA weights to calculate the Tail Gini of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
alpha : float, optional
Significance level of TaiL Gini. The default is 0.05.
a_sim : float, optional
Number of CVaRs used to approximate the Tail Gini. The default is 100.
Returns
-------
value : 1d-array
A OWA weights vector of size Tx1.
"""
alphas = np.linspace(alpha, 0.0001, a_sim)[::-1]
w_ = [(alphas[1] - 0) * alphas[0] / alphas[-1] ** 2]
for i in range(1, len(alphas) - 1):
w_.append((alphas[i + 1] - alphas[i - 1]) * alphas[i] / alphas[-1] ** 2)
w_.append((alphas[-1] - alphas[-2]) / alphas[-1])
w_ = owa_wcvar(T, alphas, w_)
return w_
def owa_wr(T):
r"""
Calculate the OWA weights to calculate the Worst realization (minimum) of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
Returns
-------
value : 1d-array
A OWA weights vector of size Tx1.
"""
w_ = np.zeros((T, 1))
w_[0, :] = -1
return w_
def owa_rg(T):
r"""
Calculate the OWA weights to calculate the range of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
Returns
-------
value : 1d-array
A OWA weights vector of size Tx1.
"""
w_ = np.zeros((T, 1))
w_[0, :] = -1
w_[-1, :] = 1
return w_
def owa_cvrg(T, alpha=0.05, beta=None):
r"""
Calculate the OWA weights to calculate the CVaR range of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
alpha : float, optional
Significance level of CVaR of losses. The default is 0.05.
beta : float, optional
Significance level of CVaR of gains. If None it duplicates alpha.
The default is None.
Returns
-------
value : 1d-array
A OWA weights vector of size Tx1.
"""
if beta is None:
beta = alpha
w_ = owa_cvar(T, alpha) - owa_cvar(T, beta)[::-1]
return w_
def owa_wcvrg(T, alphas, weights_a, betas=None, weights_b=None):
r"""
Calculate the OWA weights to calculate the WCVaR range of a returns series.
Parameters
----------
T : int
Number of observations of the returns series.
alphas : list
List of significance levels of each CVaR of losses model.
weights_a : list
List of weights of each CVaR of losses model.
betas : list, optional
List of significance levels of each CVaR of gains model. If None it duplicates alpha.
The default is None.
weights_b : list, optional
List of weights of each CVaR of gains model. If None it duplicates weights_a.
The default is None.
Returns
-------
value : 1d-array
A OWA weights vector of size Tx1.
"""
if betas is None or weights_b is None:
betas = alphas
weights_b = weights_a
w_ = owa_wcvar(T, alphas, weights_a) - owa_wcvar(T, betas, weights_b)[::-1]
return w_
def owa_tgrg(T, alpha=0.05, a_sim=100, beta=None, b_sim=None):
r"""
Calculate the OWA weights to calculate the Tail Gini range of a returns
series.
Parameters
----------
T : int
Number of observations of the returns series.
alpha : float, optional
Significance level of Tail Gini of losses. The default is 0.05.
a_sim : float, optional
Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
beta : float, optional
Significance level of Tail Gini of gains. If None it duplicates alpha value.
The default is None.
b_sim : float, optional
Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
The default is None.
Returns
-------
value : 1d-array
A OWA weights vector of size Tx1.
"""
if beta is None:
beta = alpha
if b_sim is None:
b_sim = a_sim
w_ = owa_tg(T, alpha, a_sim) - owa_tg(T, beta, b_sim)[::-1]
return w_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -238,7 +238,6 @@ reportlab==3.6.9; python_version >= "3.7" and python_version < "4"
requests-oauthlib==1.3.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
rich==10.16.2; python_full_version >= "3.6.2" and python_full_version < "4.0.0"
riskfolio-lib==3.0.2; python_full_version >= "3.7"
robin-stocks==2.1.0; python_version >= "3"
rsa==4.8; python_version >= "3.6" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6")
scikit-learn==1.0.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7"

1
requirements.txt vendored
View File

@@ -190,7 +190,6 @@ reportlab==3.6.9; python_version >= "3.7" and python_version < "4"
requests-oauthlib==1.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0"
requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
rich==10.16.2; python_full_version >= "3.6.2" and python_full_version < "4.0.0"
riskfolio-lib==3.0.2; python_full_version >= "3.7"
robin-stocks==2.1.0; python_version >= "3"
scikit-learn==1.0.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7"
scipy==1.8.0; python_version >= "3.8" and python_version < "3.11" and python_full_version >= "3.7.1" and python_full_version < "4.0.0" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7")