mMV-based optimal portfolios

MV stands for Mean Variance. The MV optimal portfolio was introduced by the economist H.M. Markowitz in 1952. It was the main body of work that later had triggered the development of Modern Portfolio Theory (MPT).

Variance is the dispersion measure for MV-based optimal portfolio models, i.e.,

\[\begin{equation*} {\rm VAR} = w^T C w, \end{equation*}\]

where:

  • \(w\) is the vector of portfolio weights,

  • \(C\) is the covariance matrix between portfolio components.

Note: In our case \(C\) is estimated from historical observations of portfolio components rate of return.

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 MV-Sharpe ratio,

  • Minimization of the inverse of MV-Sharpe ratio,

  • 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:

  • MVAnalyzer: computes the portfolio weights and performs in-sample analysis,

  • Port_MV : performs portfolio backtesting, out-of-sample analysis.

TOP

MVAnalyzer class

class azapy.Analyzers.MVAnalyzer.MVAnalyzer(mktdata=None, colname='adjusted', freq='Q', hlength=3.25, name='MV', rtype='Sharpe', mu=None, d=1, mu0=0, aversion=None, ww0=None, method='ecos', verbose=False)

Bases: _RiskAnalyzer

MV (Mean-Variance) - Variance 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 MV risk

  • primary_risk_comp : list - redundant (single element list containing MV risk value)

  • secondary_risk_comp : list - redundant (same as primary_risk_comp)

  • sharpe : float - MV-Sharpe 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, primary_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.

getRiskComp()

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__(mktdata=None, colname='adjusted', freq='Q', hlength=3.25, name='MV', rtype='Sharpe', mu=None, d=1, mu0=0, aversion=None, ww0=None, method='ecos', verbose=False)

Constructor

Parameters:
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 ‘MV’.

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

methodstr, optional

Quadratic programming numerical method. Could be ‘ecos’ or ‘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 asset symbols and indexed by observation dates.

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.

TOP

Example MVAnalyzer

# 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 MV parameters 
title_plot = 'MV frontiers'
hlength = 3.25
method = 'ecos' # default choice

# build the analyzer object
cr1 = az.MVAnalyzer(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, verbose=True)

# 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)

#==============================================================================

TOP

Port_MV class

class azapy.PortOpt.Port_MV.Port_MV(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 MV (mean variance) 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.)

get_mktdata()

Returns the actual market data used for portfolio evaluations.

get_nshares()

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([rtype, mu, mu0, aversion, ww0, ...])

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.
get_nshares()

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(rtype='Sharpe', mu=None, mu0=0, aversion=None, ww0=None, hlength=3.25, method='ecos', verbose=False)

Sets model parameters and evaluates portfolio time-series.

Parameters:
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.

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

Quadratic programming numerical method. Could be ‘ecos’ or ‘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’.

TOP

Example Port_MV

# 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 MV measure parameters alpha and coef
hlength = 1.25
portname = 'MV'

# set Port_MV class
p4 = az.Port_MV(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(rtype=rtype, mu0=mu0, 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(rtype=rtype, 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(rtype=rtype, aversion=aversion, 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(rtype=rtype, mu=mu, 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(rtype=rtype, mu=mu, 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)}")
    
#==============================================================================

TOP