Constant weighted portfolio

Portfolio with constant weights periodically rebalanced.

A remarkable member of this class is EWP (Equal Weighted Portfolio). It is a popular benchmark to assess a portfolio’s performance.

Relative to a risk based optimal portfolio, the equal weighted portfolio is always inefficient. It means that in-sample there is always an efficient portfolio that has the same risk profile but a higher expected rate of returns than the equal weighted portfolio. However, out-of-sample the equal weighted portfolio may outperform this efficient portfolio. This odd effect occurs for many reasonable portfolio compositions under rather normal market conditions. Therefore, it is always advisable to compare the performance of a portfolio optimization strategy with the performance of equal weighted portfolio. An example is presented in this Jupyter note.

Note: Constant weights should not be confused with constant number of shares. Constant number of shares leads to a Buy and Hold type of portfolio, where the number of shares on each portfolio component is kept constant during the life of the investment. A portfolio with constant weights assumes that the fraction of capital invested in each portfolio component is constant after each rebalancing event.

There is 1 support class:

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

TOP

Port_ConstW class

class azapy.PortOpt.Port_ConstW.Port_ConstW(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 Constant Weighted 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([ww, verbose])

Sets model weights.

__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(ww=None, verbose=False)

Sets model weights.

Parameters:
wwpandas.Series, optional

Portfolio weights per symbol. If it is set to None then the EWP (Equal Weighted Portfolio) will be considered. The default is None.

verboseBoolean, optional

Sets verbose mode. The default is False.

Returns:
`pandas.DataFrame`Portfolio time-series in the format ‘date’,
‘pcolname’.

TOP

Example Port_ConstW

# Examples
import pandas as pd
import time
import azapy as az

#=============================================================================
# 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 some weights
ww = pd.Series(1./len(symb), index=symb)

#=============================================================================
# Compute portfolio

p3 = az.Port_ConstW(mktdata, pname='ConstW')

tic = time.perf_counter()
port3  = p3.set_model(ww)    
toc = time.perf_counter()
print(f"time to get port: {toc-tic:f}")

_ = p3.port_view()
_ = p3.port_view_all() 
drawdown = p3.port_drawdown(fancy=True)
perf = p3.port_perf(fancy=True)
annual = p3.port_annual_returns()
quarterly = p3.port_quarterly_returns()
monthly = p3.port_monthly_returns()
period = p3.port_period_returns()
nsh = p3.get_nshares()
acc = p3.get_account(fancy=True)

with pd.option_context('display.max_columns', None):
    print(f"Portfolio Drawdown\n{drawdown}")
    print(f"Portfolio performance\n{perf}")
    print(f"Annual Returns\n{annual}")
    print(f"Quarterly Returns\n{quarterly}")
    print(f"Monthly Returns\n{monthly}")
    print(f"Investment Period Returns\n{period.round(4)}")
    print(f"Number of Shares invested\n{nsh}")
    print(f"Accounting Info\n{acc}")

#=============================================================================
# Test: compare to an equivalent Port_Rebalanced
# Setup Port_Rebalanced
# Build weights schedule
wwr = az.schedule_offset(sdate=sdate, edate=edate, freq='Q')

for sy in symb:
    wwr[sy] = [1./len(symb)] * len(wwr)

# Compute Port_Rebalanced
p2 = az.Port_Rebalanced(mktdata, pname='TestPort')
port2  = p2.set_model(wwr)    

# must be identical   
pp = az.Port_Simple([port2, port3])
_ = pp.set_model()
_ = pp.port_view_all(componly=True)

TOP