mBTAD-based optimal portfolios¶
Omega ratio was introduced in 2002 as an alternative to Sharpe ratio. It can be defined as
where \(\alpha\) is the Omega threshold and \(F(\cdot)\) is the rate of return cdf. It is common to associate \(\alpha\) with the risk-free rate accessible to the investor. The above expression suggests that \(\Omega_{\alpha}\) ratio is the Sharpe ratio for Below Threshold Absolute Deviation measure (BTAD),
It can be evaluated either in terms of standard or detrended rate of returns (where \(r\) is replaced with \({\bar r} = r - E[r]\)).
Although studied before, the BTAD risk measure were resurrected after the introduction of Omega ratio-based portfolio selection.
azapy implements a generalization of BTAD measure, namely the mixture BTAD (mBTAD).
The mixture is defined as a superposition of BTAD measures for different thresholds, i.e,
where:
\(L\) is the size of the mixture,
\(\{{\cal K}_l\}_{l=1,\cdots,L}\) is a set of positive coefficients normalized to unit,
\(\{\alpha_l\}_{l=1,\cdots,L}\) is a set of distinct thresholds.
Note: a possible choice could be \(L=3\) and \(\alpha=[-0.01, 0, 0.01]\)
The single BTAD is a special case on mBTAD for \(L=1\).
Note: mBTAD dispersion measure for \(L=1\), \(\alpha_1=0\) and detrended rate of returns is the same as mMAD first level dispersion measure.
Note: mBTAD measures (except for \(L=1\) and \(\alpha_1=0\) with detrended rate of returns) are not proper dispersion measures. They violate the positive homogeneity axiom and in the case of using standard rate of return they also violate the location invariance axiom. However, the mathematical formalism of risk-based optimal portfolio constructions can be applied.
We call the BTAD-Sharpe ratio Omega ratio, which is a well-established terminology among practitioners.
Note: The BTAD-Sharpe ratio, for \(L=1\), \(\alpha_1=\mu_0\) (the risk-free rate) and standard rate of returns, is the initial Omega ratio, \(\Omega_{\mu_0}\), introduced by Keating and Shadwick in 2002.
The following portfolio optimization strategies are available:
Minimization of risk for targeted expected rate of return value,
Minimum risk portfolio,
Maximization of expected rate of return for a risk vale generated by a benchmark portfolio (e.g. same risk as equal weighted portfolio),
Maximization of expected rate of return for fixed risk-aversion factor,
Maximization of Omega ratio,
Minimization of the inverse of Omega ratio,
Maximization of diversification factor for targeted expected rate of return value (beta version),
Maximum diversified portfolio (beta version),
Maximization of expected rate of return for a diversification factor value generated by a benchmark portfolio (e.g., same diversification factor as equal weighted portfolio) (beta version),
Maximization of diversification factor for an expected rate of return generated by a benchmark portfolio (e.g., same diversification factor as equal weighted portfolio) (beta version).
The rigorous mathematical description of these strategies is presented here.
There are 2 support classes:
BTADAnalyzer: computes the portfolio weights and performs in-sample analysis,
Port_BTAD : performs portfolio backtesting, out-of-sample analysis.
BTADAnalyzer class¶
- class azapy.Analyzers.BTADAnalyzer.BTADAnalyzer(alpha=[0.0], coef=None, mktdata=None, colname='adjusted', freq='Q', hlength=3.25, name='BTAD', rtype='Sharpe', mu=None, d=1, mu0=0.0, aversion=None, ww0=None, detrended=False, method='ecos', verbose=False)¶
Bases:
_RiskAnalyzer
Mixture BTAD (Below Threshold Absolute Deviation) based optimal portfolio strategies.
- Attributes
status : int - the computation status (0 - success, any other value signifies an error)
ww : pandas.Series - the portfolio weights
RR : float - portfolio rate of return
risk : float - portfolio mBTAD risk
primary_risk_comp : list - portfolio mBTAD components
secondary_risk_comp : list - portfolio thresholds, alpha (input values)
sharpe : float - Omega ration if rtype is set to ‘Sharpe’ or ‘Sharpe2’ otherwise None.
diverse : float - diversification factor if rtype is set to ‘Divers’ or ‘MaxDivers’ otherwise None.
name : str - portfolio name
- Note the following 2 important methods:
getWeights : Computes the optimal portfolio weights. During its computations the following class members are also set: risk, primery_risk_comp, secondary_risk_comp, sharpe, RR, divers.
getPositions : Provides practical information regarding the portfolio rebalancing delta positions and costs.
Methods
getDiversification
(ww[, rrate])Returns the value of the diversification factor for a give portfolio.
getPositions
([nshares, cash, ww, nsh_round, ...])Computes the rebalanced number of shares.
getRisk
(ww[, rrate])Returns the value of the dispersion (risk) measure for a give portfolio.
Returns the risk of each portfolio component.
getWeights
([rtype, mu, d, mu0, aversion, ...])Computes the optimal portfolio weights.
set_method
(method)Sets computation method.
set_mktdata
(mktdata[, colname, freq, ...])Sets historical market data.
set_random_seed
([seed])Sets the seed for Dirichlet random generator used in viewFrontiers to select the random inefficient portfolios.
set_rrate
(rrate)Sets portfolio components historical rates of return.
set_rtype
([rtype, mu, d, mu0, aversion, ww0])Sets the optimization type.
viewFrontiers
([minrisk, efficient, ...])Computes the elements of the portfolio frontiers.
- __init__(alpha=[0.0], coef=None, mktdata=None, colname='adjusted', freq='Q', hlength=3.25, name='BTAD', rtype='Sharpe', mu=None, d=1, mu0=0.0, aversion=None, ww0=None, detrended=False, method='ecos', verbose=False)¶
Constructor
- Parameters:
- alphalist, optional
List of BTAD thresholds. The default is [0.].
- coeflist, optional
List of positive mixture coefficients. Must be the same size as alpha. A None value assumes an equal weighted risk mixture. The vector of coefficients will be normalized to unit. The default is None.
- mktdatapandas.DataFrame, optional
Historic daily market data for portfolio components in the format returned by azapy.mktData function. The default is None.
- colnamestr, optional
Name of the price column from mktdata used in the weight’s calibration. The default is ‘adjusted’.
- freqstr, optional
Rate of return horizon. It could be ‘Q’ for a quarter or ‘M’ for a month. The default is ‘Q’.
- hlengthfloat, optional
History length in number of years used for calibration. A fractional number will be rounded to an integer number of months. The default is 3.25 years.
- namestr, optional
Portfolio name. The default is ‘BTAD’.
- rtypestr, optional
Optimization type. Possible values:
‘Risk’ : optimal risk portfolio for targeted expected rate of return.
‘Sharpe’ : optimal Sharpe portfolio - maximization solution.
‘Sharpe2’ : optimal Sharpe portfolio - minimization solution.
‘MinRisk’ : minimum risk portfolio.
‘RiskAverse’ : optimal risk portfolio for a fixed risk-aversion factor.
‘InvNrisk’ : optimal risk portfolio with the same risk value as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘Diverse’ : optimal diversified portfolio for targeted expected rate of return (max of inverse 1-Diverse).
‘Diverse2’ : optimal diversified portfolio for targeted expected rate of return (min of 1-Diverse).
‘MaxDiverse’ : maximum diversified portfolio.
‘InvNdiverse’ : optimal diversified portfolio with the same diversification factor as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘InvNdrr’ : optimal diversified portfolio with the same expected rate of return as a benchmark portfolio (e.g., same as equal weighted portfolio).
The default is ‘Sharpe’.
- mufloat, optional
Targeted portfolio expected rate of return. Relevant only if rtype=’Risk’ or rtype=’Divers’. The default is None.
- dint, optional
Frontier type. Active only if rtype=’Risk’. A value of 1 will trigger the evaluation of optimal portfolio along the efficient frontier. Otherwise, it will find the portfolio with the lowest rate of return along the inefficient portfolio frontier. The default is 1.
- mu0float, optional
Risk-free rate accessible to the investor. Relevant only if rtype=’Sharpe’ or rtype=’Sharpe2’. The default is 0.
- aversionfloat, optional
The value of the risk-aversion coefficient. Must be positive. Relevant only if rtype=’RiskAverse’. The default is None.
- ww0list, numpy.array or pandas.Series, optional
Targeted portfolio weights. Relevant only if rtype=’InvNrisk’. Its length must be equal to the number of symbols in rrate (mktdata). All weights must be >= 0 with thier sum > 0. If it is a list or a numpy.array then the weights are assumed to be in order of rrate.columns. If it is a pandas.Series then the index should be compatible with the rrate.columns or mktdata symbols (same symbols, not necessarily in the same order). If it is None then it will be set to equal weights. The default is None.
- detrendedBoolean, optional
If it set to True then the rates of return are detrended (mean=0). The default value is True.
- methodstr, optional
Linear programming numerical method. Could be: ‘ecos’, ‘highs-ds’, ‘highs-ipm’, ‘highs’, ‘interior-point’, ‘glpk’ and ‘cvxopt’. The default is ‘ecos’.
- verboseBoolean, optional
If it is set to True, then various computation messages (meant as warnings) will be printed. The default is False.
- Returns:
- The object.
- getDiversification(ww, rrate=None)¶
Returns the value of the diversification factor for a give portfolio.
- Parameters:
- wwlist, numpy.array or pandas.Series;
Portfolio weights. Its length must be equal to the number of symbols in rrate (mktdata). All weights must be >=0 with sum > 0. If it is a list or a numpy.array then the weights are assumed to be in order of rrate.columns. If it is a pandas.Series then the index should be compatible with the rrate.columns or mktdata symbols (not necessarily in the same order).
- rratepandas.DataFrame, optional
Contains the portfolio components historical rates of return. If it is not None then it will overwrite the rates of return computed in the constructor from mktdata. The default is None.
- Returns:
- `float`The diversification value.
- getPositions(nshares=None, cash=0, ww=None, nsh_round=True, verbose=False)¶
Computes the rebalanced number of shares.
- Parameters:
- nsharespanda.Series, optional
Initial number of shares per portfolio component. A missing component entry will be considered 0. A None value assumes that all components entries are 0. The name of the components must be present in the mrkdata. The default is None.
- cashfloat, optional
Additional cash to be added to the capital. A negative entry assumes a reduction in the total capital available for rebalance. The total capital cannot be < 0. The default is 0.
- wwpanda.Series, optional
External overwrite portfolio weights. If it not set to None these weights will overwrite the calibration results. The default is None.
- nsh_roundBoolean, optional
If it is True the invested numbers of shares are round to the nearest integer and the residual cash capital (positive or negative) is carried to the next reinvestment cycle. A value of False assumes investments with fractional number of shares (no rounding). The default is True.
- verboseBoolean, optional
Is it set to True the function prints the closing prices date. The default is True.
- Returns:
- `pandas.DataFrame`the rolling information.
- Columns:
- ‘old_nsh’ :
initial number of shares per portfolio component and the additional cash. These are input values.
- ‘new_nsh’ :
the new number of shares per component plus the residual cash (due to the rounding to an integer number of shares). A negative entry means that the investor needs to add more cash to cover for the roundup shortfall. It has a small value.
- ‘diff_nsh’ :
number of shares (buy/sale) needed to rebalance the portfolio.
- ‘weights’ :
portfolio weights used for rebalancing. The cash entry is the new portfolio value (invested capital).
- ‘prices’ :
the share prices used for rebalance evaluations.
Note: Since the prices are closing prices, the rebalance can be computed after the market close and before the trading execution (next day). Additional cash slippage may occur due to share price differential between the previous day closing and execution time.
- getRisk(ww, rrate=None)¶
Returns the value of the dispersion (risk) measure for a give portfolio.
- Parameters:
- wwlist, numpy.array or pandas.Series
Portfolio weights. Its length must be equal to the number of symbols in rrate (mktdata). All weights must be >=0 with sum > 0. If it is a list or a numpy.array, then the weights are assumed to be in order of rrate.columns. If it is a pandas.Series, then the index should be compatible with the rrate.columns or mktdata symbols (not necessarily in the same order).
- rratepandas.DataFrame, optional
Contains the portfolio components historical rates of return. If it is not None, it will overwrite the rates of return computed in the constructor from mktdata. The default is None.
- Returns:
- `float`The dispersion (risk) measure value.
- getRiskComp()¶
Returns the risk of each portfolio component.
- Returns:
- `pandas.Series`risk per symbol.
- getWeights(rtype=None, mu=None, d=1, mu0=0.0, aversion=None, ww0=None, mktdata=None, **params)¶
Computes the optimal portfolio weights.
- Parameters:
- rtypestr, optional
Optimization type. If is not None it will overwrite the value set by the constructor. The default is None. Other possible values:
‘Risk’ : optimal risk portfolio for targeted expected rate of return.
‘Sharpe’ : optimal Sharpe portfolio - maximization solution.
‘Sharpe2’ : optimal Sharpe portfolio - minimization solution.
‘MinRisk’ : minimum risk portfolio.
‘RiskAverse’ : optimal risk portfolio for a fixed risk-aversion factor.
‘InvNrisk’ : optimal risk portfolio with the same risk value as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘Diverse’ : optimal diversified portfolio for targeted expected rate of return (max of inverse 1-Diverse).
‘Diverse2’ : optimal diversified portfolio for targeted expected rate of return (min of 1-Diverse).
‘MaxDiverse’ : maximum diversified portfolio.
‘InvNdiverse’ : optimal diversified portfolio with the same diversification factor as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘InvNdrr’ : optimal diversified portfolio with the same expected rate of return as a benchmark portfolio (e.g., same as equal weighted portfolio).
- mufloat, optional
Targeted portfolio expected rate of return. Relevant only if rtype=’Risk’ or rtype=’Divers’. The default is None.
- dint, optional
Frontier type. Active only if rtype=’Risk’. A value of 1 will trigger the evaluation of optimal portfolio along the efficient frontier. Otherwise, it will find the portfolio with the lowest rate of return along the inefficient portfolio frontier. The default is 1.
- mu0float, optional
Risk-free rate accessible to the investor. Relevant only if rtype=’Sharpe’ or rtype=’Sharpe2’. The default is 0.
- aversionfloat, optional
The value of the risk-aversion coefficient. Must be positive. Relevant only if rtype=’RiskAverse’. The default is None.
- ww0list, numpy.array or pandas.Series, optional
Targeted portfolio weights. Relevant only if rtype=’InvNrisk’. Its length must be equal to the number of symbols in rrate (mktdata). All weights must be >= 0 with sum > 0. If it is a list or a numpy.array then the weights are assumed to be in order of rrate.columns. If it is a pandas.Series then the index should be compatible with the rrate.columns or mktdata symbols (same symbols, not necessarily in the same order). If it is None then it will be set to equal weights. The default is None.
- mktdatapandas.DataFrame, optional
The portfolio components historical, prices or rates of return, see ‘pclose’ definition below. If it is not None, it will overwrite the set of historical rates of return computed in the constructor from ‘mktdata’. The default is None.
- **paramsother optional parameters
Most common:
- verboseBoolean, optional
If it set to True, then it will print a message when the optimal portfolio degenerates to a single asset. The default is False.
- pcloseBoolean, optional
If it is absent then the mktdata is considered to contain rates of return, with columns the asset symbols and indexed by the observation dates,
True : assumes mktdata contains closing prices only, with columns the asset symbols and indexed by the observation dates,
False : assumes mktdata is in the usual format returned by azapy.mktData function.
- Returns:
- `pandas.Series`portfolio weights per symbol.
- set_method(method)¶
Sets computation method.
- Parameters:
- methodstr;
Must be a valid method name. It will overwrite the value set by the constructor.
- Returns:
- None
- set_mktdata(mktdata, colname=None, freq=None, hlength=None, pclose=False)¶
Sets historical market data. It will overwrite the choice made in the constructor.
- Parameters:
- mktdatapandas.DataFrame
Historic daily market data for portfolio components in the format returned by azapy.mktData function.
- colnamestr, optional
Name of the price column from mktdata used in the weight’s calibration. Unless it is None, it will overwrite the value defined in the constructor. The default is ‘None’.
- freqstr, optional
Rate of return horizon. It could be ‘Q’ for a quarter or ‘M’ for a month. Unless it is None, it will overwrite the value defined in the constructor. The default is ‘None’.
- hlengthfloat, optional
History length in number of years used for calibration. A fractional number will be rounded to an integer number of months. Unless it is None, it will overwrite the value defined in the constructor. The default is ‘None’.
- pcloseBoolean, optional
True : assumes mktdata contains closing prices only, with columns the asset symbols and indexed by the observation dates,
False : assumes mktdata is in the usual format returned by azapy.mktData function. Unless it is None, it will overwrite the value defined in the constructor. The default is ‘None’.
- Returns:
- None
- set_random_seed(seed=42)¶
Sets the seed for Dirichlet random generator used in viewFrontiers to select the random inefficient portfolios.
- Parameters:
- seedint, optional
The random generator seed, in case you want to set it to a weird value other than 42 :). The default is 42.
- Returns:
- None
- set_rrate(rrate)¶
Sets portfolio components historical rates of return. It will overwrite the value computed by the constructor from mktdata.
- Parameters:
- rratepandas.DataFrame
Portfolio components historical rates of return. The columns are: ‘date’, ‘symbol1’, ‘symbol2’, etc.
- Returns
- ——-
- `None`
- set_rtype(rtype=None, mu=None, d=1, mu0=0.0, aversion=None, ww0=None)¶
Sets the optimization type. It will overwrite the value set in the constructor.
- Parameters:
- rtypestr, optional
Optimization type. Possible values:
‘Risk’ : optimal risk portfolio for targeted expected rate of return.
‘Sharpe’ : optimal Sharpe portfolio - maximization solution.
‘Sharpe2’ : optimal Sharpe portfolio - minimization solution.
‘MinRisk’ : minimum risk portfolio.
‘RiskAverse’ : optimal risk portfolio for a fixed risk-aversion factor.
‘InvNrisk’ : optimal risk portfolio with the same risk value as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘Diverse’ : optimal diversified portfolio for targeted expected rate of return (max of inverse 1-Diverse).
‘Diverse2’ : optimal diversified portfolio for targeted expected rate of return (min of 1-Diverse).
‘MaxDiverse’ : maximum diversified portfolio.
‘InvNdiverse’ : optimal diversified portfolio with the same diversification factor as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘InvNdrr’ : optimal diversified portfolio with the same expected rate of return as a benchmark portfolio (e.g., same as equal weighted portfolio).
Unless it is None, it will overwrite the value defined in the constructor. The default is ‘None’.
- mufloat, optional
Targeted portfolio expected rate of return. Relevant only if rtype=’Risk’ or rtype=’Divers’. The default is None.
- dint, optional
Frontier type. Active only if rtype=’Risk’. A value of 1 will trigger the evaluation of optimal portfolio along the efficient frontier. Otherwise, it will find the portfolio with the lowest rate of return along the inefficient portfolio frontier. The default is 1.
- mu0float, optional
Risk-free rate accessible to the investor. Relevant only if rtype=’Sharpe’ or rtype=’Sharpe2’. The default is 0.
- aversionfloat, optional
The value of the risk-aversion coefficient. Must be positive. Relevant only if rtype=’RiskAverse’. The default is None.
- ww0list, numpy.array or pandas.Series, optional
Targeted portfolio weights. Relevant only if rtype=’InvNrisk’. Its length must be equal to the number of symbols in rrate (mktdata). All weights must be >= 0 with sum > 0. If it is a list or a numpy.array then the weights are assumed to be in order of rrate.columns. If it is a pandas.Series then the index should be compatible with the rrate.columns or mktdata symbols (same symbols, not necessarily in the same order). If it is None then it will be set to equal weights. The default is None.
- Returns:
- None
- viewFrontiers(minrisk=True, efficient=20, inefficient=20, sharpe=True, musharpe=0.0, maxdiverse=False, diverse_efficient=0, diverse_inefficient=0, invN=True, invNrisk=True, invNdiverse=False, invNdrr=False, component=True, randomport=20, addport=None, fig_type='RR_risk', **opt)¶
Computes the elements of the portfolio frontiers.
- Parameters:
- minriskBoolean, optional
If it is True then the minimum risk portfolio will be visible. The default is True.
- efficientint, optional
Number of points along the efficient risk frontier (equally spaced along the rate of return axis). The default is 20.
- inefficientint, optional
Number of points along the inefficient risk frontier (equally spaced along the rate of returns axis). The default is 20.
- sharpeBoolean, optional
If it is True then the maximum Sharpe portfolio will be visible. The default is True.
- musharpefloat, optional
Risk-free rate value used in the evaluation of generalized Sharpe ratio. The default is 0.
- maxdiverseBoolean, optional
If it is True then the maximum diversified portfolio will be visible. The default is True.
- diverse_efficientint, optional
Number of points along the efficient diversification frontier (equally spaced along the rate of return axis). The default is 20.
- diverse_inefficientint, optional
Number of points along the inefficient diversification frontier (equally spaced along the rate of return axis). The default is 20.
- invNBoolean, optional
If it is True, then the equal weighted portfolio and the optimal portfolio with the same risk value are added to the plot. The default is True.
- invNriskBoolean, optional
If it is True, then the efficient risk portfolio with same risk as equal weighted portfolio is added to the plot. The default is False.
- invNdiverseBoolean, optional
If it is True, then the efficient diversified portfolio with the same diversification factor as the equal weighted portfolio is added to the plot. The default is False.
- invNdrrBoolean, optional
If it is True, then the efficient diversified portfolio with the same expected rate of return as the equal weighted portfolio is added to the plot. The default value is False.
- componentBoolean, optional
If it is True, then the single component portfolios are added to the plot. The default is True.
- randomportint, optional
Number of portfolios with random weights (inefficient) to be added to the plot (for reference). The default is 20.
- addportdict or pandas.DataFrame, optional
The weights of additional portfolio to be added to the plot. If it is a dict then the keys are the labels, and the values are list of weights in the order of rrate.columns. If it is a pandas.DataFrame the index are the labels, and each row is a set of weights. The columns names should match the symbols names. The default is None.
- fig_typestr, optional
Graphical representation format.
‘RR_risk’ : expected rate of return vs risk
‘Sharpe_RR’ : sharpe vs expected rate of return
‘Diverse_RR’ : diversification vs expected rate of return
The default is ‘RR_risk’.
- **optoptional
Additional parameters:
- ‘title’str
The default is ‘Portfolio frontiers’.
- ‘xlabel’str
The default is
‘risk’ if fig_type=’RR_risk’,
‘rate of returns’ otherwise.
- ‘ylabel’str
The default is
‘rate of returns’ if fig_type=’RR_risk’
‘sharpe’ if fig_type=’Sharpe_RR’
‘diversification’ if fig_type=diverse_RR
- ‘tangent’Boolean
If set to True, then the tangent (to max sharpe point) is added. It has effect only if fig_type=’RR_risk’. The default is True.
- savetostr
File name to save the figure. The extension dictates the format: png, pdf, svg, etc. For more details see the mathplotlib documentation for savefig. The default is None.
- datadefaultdict
Numerical data to construct the plot. If it is not None, then it will take precedence and no other numerical evaluations will be performed. It is meant to produce different plot representations without reevaluations. The default is None.
- invN_labelstr
The label for equal weighted portfolio. The default is ‘1/N’.
- invNrisk_labelstr
The lable for efficient portfolio with same risk as equal weighted portfolio. The default is invNrisk.
- invNdiverse_labelstr
The label for diverse-efficient portfolio with the same diversificeation factor as equal weighted porfolio. The default is ‘invNdiv’.
- invNdrr_labelstr
The label of diverse-efficient portfolio with the same expected rate of return as equal weighted portolio. The defualt is ‘invNdrr’.
- maxdiverse_labelstr
The label of maximum diverified portfolio. The default is ‘MaxD’.
- minrisk_label‘str’
The label of minimum risk portfolio. The default is ‘MinR’.
- sharpe_labelstr
The label of maximum Sharpe portfolio. The default is ‘Sharpe’.
- Returns:
- `dict`Numerical data used to make the plots. It can be passed back
- to reconstruct the plots without reevaluations.
Example BTADAnalyzer¶
# Examples
import numpy as np
import pandas as pd
import azapy as az
print(f"azapy version {az.version()}", flush=True)
#==============================================================================
# collect market data
mktdir = '../../MkTdata'
sdate = '2012-01-01'
edate = '2021-07-27'
symb = ['GLD', 'TLT', 'XLV', 'IHI', 'VGT']
mktdata = az.readMkT(symb, sdate=sdate, edate=edate, file_dir=mktdir)
#==============================================================================
# Define mBTAD measure parameters alpha and coef
alpha = [-0.01, 0.0, +0.01]
coef = np.full(len(alpha), 1 / len(alpha))
# set now the title of the frontiers plots
title_plot = 'mBTAD frontiers'
hlength = 3.25
method = 'ecos' # default choice
# build the analyzer object
cr1 = az.BTADAnalyzer(alpha, coef, mktdata, hlength=hlength, method=method)
#==============================================================================
# Beyond this point any section can be run independently
#==============================================================================
print("\n******************************************************************\n")
print("\n*** Risk of a given portfolio ***")
print("---we choose a random portfolio---")
ww = np.random.dirichlet([0.5] * len(symb))
risk = cr1.getRisk(ww)
status = cr1.status
RR = cr1.RR
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
print(f"Risk comp time {comp_time:f}\n "
f"Portfolio parameters:\nweights {ww.round(4)}\n"
f"expected rate of return {RR:f}\n"
f"risk {risk:f}\n"
f"primary risk comp {primary_risk.round(6)}\n"
f"secondary risk comp {secondary_risk.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("\n*** Diversification + Risk of a given portfolio ***")
print("---we choose a random portfolio---")
ww = np.random.dirichlet([0.5] * len(symb))
diverse = cr1.getDiversification(ww)
status = cr1.status
risk = cr1.risk
RR = cr1.RR
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
print(f"Diverse + Risk comp time {comp_time:f}\n "
f"Portfolio parameters:\nweights {ww.round(4)}\n"
f"expected rate of return {RR:f}\n"
f"diversification factor {diverse:f}\n"
f"risk {risk:f}\n"
f"primary risk comp {primary_risk.round(6)}\n"
f"secondary risk comp {secondary_risk.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Optimal risk portfolio for targeted expected rate of return ***")
rtype = 'Risk'
mu = 0.04
ww = cr1.getWeights(rtype, mu)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
print(f"rtype {rtype} for mu {mu} "
f"computation status {status} time {comp_time:f}\n"
f"optimal weights:\n{ww.round(4)}\n"
f"expected rate of return {RR:f}\n"
f"risk {risk:f}\n"
f"primary risk comp {primary_risk.round(6)}\n"
f"secondary risk comp {secondary_risk.round(6)}\n")
print("=== test - compute risk for portfolio with optimal weights ===")
print(f"optimal weights\n{ww}")
risk_test = cr1.getRisk(ww)
status_test = cr1.status
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"rtype {rtype} for mu {mu} computation status {status}\n"
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Minimum risk portfolio ***")
rtype = 'MinRisk'
ww = cr1.getWeights(rtype)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time}\n")
print("=== test - compute optimal risk portfolio for "
"mu = min component expected rate of return ===")
# results should be identical
rtype_test = 'Risk'
mu = max(cr1.muk.min(), 0)
ww_test = cr1.getWeights(rtype_test, mu)
status_test = cr1.status
RR_test = cr1.RR
risk_test= cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
comp_time_test = cr1.time_level1
print(f"test rtype {rtype_test} computation status {status_test} "
f"time {comp_time_test:f}\n")
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"optimal weights\n{weights.round(4)}\n" +
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test}\n"
f"primary risk comp\n{prc}\n"
f"secondary risk comp\n{src}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Sharpe optimal portfolio - max Sharpe ratio ***")
rtype = 'Sharpe'
mu0 = 0. # 0. risk free rate (default value)
ww = cr1.getWeights(rtype, mu0=mu0)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
sharpe = cr1.sharpe
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time}\n")
print("=== test1 - compute risk for portfolio with Sharpe weights ===")
risk_test = cr1.getRisk(ww)
status_test = cr1.status
ww_test = cr1.ww
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
comp_time_test = cr1.time_level1
sharpe_test = (RR_test - mu0) / risk_test
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"test computation status {status_test} comp time {comp_time_test:f}\n\n"
f"optimal weights\n{weights.round(4)}\n"
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"sharpe {sharpe:f} test {sharpe_test:f} diff {sharpe - sharpe_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
print("=== test2 - compute optimal risk portfolio for "
"mu equal to Sharpe portfolio expected rate of return ===")
ww_test = cr1.getWeights('Risk', mu=RR)
status_test = cr1.status
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
comp_time_test = cr1.time_level1
sharpe_test = (RR_test - mu0) / risk_test
print(f"test computation status {status_test} time {comp_time_test:f}\n")
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"optimal weights\n{weights.round(4)}\n"
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"sharpe {sharpe:f} test {sharpe_test:f} diff {sharpe - sharpe_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Sharpe optimal portfolio - min inverse Sharpe ratio ***")
rtype = 'Sharpe2'
mu0 = 0. # 0. risk free rate (default value)
ww = cr1.getWeights(rtype, mu0=mu0)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
sharpe = cr1.sharpe
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test - compare Sharpe with Sharpe2 ===")
rtype_test = 'Sharpe'
mu0_test = mu0
ww_test = cr1.getWeights(rtype_test, mu0=mu0_test)
status_test = cr1.status
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
sharpe_test = cr1.sharpe
comp_time_test = cr1.time_level1
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"rtype {rtype_test} computation status {status_test} "
f"comp time {comp_time_test:f}\n\n"
f"optimal weights\n{weights.round(4)}\n"
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"sharpe {sharpe:f} test {sharpe_test:f} diff {sharpe - sharpe_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Optimal risk portfolio for fixed risk-aversion factor ***")
# set the aversion factor equal to Sharpe ratio for mu0=0.
# compute Sharpe portfolio for mu0=0. (default)
rtype_test = 'Sharpe'
ww_test = cr1.getWeights(rtype_test)
status_test = cr1.status
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
sharpe_test = cr1.sharpe
comp_time_test = cr1.time_level1
# actual computation
rtype = 'RiskAverse'
aversion = np.abs(sharpe_test)
print(f"aversion = Sharpe ratio = {aversion:f}")
ww = cr1.getWeights(rtype, aversion=aversion)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test - compare optimal risk portfolio for aversion factor equal to "
"Sharpe ratio ===\n=== (must return the Sharpe portfolio) ===")
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"test rtype {rtype_test} computation status {status_test} "
f"comp time {comp_time_test:f}\n\n"
f"optimal weights\n{weights.round(4)}\n"
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Optimal risk portfolio "
"with same risk as a benchmark portfolio ***")
print("\t--------------------------------------------------------------------"
"\n"
"\tNote: If the benchmark portfolio risk is greater than the risk\n"
"\tof single asset portfolio with the highest expected rate of return,\n"
"\tthen the InvNrisk portfolio defaults to this single asset portfolio."
"\n"
"\t--------------------------------------------------------------------"
"\n")
ww0 = np.random.dirichlet([0.5] * len(symb))
# for equal weighted portfolio uncomment the line below
# ww0 = np.full(len(symb), 1/len(symb))
print(f"benchmark portfolio weights {ww0.round(4)}")
rtype = 'InvNrisk'
ww = cr1.getWeights(rtype, ww0=ww0)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test1 - compare the risk with the benchmark ===")
symb_max = cr1.muk.idxmax()
ww_s = pd.Series(0., index=symb)
ww_s[symb_max] = 1.
risk_s = cr1.getRisk(ww_s)
risk_test = cr1.getRisk(ww0)
if risk_s < risk_test:
print(f"benchmark port risk {risk_test:f} smaller than {risk_s:f}\n"
f"default to single asset portfolio {symb_max}")
risk_test = risk_s
print(f"risk {risk:f} benchmark risk {risk_test:f} diff {risk - risk_test:f}")
print("\n=== test2 - compare with the optimal risk portfolio for "
"mu = InvNrisk port expected rate of return ===\n"
"=== must be the same (up to precision) ===")
rtype_test = 'Risk'
mu_test = RR
ww_test = cr1.getWeights(rtype_test, mu=mu_test)
status_test = cr1.status
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
comp_time_test = cr1.time_level1
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"rtype {rtype_test:} computation status {status_test} "
f"comp time {comp_time_test:f}\n\n"
f"optimal weights\n{weights.round(4)}\n"
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Optimal diversified portfolio for targeted "
"expected rate of return ***")
rtype = 'Diverse'
mu = 0.04
ww = cr1.getWeights(rtype, mu)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
diverse = cr1.diverse
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test - compute risk/diversification for a portfolio with weights "
"equal to the optimal weights ===")
print(f"optimal weights\n{ww.round(4)}")
diverse_test = cr1.getDiversification(ww)
status_test = cr1.status
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"diversification factor {diverse:f} test {diverse_test:f} "
f"diff {diverse - diverse_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Optimal diversified portfolio for targeted "
"expected rate of return (alternative) ***")
rtype = 'Diverse2'
mu = 0.04
ww = cr1.getWeights(rtype, mu)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
diverse = cr1.diverse
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test - compare rtype 'Diverse2' with 'Diverse' ===")
rtype_test = 'Diverse'
ww_test = cr1.getWeights(rtype_test, mu)
status_test = cr1.status
RR_test = cr1.RR
risk_test = cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
diverse_test = cr1.diverse
comp_time_test = cr1.time_level1
print(f"rtype {rtype_test} computation status {status_test} "
f"comp time {comp_time_test:f}\n")
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"optimal weights\n{weights.round(4)}\n"
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"diversification factor {diverse:f} test {diverse_test:f} "
f"diff {diverse - diverse_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Maximum diversified portfolio ***")
rtype = 'MaxDiverse'
ww = cr1.getWeights(rtype)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
diverse = cr1.diverse
comp_time = cr1.time_level1
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test - compute optimal diversified portfolio for "
"mu = min component expected rate of return ===")
# results should be identical
rtype_test = 'Diverse'
mu = max(cr1.muk.min(), 0)
ww_test = cr1.getWeights(rtype_test, mu)
status_test = cr1.status
RR_test = cr1.RR
risk_test= cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
diverse_test = cr1.diverse
comp_time_test = cr1.time_level1
print(f"test rtype {rtype_test} computation status {status_test} "
f"time {comp_time_test:f}\n")
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"optimal weights\n{weights.round(4)}\n" +
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"diversification factor {diverse:f} test {diverse_test:f} "
f"diff {diverse - diverse_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("******************************************************************\n")
print("\n*** Optimal diversified portfolio with same diversification "
"factor as a benchmark portfolio ***")
ww0 = np.random.dirichlet([0.5] * len(symb))
# for equal weighted portfolio uncomment the line below
# ww0 = np.full(len(symb), 1/len(symb))
print(f"benchmark portfolio weights {ww0.round(4)}")
rtype = 'InvNdiverse'
ww = cr1.getWeights(rtype, ww0=ww0)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
diverse = cr1.diverse
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test1 - compare the diversification factors ===")
diverse_test = cr1.getDiversification(ww0)
print(f"diversification {diverse:f} benchmark port {diverse_test:f} "
f"diff {diverse - diverse_test:f}\n")
print("=== test2 - compare with optimal diversified portfolio for "
"mu = InvNdiverse portfolio expected rate of return ===")
rtype_test = 'Diverse'
mu_test = RR
ww_test = cr1.getWeights(rtype_test, mu_test)
status_test = cr1.status
RR_test = cr1.RR
risk_test= cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
diverse_test = cr1.diverse
comp_time_test = cr1.time_level1
print(f"test rtype {rtype_test} computation status {status_test} "
f"time {comp_time_test:f}\n")
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"optimal weights\n{weights.round(4)}\n" +
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"diversification factor {diverse:f} test {diverse_test:f} "
f"diff {diverse - diverse_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("******************************************************************\n")
print("\n*** Optimal diversified portfolio with same expected rate of return "
"as a benchmark portfolio ***")
ww0 = np.random.dirichlet([0.5] * len(symb))
# for equal weighted portfolio uncomment the line below
# ww0 = np.full(len(symb), 1/len(symb))
print(f"benchmark portfolio weights {ww0.round(4)}")
rtype = 'InvNdrr'
ww = cr1.getWeights(rtype, ww0=ww0)
status = cr1.status
RR = cr1.RR
risk = cr1.risk
primary_risk = cr1.primary_risk_comp
secondary_risk = cr1.secondary_risk_comp
comp_time = cr1.time_level1
diverse = cr1.diverse
print(f"rtype {rtype} computation status {status} comp time {comp_time:f}\n")
print("=== test1 - compare the portfolios expected rate of return ===")
_ = cr1.getRisk(ww0)
RR_test = cr1.RR
print(f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n")
print("=== test2 - compare with optimal diversified portfolio for "
"mu = benchmark portfolio expected rate of return ===")
mu = np.dot(ww0, cr1.muk)
# first compute MaxDiverese portfolio expected rate of return
rtype_test = 'MaxDiverse'
ww_test = cr1.getWeights(rtype_test)
status_test = cr1.status
comp_time_test = cr1.time_level1
print(f"rtype {rtype_test} comp time {comp_time_test:f}")
if status_test == 0:
mu_maxd = cr1.RR
# close the branch of the frontiers
rtype_test = 'Diverse'
if mu > mu_maxd:
ww_test = cr1.getWeights(rtype_test, mu, d=1)
comp_time_test += cr1.time_level1
print(f"rtype {rtype_test} d=1 comp time {cr1.time_level1:f}\n"
f"test comp time {comp_time_test:f}")
elif mu < mu_maxd:
ww_test = cr1.getWeights(rtype_test, mu, d=-1)
comp_time_test += cr1.time_level1
print(f"rtype {rtype_test} d=-1 comp time {cr1.time_level1:f}\n"
f"test comp time {comp_time_test:f}")
status_test = cr1.status
RR_test = cr1.RR
risk_test= cr1.risk
primary_risk_test = cr1.primary_risk_comp
secondary_risk_test = cr1.secondary_risk_comp
diverse_test = cr1.diverse
print(f"test rtype {rtype_test} computation status {status_test} "
f"time {comp_time_test:f}\n")
weights = pd.DataFrame({'ww': ww, 'test': ww_test, 'diff': ww - ww_test})
prc = pd.DataFrame({'primary': primary_risk, 'test': primary_risk_test,
'diff': primary_risk - primary_risk_test})
src = pd.DataFrame({'secondary': secondary_risk, 'test': secondary_risk_test,
'diff': secondary_risk - secondary_risk_test})
print(f"optimal weights\n{weights.round(4)}\n" +
f"expected rate of return {RR:f} test {RR_test:f} diff {RR - RR_test:f}\n"
f"diversification factor {diverse:f} test {diverse_test:f} "
f"diff {diverse - diverse_test:f}\n"
f"risk {risk:f} test {risk_test:f} diff {risk - risk_test:f}\n"
f"primary risk comp\n{prc.round(6)}\n"
f"secondary risk comp\n{src.round(6)}\n")
#==============================================================================
print("\n******************************************************************\n")
print("*** Frontiers evaluations - standard view***")
opt = {'title': title_plot, 'tangent': True}
print("\n expected rate of return vs risk representation")
rft = cr1.viewFrontiers(options=opt)
print("\n Sharpe vs expected rate of return representation")
_ = cr1.viewFrontiers(data=rft, fig_type='Sharpe_RR', options=opt)
print("\n diversification factor vs expected rate of return")
_ = cr1.viewFrontiers(data=rft, fig_type='Diverse_RR', options=opt)
#==============================================================================
print("\n******************************************************************\n")
print("*** Frontiers evaluations - custom view***")
# 10 (random in this example) additional portfolios to be added to the plot
rng = np.random.RandomState(42)
addp = {}
for i in range(10):
addp['p' + str(i+1)] = rng.dirichlet([0.5] * len(symb))
addport = pd.DataFrame().from_dict(addp, 'index', columns=symb)
opt = {'tangent': True, 'title': title_plot, 'minrisk_label': 'mRx',
'sharpe_label': 'sharpe', 'addport_label': True, 'xlabel': "RofR"}
print("\n expected rate of return vs risk representation")
fd1 = cr1.viewFrontiers(minrisk=True, efficient=20, inefficient=20,
maxdiverse=True,
diverse_efficient=20, diverse_inefficient=20,
invNdiverse=True, invNdrr=True,
randomport=10,
options=opt, addport=addport)
print("\n Sharpe vs expected rate of return representation")
_ = cr1.viewFrontiers(fig_type='Sharpe_RR',
invNdiverse_label=None, data=fd1, options=opt)
print("\n diversification factor vs expected rate of return")
_ = cr1.viewFrontiers(fig_type='Diverse_RR',
invNrisk_label=None, data=fd1, options=opt)
#==============================================================================
print("\n******************************************************************\n")
print("*** Example of rebalancing positions for a Sharpe strategy ***")
# set Sharpe strategy
rtype = 'Sharpe'
mu0 = 0. # 0. risk free rate (default value)
ww = cr1.getWeights(rtype, mu0=mu0)
# assumed existing positions and cash
ns = pd.Series(100, index=symb)
cash = 0.
# new positions and rolling info
# optimization strategy
rtype = 'Sharpe'
mu0 = 0. # risk free rate
pos = cr1.getPositions(nshares=ns, cash=cash)
print(f" New position report\n {pos}")
#==============================================================================
print("\n******************************************************************\n")
print("*** Speed comparisons for different methods ***")
# may take some time to complete
# to run please uncomment the lines below
# methods = cr1.methods
# # remove 'interior_point' if exists - it is painfully slow
# if 'interior-point' in methods:
# methods.remove('interior-point')
# rtypes = cr1.rtypes
# mu = 0.04
# mu0 = 0.
# aversion = 0.6
# ewp = np.full(len(symb), 1/len(symb))
# res_time = pd.DataFrame(0., index=rtypes, columns=methods)
# res_RR = pd.DataFrame(0., index=rtypes, columns=methods)
# for method_ in methods:
# for rtype_ in rtypes:
# cr1.set_method(method_)
# _ = cr1.getWeights(rtype_, mu=mu, mu0=mu0, aversion=aversion, ww0=ewp)
# print(f"method {method_} rtype {rtype_} status {cr1.status}")
# res_time.loc[rtype_, method_] = \
# cr1.time_level1 if cr1.status == 0 else np.nan
# res_RR.loc[rtype_, method_] = cr1.RR if cr1.status == 0 else np.nan
# print(f"\nComputation time (s) per method per rtype\n{res_time.round(6)}\n")
# print(f"expected rate of return\n{res_RR.round(4)}\n")
# #restore the initial method
# cr1.set_method(method)
#==============================================================================
Port_BTAD class¶
- class azapy.PortOpt.Port_BTAD.Port_BTAD(mktdata, symb=None, sdate=None, edate=None, col_price='close', col_divd='divd', col_ref='adjusted', col_calib='adjusted', pname='Port', pcolname=None, capital=100000, schedule=None, freq='Q', noffset=-3, fixoffset=-1, histoffset=3.25, calendar=None, multithreading=True, nsh_round=True)¶
Bases:
_Port_Generator
Backtesting BTAD (Below Threshold Absolute Deviation) portfolio periodically rebalanced.
- Attributes
pname : str - portfolio name
ww : pandasDataFrame - portfolio weights at each rebalancing date
port : pandas.Series - portfolio historical time-series
schedule : pandas.DataFrame - rebalancing schedule
The most important method is set_model. It must be called before any other method.
Methods
get_account
([fancy])Returns additional bookkeeping information regarding rebalancing (e.g., residual cash due rounding number of shares, previous period dividend cash accumulation, etc.)
Returns the actual market data used for portfolio evaluations.
Returns the number of shares held after each rolling date.
get_port
()Returns the portfolio time-series.
get_weights
([fancy])Returns the portfolio weights at each rebalancing period.
port_annual_returns
([withcomp, componly, fancy])Portfolio annual (calendar) rates of returns.
port_drawdown
([top, fancy, withcomp, componly])Computes the portfolio drawdowns.
port_monthly_returns
([withcomp, componly, fancy])Portfolio monthly (calendar) rate of returns.
port_perf
([componly, fancy])Brief description of portfolio and its components performances in terms of average historical rate of returns and maximum drawdowns.
port_period_perf
([fancy])Returns portfolio performance for each rolling period i.e. the rate of return, the rolling min and max returns, and max drawdown during the period.
port_period_returns
([fancy])Computes the rolling periods rate of returns.
port_quarterly_returns
([withcomp, componly, ...])Portfolio quarterly (calendar) rate of returns.
port_view
([emas, bollinger])Plots the portfolio time series together with optional technical indicators.
port_view_all
([sdate, edate, componly])Plots the portfolio and its component time-series on a relative basis.
set_model
([alpha, coef, rtype, mu, mu0, ...])Sets model parameters and evaluates portfolio time-series.
- __init__(mktdata, symb=None, sdate=None, edate=None, col_price='close', col_divd='divd', col_ref='adjusted', col_calib='adjusted', pname='Port', pcolname=None, capital=100000, schedule=None, freq='Q', noffset=-3, fixoffset=-1, histoffset=3.25, calendar=None, multithreading=True, nsh_round=True)¶
Constructor
- Parameters:
- mktdatapandas.DataFrame
MkT data in the format “symbol”, “date”, “open”, “high”, “low”, “close”, “volume”, “adjusted”, “divd”, “split” (e.g., as returned by azapy.readMkT function).
- symblist, optional
List of symbols for the basket components. All symbols MkT data should be included in mktdata. If set to None the symb will be set to include all the symbols from mktdata. The default is None.
- sdatedate like, optional
Start date for historical data. If set to None the sdate will be set to the earliest date in mktdata. The default is None.
- edatedate like, optional
End date for historical dates and so the simulation. Must be greater than sdate. If it is None then edate will be set to the latest date in mktdata. The default is None.
- col_pricestr, optional
Column name in the mktdata DataFrame that will be considered for portfolio aggregation. The default is ‘close’.
- col_divdstr, optional
Column name in the mktdata DataFrame that holds the dividend information. The default is ‘dvid’.
- col_refstr, optional
Column name in the mktdata DataFrame that will be used as a price reference for portfolio components. The default is ‘adjusted’.
- col_calibstr, optional
Column name used for historical weights calibrations. The default is ‘adjusted’.
- pnamestr, optional
The name of the portfolio. The default is ‘Port’.
- pcolnamestr, optional
Name of the portfolio price column. If it set to None then pcolname=pname. The default is None.
- capitalfloat, optional
Initial portfolio Capital in dollars. The default is 100000.
- schedulepandas.DataFrame, optional
Rebalancing schedule, with columns for ‘Droll’ rolling date and ‘Dfix’ fixing date. If it is None than the schedule will be set using the freq, noffset, fixoffset and calendar information. The default is None.
- freqstr, optional
Rebalancing frequency. It can be ‘Q’ for quarterly or ‘M’ for monthly rebalancing, respectively. It is relevant only if the schedule is None. The default is ‘Q’.
- noffsetint, optional
Number of business days offset for rebalancing date ‘Droll’ relative to the end of the period (quart or month). A positive value add business days beyond the calendar end of the period while a negative value subtracts business days. It is relevant only if the schedule is None. The default is -3.
- fixoffsetint, optional
Number of business day offset of fixing date ‘Dfix’ relative to the rebalancing date ‘Droll’. It can be 0 or negative. It is relevant only if the schedule is None. The default is -1.
- calendarnumpy.busdaycalendar, optional
Business calendar. If it is None then it will be set to NYSE business calendar. The default value is None.
- multithreadingBoolean, optional
If it is True, then the rebalancing weights will be computed concurrent. The default is True.
- nsh_roundBoolean, optional
If it is True the invested numbers of shares are round to the nearest integer and the residual cash capital (positive or negative) is carried to the next reinvestment cycle. A value of False assumes investments with fractional number of shares (no rounding). The default is True.
- Returns:
- The object.
- get_account(fancy=False)¶
Returns additional bookkeeping information regarding rebalancing (e.g., residual cash due rounding number of shares, previous period dividend cash accumulation, etc.)
- Parameters:
- fancyBoolean, optional
False: the values are reported in unaltered algebraic format.
True : the values are reported rounded.
The default is False.
- Returns:
- `pandas.DataFrame`Reports, for each rolling period identified by ‘Droll’,
number of shares hold for each symbol,
‘cash_invst’ : cash invested at the beginning of period,
‘cash_roll’ : cash rolled to the next period,
‘cash_divd’ : cash dividend accumulated in the previous period.
Note: The capital at the beginning of the period is cash_invst + cash_roll. It is also equal to the previous period: value of the shares on the fixing date + cash_roll + cash_divd. There are 2 sources for the cash_roll. The roundup to integer number of shares and the shares close price differences between the fixing (computation) and rolling (execution) dates. It could be positive or negative. The finance of the cash_roll during each rolling period is assumed to be done separately by the investor.
- get_mktdata()¶
Returns the actual market data used for portfolio evaluations.
- Returns:
- `pandas.DataFrame`market data.
Returns the number of shares held after each rolling date.
- Returns:
- `pandas.DataFrame`number of shares per symbol.
- get_port()¶
Returns the portfolio time-series.
- Returns:
- `pandas.DataFrame`portfolio time-series.
- get_weights(fancy=False)¶
Returns the portfolio weights at each rebalancing period.
- Parameters:
- fancyBoolean, optional
False: reports the weights in algebraic format.
True: reports the weights in percentage rounded to 2 decimals.
The default is False.
- Returns:
- `pandas.DataFrame`portfolio weights per symbol.
- port_annual_returns(withcomp=False, componly=False, fancy=False)¶
Portfolio annual (calendar) rates of returns.
- Parameters:
- withcompBoolean, optional
If True, adds the portfolio components annual returns to the report. The default is False.
- componlyBoolean, optional
If True, only the portfolio components annual returns are reported. The flag is active only if withcomp=True. The default is False.
- fancyBoolean, optional
False : The rates are reported in unaltered algebraic format.
True :The rates are reported in percentage rounded to 2 decimals and presented is color style.
The default is False.
- Returns:
- `pandas.DataFrame`the report.
- port_drawdown(top=5, fancy=False, withcomp=False, componly=False)¶
Computes the portfolio drawdowns.
- Parameters:
- topint, optional
The number of largest drawdowns that will be reported. The default is 5.
- fancyBoolean, optional
- FalseThe drawdowns values are reported in unaltered
algebraic format.
- TrueThe drawdowns values are reported in percentage
rounded to 2 decimals.
The default is False.
- withcompBoolean, optional
If True, the portfolio components drawdowns are also reported. The default is False.
- componlyBoolean, optional
If True, only the portfolio components drawdowns are reported. The flag is active only if withcomp=True. The default is False.
- Returns:
- `panda.DataFrame`Table of drawdown events.
- Columns:
‘DD’ : drawdown rate
‘Date’ : recorded date of the drawdown
‘Star’ : start date of the drawdown
‘End’ : end date of the drawdown
- port_monthly_returns(withcomp=False, componly=False, fancy=False)¶
Portfolio monthly (calendar) rate of returns.
- Parameters:
- withcompBoolean, optional
If True, adds the portfolio components monthly returns to the report. The default is False.
- componlyBoolean, optional
If True, only the portfolio components monthly returns are reported. The flag is active only if withcomp=True. The default is False.
- fancyBoolean, optional
False : The rates are reported in unaltered algebraic format.
True : The rates are reported in percentage rounded to 2 decimals and presented is color style.
The default is False.
- Returns:
- `pandas.DataFrame`the report.
- port_perf(componly=False, fancy=False)¶
Brief description of portfolio and its components performances in terms of average historical rate of returns and maximum drawdowns.
- Parameters:
- componlyBoolean, optional
If True, only the portfolio components maximum drawdowns are reported. The default is False.
- fancyBoolean, optional
False : The rate of returns and drawdown values are reported in unaltered algebraic format.
True : The rate of returns and drawdown values are reported in percentage rounded to 2 decimals.
The default is False.
- Returns:
- `pandas.DataFrame`Performance information.
- Columns
‘RR’ : rate of returns
‘DD’ : maximum rate of drawdown
‘RoMaD’ : abs(RR/DD), Rate of Return over Maximum Drawdown
‘DD_date’ : recorder date of maximum drawdown
‘DD_start’ : start date of maximum drawdown
‘DD_end’ : end date of maximum drawdown
- port_period_perf(fancy=False)¶
Returns portfolio performance for each rolling period i.e. the rate of return, the rolling min and max returns, and max drawdown during the period.
- Parameters:
- fancyBoolean, optional
False: returns in algebraic form.
True: returns percentage rounded to 2 decimals.
- The default is `False`.
- Returns:
- pandas.DataFrame
‘Droll’ - indicates the start of the period.
‘RR’ - period rate of return.
‘RR_Min’ - minimum rolling rate of return in the period.
‘RR_Max’ - maximum rolling rate of return in the period.
‘DD_Max’ - maximum drawdown in the period.
‘RR_Min_Date’ - date of ‘RR_Min’.
‘RR_Max_Date’ - date of ‘RR_Max’.
‘DD_Max_Date’ - date of ‘DD_Max’.
- port_period_returns(fancy=False)¶
Computes the rolling periods rate of returns.
- Parameters:
- fancyBoolean, optional
False: returns in algebraic form.
True: returns percentage rounded to 2 decimals.
- The default is `False`.
- Returns:
- `pandas.DataFrame`The report.
Each rolling period is indicated by its start date, ‘Droll’. Included are the fixing data, ‘Dfix’, and the portfolio weights.
- port_quarterly_returns(withcomp=False, componly=False, fancy=False)¶
Portfolio quarterly (calendar) rate of returns.
- Parameters:
- withcompBoolean, optional
If True, adds the portfolio components monthly returns to the report. The default is False.
- componlyBoolean, optional
If True, only the portfolio components monthly returns are reported. The flag is active only if withcomp=True. The default is False.
- fancyBoolean, optional
False : The rates are reported in unaltered algebraic format.
True : The rates are reported in percentage rounded to 2 decimals and presented is color style.
The default is False.
- Returns:
- `pandas.DataFrame`the report.
- port_view(emas=[30, 200], bollinger=False, **opt)¶
Plots the portfolio time series together with optional technical indicators.
- Parameters:
- emaslist of int, optional
List of EMA durations. The default is [30, 200].
- bollingerBoolean, optional
If set True it adds the Bollinger bands. The default is False.
- **optother optional parameters
- fancyBoolean, optional
False : it uses the matplotlib capabilities.
True : it uses plotly library for interactive time-series view.
The default is False.
title : str, optional plot title. The default is ‘Port performance’.
xlabel : str, optional name of x-axis. The default is ‘date’.
ylabel : str; optional name of y-axis. The default is None.
- savetostr, optional
The name of the file where to save the plot. The default is None.
- Returns:
- `pandas.DataFrame`Contains the time-series included in plot.
- port_view_all(sdate=None, edate=None, componly=False, **opt)¶
Plots the portfolio and its component time-series on a relative basis.
- Parameters:
- sdatedate like, optional
Start date of plotted time-series. If it is set to None then the sdate is set to the earliest date in the time-series. The default is None.
- edatedate like, optional
End date of plotted time-series. If it set to None, then the edate is set to the most recent date of the time-series. The default is None.
- componlyBoolean, optional
True : only the portfolio components time-series are plotted.
False: the portfolio and its components times-series are plotted.
The default is True.
- **optother parameters
- fancyBoolean, optional
False : it uses the pandas plot (matplotlib) capabilities.
True : it uses plotly library for interactive time-series view.
The default is False.
title : str, optional plot title. The default is ‘Relative performance’.
xlabel : str, optional name of x-axis. The default is ‘date’.
ylabel : str; optional name of y-axis. The default is None.
- savetostr, optional
The name of the file where to save the plot. The default is None.
- Returns:
- `pandas.DataFrame`A Data Frame containing the time-series.
- set_model(alpha=[0.0], coef=None, rtype='Sharpe', mu=None, mu0=0, aversion=None, ww0=None, detrended=True, hlength=3.25, method='ecos', verbose=False)¶
Sets model parameters and evaluates portfolio time-series.
- Parameters:
- alphalist, optional
List of thresholds. The default is [0.].
- coeflist, optional
List of positive mixture coefficients. Note that len(coef) must be equal to len(alpha). A None value assumes an equal weighted risk mixture. The vector of coefficients will be normalized to unit. The default is None.
- rtypestr, optional
Optimization type. Possible values:
‘Risk’ : optimal-risk portfolio for targeted expected rate of return.
‘Sharpe’ : Sharpe-optimal portfolio - maximization solution.
‘Sharpe2’ : Sharpe-optimal portfolio - minimization solution.
‘MinRisk’ : minimum risk portfolio.
‘RiskAverse’ : optimal-risk portfolio for a fixed risk-aversion factor.
‘InvNrisk’ : optimal-risk portfolio with the same risk value as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘Diverse’ : optimal-diversified portfolio for targeted expected rate of return (maximum of inverse of 1-D).
‘Diverse2’ : optimal-diversified portfolio for targeted expected rate of return (minimum of 1-D).
‘MaxDiverse’ : maximum diversified portfolio.
‘InvNdiverse’ : optimal-diversified portfolio with the same diversification factor as a benchmark portfolio (e.g., same as equal weighted portfolio).
‘InvNdrr’ : optima- diversified portfolio with the same expected rate of return as a benchmark portfolio (e.g., same as equal weighted portfolio).
The default is ‘Sharpe’.
- mufloat, optional
Targeted portfolio expected rate of return. Relevant only if rtype=’Risk’ The default is None.
- mu0float, optional
Risk-free rate accessible to the investor. Relevant only if rtype=’Sharpe’ or rtype=’Sharpe2’. The default is 0.
- aversionfloat, optional
The value of the risk-aversion factor. Must be positive. Relevant only if rtype=’RiskAvers’. The default is None.
- ww0list (also numpy.array or pandas.Series), optional
Targeted portfolio weights. Relevant only if rtype=’InvNrisk’. Its length must be equal to the number of symbols in rrate (mktdata). All weights must be >= 0 with their sum > 0. If it is a list or a numpy.array then the weights are assumed to by in order of rrate.columns. If it is a pandas.Series then the index should be compatible with the rrate.columns or mktdata symbols (same symbols, not necessarily in the same order). If it is None then it will be set to equal weights. The default is None.
- detrendedBoolean, optional
If it set to True then the rates of return are detrended (mean=0). The default value is True.
- hlengthfloat, optional
The length in year of the historical calibration period relative to ‘Dfix’. A fractional number will be rounded to an integer number of months. The default is 3.25 years.
- methodstr, optional
Linear programming numerical method. Could be: ‘ecos’, ‘highs-ds’, ‘highs-ipm’, ‘highs’, ‘interior-point’, ‘glpk’ and ‘cvxopt’. The default is ‘ecos’.
- verboseBoolean, optional
If it set to True then it will print messages when the optimal portfolio degenerates to a single asset portfolio as a limited case. The default is False.
- Returns:
- `pandas.DataFrame`The portfolio time-series in the format ‘date’,
- ‘pcolname’.
Example Port_BTAD¶
# Examples
import pandas as pd
import azapy as az
print(f"azapy version {az.version()}", flush=True)
#==============================================================================
# Collect market data
mktdir = '../../MkTdata'
sdate = '2012-01-01'
edate = 'today'
symb = ['GLD', 'TLT', 'XLV', 'IHI', 'VGT', 'OIH']
mktdata = az.readMkT(symb, sdate=sdate, edate=edate, file_dir=mktdir)
#==============================================================================
# Define mBTAD measure parameters alpha and coef
alpha = [-0.01, 0.0, +0.01]
coef = [1.] * len(alpha)
detrended = True
hlength = 1.25
portname = 'mBTAD'
# set Port_BTAD class
p4 = az.Port_BTAD(mktdata, pname=portname)
#==============================================================================
# Beyond this point any section can be run independently
#==============================================================================
# Sharpe optimal portfolio for 0 risk free rate
rtype = 'Sharpe'
mu0 = 0.
port4 = p4.set_model(alpha=alpha, coef=coef, rtype=rtype, mu0=mu0,
detrended=detrended, hlength=hlength)
# plots
_ = p4.port_view(title=portname + "-Sharpe", ylabel="price ($)")
_ = p4.port_view_all(title=portname + " Portfolio", ylabel="relative move")
# performance monitoring
performance = p4.port_perf()
drawdowns = p4.port_perf()
aret = p4.port_annual_returns()
qret = p4.port_quarterly_returns()
mret = p4.port_monthly_returns()
pret = p4.port_period_returns()
with pd.option_context('display.max_columns', None):
print(f"Performance\n{performance.round(4)}")
print(f"Portfolio Historical Drawdowns\n{drawdowns.round(4)}")
print(f"Portfolio Annual Returns\n{aret.round(4)}")
print(f"Portfolio Quarterly Returns\n{qret.round(4)}")
print(f"Portfolio Monthly Returns\n{mret.round(4)}")
print(f"Portfolio Period Returns\n{pret.round(2)}")
# accounting information
ww = p4.get_weights()
nshares = p4.get_nshares()
accinfo = p4.get_account()
with pd.option_context('display.max_columns', None):
print(f"Portfolio Historical Weights\n{ww.round(4)}")
print(f"Portfolio Numbers of Shares\n{nshares}")
print(f"Portfolio Rolling Accounting Information\n{accinfo.round(0)}")
#==============================================================================
# compare several standard strategies with equal weighted portfolio
# MaxDiverse - maximum diversified portfolio
# MinRisk - minimum risk portfolio
# Sharpe - maximum generalized Sharpe portfolio
# InvNrisk - optimal-risk portfolio with same risk as EWP
# InvNdrr - optimal-diversified portfolio with
# same expected rate of return as EWP
# InvNdiverse - optimal-diversified portfolio with
# same diversification factor as EWP
rtypes = ['MaxDiverse', 'MinRisk', 'Sharpe',
'InvNrisk', 'InvNdrr', 'InvNdiverse']
port = []
for rtype in rtypes:
port4 = p4.set_model(alpha=alpha, coef=coef, rtype=rtype,
detrended=detrended, hlength=hlength)
port4.columns = [rtype]
port.append(port4)
# add EWP (1/N portfolio)
p5 = az.Port_ConstW(mktdata, pname="1/N")
port5 = p5.set_model()
port.append(port5)
# compare
pp = az.Port_Simple(port)
_ = pp.set_model()
_ = pp.port_view_all(componly=True,
title=portname + " Portfolios - Relative Performance",
xlabel="year")
# compare performances
perfs = pp.port_perf(componly=True)
print(f"Portfolio Performances\n{perfs.round(4)}")
arets = pp.port_annual_returns(withcomp=True, componly=True)
print(f"Annual Returns\n{arets.round(4) * 100}")
#==============================================================================
# Other examples
# Optimal-risk portfolio for fixed aversion factor
rtype = 'RiskAverse'
aversion = 0.4
port4 = p4.set_model(alpha=alpha, coef=coef, rtype=rtype, aversion=aversion,
detrended=detrended, hlength=hlength)
# plots
_ = p4.port_view(title=portname + " aversion = " + str(aversion),
ylabel="price ($)")
_ = p4.port_view_all(title=portname + " Portfolio", ylabel="relative move")
# performance monitoring
performance = p4.port_perf()
drawdowns = p4.port_perf()
aret = p4.port_annual_returns()
qret = p4.port_quarterly_returns()
mret = p4.port_monthly_returns()
pret = p4.port_period_returns()
with pd.option_context('display.max_columns', None):
print(f"Performance\n{performance.round(4)}")
print(f"Portfolio Historical Drawdowns\n{drawdowns.round(4)}")
print(f"Portfolio Annual Returns\n{aret.round(4)}")
print(f"Portfolio Quarterly Returns\n{qret.round(4)}")
print(f"Portfolio Monthly Returns\n{mret.round(4)}")
print(f"Portfolio Period Returns\n{pret.round(2)}")
# accounting information
ww = p4.get_weights()
nshares = p4.get_nshares()
accinfo = p4.get_account()
with pd.option_context('display.max_columns', None):
print(f"Portfolio Historical Weights\n{ww.round(4)}")
print(f"Portfolio Numbers of Shares\n{nshares}")
print(f"Portfolio Rolling Accounting Information\n{accinfo.round(0)}")
#==============================================================================
# Optimal-risk portfolio for targeted expected rate of return
rtype = 'Risk'
mu = 0.06
port4 = p4.set_model(alpha=alpha, coef=coef, rtype=rtype, mu=mu,
detrended=detrended, hlength=hlength)
# plots
_ = p4.port_view(title=portname + " - Optimal Risk for mu = " + str(mu),
ylabel="price ($)")
_ = p4.port_view_all(title=portname + " Portfolio", ylabel="relative move")
# performance monitoring
performance = p4.port_perf()
drawdowns = p4.port_perf()
aret = p4.port_annual_returns()
qret = p4.port_quarterly_returns()
mret = p4.port_monthly_returns()
pret = p4.port_period_returns()
with pd.option_context('display.max_columns', None):
print(f"Performance\n{performance.round(4)}")
print(f"Portfolio Historical Drawdowns\n{drawdowns.round(4)}")
print(f"Portfolio Annual Returns\n{aret.round(4)}")
print(f"Portfolio Quarterly Returns\n{qret.round(4)}")
print(f"Portfolio Monthly Returns\n{mret.round(4)}")
print(f"Portfolio Period Returns\n{pret.round(2)}")
# accounting information
ww = p4.get_weights()
nshares = p4.get_nshares()
accinfo = p4.get_account()
with pd.option_context('display.max_columns', None):
print(f"Portfolio Historical Weights\n{ww.round(4)}")
print(f"Portfolio Numbers of Shares\n{nshares}")
print(f"Portfolio Rolling Accounting Information\n{accinfo.round(0)}")
#==============================================================================
# Optimal-diversified portfolio for targeted expected rate of return
rtype = 'Diverse'
mu = 0.06
port4 = p4.set_model(alpha=alpha, coef=coef, rtype=rtype, mu=mu,
detrended=detrended, hlength=hlength)
# plots
_ = p4.port_view(title=portname + " - Optimal Diverse. for mu = " + str(mu),
ylabel="price ($)")
_ = p4.port_view_all(title=portname + " Portfolio", ylabel="relative move")
# performance monitoring
performance = p4.port_perf()
drawdowns = p4.port_perf()
aret = p4.port_annual_returns()
qret = p4.port_quarterly_returns()
mret = p4.port_monthly_returns()
pret = p4.port_period_returns()
with pd.option_context('display.max_columns', None):
print(f"Performance\n{performance.round(4)}")
print(f"Portfolio Historical Drawdowns\n{drawdowns.round(4)}")
print(f"Portfolio Annual Returns\n{aret.round(4)}")
print(f"Portfolio Quarterly Returns\n{qret.round(4)}")
print(f"Portfolio Monthly Returns\n{mret.round(4)}")
print(f"Portfolio Period Returns\n{pret.round(2)}")
# accounting information
ww = p4.get_weights()
nshares = p4.get_nshares()
accinfo = p4.get_account()
with pd.option_context('display.max_columns', None):
print(f"Portfolio Historical Weights\n{ww.round(4)}")
print(f"Portfolio Numbers of Shares\n{nshares}")
print(f"Portfolio Rolling Accounting Information\n{accinfo.round(0)}")
#==============================================================================