# Kelly optimal portfolio¶

Kelly optimal portfolio is named after John Larry Kelly Jr. (1923-1965) the author of Kelly criterion for betting on favorable gambling games.

To illustrate Kelly criterion let’s examine a simple coin tossing game. In this game you are allowed to bet any portion of your capital on hands on the outcome of the tossing. You may bet repeatedly until either you go bankrupt, or you get bored . We also assume that the coin is unfair. And you know that the probability to get Heads, say $$p=60\%$$. The question is how much you should bet on each coin tossing.

Since the probability of getting Heads is bigger than $$50\%$$, you will always bet on the Heads with no exceptions. However, you still need to determine how much you should bet. Certainly, not betting at all will not increase your capital. On the other hand, betting the entire capital in all instances will certainly lead to bankruptcy.

Kelly criterion provides an optimal solution to this problem. It consists in choosing the betting size that maximizes the expectation of the log returns of the game.

In this case, the maximization can be carried out analytically. It is a straightforward computation. The result is that the optimal betting size must be $$2p-1$$ times the capital on hands, provided that $$p \ge 50\%$$, and $$0$$ otherwise. This strategy guaranties that we will never go bankrupt, and our capital may increase unlimited as we play (if $$p \ge 50\%$$).

Things are a bit more complicated if for example there are $$N$$ simultaneous uncorrelated tossing coin games like the one described above. And we want to figure out a betting strategy in all $$N$$ games. In this case the maximization doesn’t have an analytical solution, but it can be computed numerically.

azapy provides a simple function that performs this computation,

gamblingKelly(pp=[0.6])


where pp is a list of probabilities to get Heads in each of the $$N$$ games. The default is [0.6]. The function returns a list with the fractions of capital on hands that must be bet in each of the $$N$$ games.

Example:

import azapy as az

# 3 independent games - probabilities to get Heads
p = [0.55, 0.6, 0.65]

ww = az.gamblingKelly(p)

# bet sizes for each game as percentage of capital in hand
print(f"bet sizes as a fraction of capital (in percent)\n{ww}")

# percentage of the total capital invested in each round
print(f"total fraction of capital invested in all games (in percent): {ww.sum()}")


Kelly optimal portfolio is constructed based on the above Kelly criterion. The optimal portfolio weights are maximizing the expectation of the portfolio log returns. Mathematically, the objective function subject to maximization is given by,

$\begin{equation*} Z = {\bf E}\left[\ln\left(1 + \sum_{k=1}^M w_k r_k \right)\right] \end{equation*}$

where:

• $$M$$ is the number of portfolio components,

• $$\{w_k\}_{k=1,\cdots,M}$$ are the weights,

• $$\{r_k\}_{i=1,\cdots,M}$$ are the asset rate of returns.

The maximization can be solved directly as a convex non-linear problem (a relatively slow procedure) or it can be reformulated as an exponential cone constraint programming problem. An approximate solution can be obtained considering the second order Taylor expansion of $$Z$$. In this case the maximization is reduced to a quadratic programming (QP) problem that can be solved numerically very efficiently. In general, the optimal portfolio weights under this approximation can be slightly different than the weights obtained by solving the “Full” optimization problem. However, the performances of the two portfolios (based on “Full” optimization and second order, “Order2”, approximation) can be very close.

From a computational point of view, the second order approximation, involving a QP solver, is the fastest, followed by exponential cone.

Our implementation supports the following methods:

• exponential cone optimization for full Kelly problem, rtype='ExpCone',

• non-linear convex optimization, rtype='Full',

• second order Taylor approximation, rtype='Order2', using either ecos or cvxopt based QP solvers.

There are 2 support classes:

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

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

TOP

## KellyEngine class¶

class azapy.Engines.KellyEngine.KellyEngine(mktdata=None, colname='adjusted', freq='Q', hlength=3.25, name='Kelly', rtype='ExpCone', method='ecos')

Bases: _RiskEngine

Kelly optimal portfolio.

Attributes
• status : int - computation status (0 - success, any other value indicates an error)

• ww : pandas.Series - portfolio weights

• name : str - portfolio name

Methods

 getPositions([nshares, cash, ww, verbose]) Computes the rebalanced number of shares. getWeights([rtype, method, mktdata]) Computes the Kelly optimal weights. set_mktdata(mktdata[, colname, freq, ...]) Sets historical market data. set_rrate(rrate) Sets portfolio components historical rates of return in the format "date", "symbol1", "symbol2", etc. set_rtype(rtype) Sets the model approximation level.
__init__(mktdata=None, colname='adjusted', freq='Q', hlength=3.25, name='Kelly', rtype='ExpCone', method='ecos')

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. Default value is ‘Kelly’.

rtypestr, optional

Optimization approximation. It can be:

‘ExpCone’ - exponential cone constraint programming solver for original Kelly problem.

‘Full’ - non-linear solver for original Kelly problem.

‘Order2’ - second order Taylor approximation of original Kelly problem. It is a QP problem.

The default is ‘ecos’.

methodstr, optional

The QP solver class. It is relevant only if rtype=’Order2’. It takes 2 values: ‘ecos’ or ‘cvxopt’. The default is ‘ecos’.

Returns:
The object.
getPositions(nshares=None, cash=0.0, ww=None, verbose=True)

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.

verboseBoolean, optional

Is it set to True the function prints the closing prices date. The default is True.

Returns:
pandas.DataFramethe 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 rebalances 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.

getWeights(rtype=None, method=None, mktdata=None, **params)

Computes the Kelly optimal weights.

Parameters:
rtypestr, optional

Optimization approximation. It can be:

‘ExpCone’ - exponential cone constraint programming solver for original Kelly problem.

‘Full’ - non-linear solver for original Kelly problem.

‘Order2’ - second order Taylor approximation of original Kelly problem. It is a QP problem.

A value different than None will overwrite the value for rtype set in the constructor.

The default is None.

methodstr, optional

The QP solver class. It is relevant only if rtype=’Order2’. It takes 2 values: ‘ecos’ or ‘cvxopt’. A value different than None will overwrite the value set in the constructor. 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.readMkT function.

Returns:
pandas.SeriesPortfolio weights per symbol.

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

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.

Returns:
None
set_rrate(rrate)

Sets portfolio components historical rates of return in the format “date”, “symbol1”, “symbol2”, etc.

Parameters:
rratepandas.DataFrame

The portfolio components historical rates of return. If it is not None, it will overwrite the rrate computed in the constructor from mktdata. The default is None.

Returns:
None
set_rtype(rtype)

Sets the model approximation level.

Parameters:
rtypestr

It could be:

• ‘Full’ for a non-linear (no approximation) model,

• ‘ExpCone’ for exponential cone constraint programming solver,

• ‘Order2’ for a second order Taylor approximation (a QP problem).

It will overwrite the value set by the constructor.

Returns:
None

TOP

## Example KellyEngine¶

# 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', 'SPY']

mktdata = az.readMkT(symb, sdate=sdate, edate=edate, file_dir=mktdir)

#==============================================================================
# set approximation level
# the levels are:
#  - 'Full' no approximation (non-linear convex optimization)
#  - 'Order2' for second order Taylor approximation (QP problem)
#  - 'ExpCone' no aproximation (exponential cone programming)
rtype1 = 'Full'
rtype2 = 'Order2'
rtype3 = 'ExpCone'

#==============================================================================
# example: weights evaluation
hl = 1.25

cr1 = az.KellyEngine(mktdata, rtype=rtype1, hlength=hl)
ww1 = cr1.getWeights()
print(f"{rtype1}: time {cr1.time_level1}")

cr2 = az.KellyEngine(mktdata, rtype=rtype2, hlength=hl)
ww2 = cr2.getWeights()
print(f"{rtype2}: time {cr2.time_level1}")

cr3 = az.KellyEngine(mktdata, rtype=rtype3, hlength=hl)
ww3 = cr3.getWeights()
print(f"{rtype3}: time {cr3.time_level1}")

wwcomp = pd.DataFrame({'Full': ww1.round(6),
'Order2': ww2.round(6),
'ExpCone': ww3.round(6)})
print(f"weights comparison\n {wwcomp.round(4)}")

#==============================================================================
# Example of rebalancing positions
# existing positions and cash
ns = pd.Series(100, index=symb)
cash = 0.

# new positions and rolling info
pos1 = cr1.getPositions(nshares=ns, cash=0.)
print(f" Full: New position report\n {pos1}")

pos2 = cr2.getPositions(nshares=ns, cash=0.)
print(f" Order2: New position report\n {pos2}")

pos3 = cr3.getPositions(nshares=ns, cash=0.)
print(f" ExpCone: New position report\n {pos3}")


TOP

## Port_Kelly class¶

class azapy.PortOpt.Port_Kelly.Port_Kelly(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)

Bases: _Port_Generator

Backtesting Kelly portfolio periodically rebalanced.

Attributes
• pname : str - portfolio name

• ww : pandasDataFrame - portfolio weights at each rebalancing date

• port : pandas.Series - portfolio historical time-series

• schedule : pandas.DataFrame - rebalancing schedule

The most important method is set_model. It must be called before any other method.

Methods

 get_account([fancy]) Returns additional bookkeeping information regarding rebalancing (e.g., residual cash due rounding number of shares, previous period dividend cash accumulation, etc.) Returns the actual market data used for portfolio evaluations. Returns the number of shares held after each rolling date. 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]) 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_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, hlength, method, verbose]) 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)

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.

If it is True, then the rebalancing weights will be computed concurrent. 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.DataFrameReports, 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.DataFramemarket data.
get_nshares()

Returns the number of shares held after each rolling date.

Returns:
pandas.DataFramenumber of shares per symbol.
get_port()

Returns the portfolio time-series.

Returns:
pandas.DataFrameportfolio 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.DataFrameportfolio 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.DataFramethe report.
port_drawdown(top=5, fancy=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.

Returns:
panda.DataFrameTable 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.DataFramethe 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.DataFramePerformance 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.DataFrameThe report.

Each rolling period is indicated by its start date, ‘Droll’. Included are the fixing data, ‘Dfix’, and the portfolio weights.

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.DataFrameContains 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.DataFrameA Data Frame containing the time-series.
set_model(rtype='ExpCone', hlength=1.25, method='ecos', verbose=False)

Sets model parameters and evaluates portfolio time-series.

Parameters:
rtypestr, optional

Type of optimization. It could take the values:

‘ExpCone’ - Exponential cone constraint programming solution for full Kelly problem.

‘Full’ - Non-linear solver for full Kelly problem.

‘Order2’ - Second order Taylor approximation of Kelly problem.

The default is ‘ExpCone’.

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

methodstr, optional

The QP solver class. It is relevant only if rtype=’Order2’. It takes 2 values: ‘ecos’ or ‘cvxopt’. The default is ‘ecos’.

verboseBoolean, optional

Sets verbose mode. The default is False.

Returns:
pandas.DataFrameThe portfolio time-series in the format ‘date’,
‘pcolname’.

TOP

## Example Port_Kelly¶

# Examples
import time
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)

#=============================================================================
# Compute optimal Kelly portfolio
# exponetial cone constraint programming solution - default
p4 = az.Port_Kelly(mktdata, pname='KellyPort')

tic = time.perf_counter()
port4 = p4.set_model()
toc = time.perf_counter()
print(f"time Exp Cone full Kelly problem: {toc-tic:f}")

ww = p4.get_weights()
_ = p4.port_view()
_ = p4.port_view_all()
performance = p4.port_perf()
drawdowns = p4.port_perf()
aret = p4.port_annual_returns()
mret = p4.port_monthly_returns()
pret = p4.port_period_returns()
nsh = p4.get_nshares()
acc = p4.get_account(fancy=True)
with pd.option_context('display.max_columns', None):
print(f"Weights\n{ww.round(4)}")
print(f"Performace\n{performance.round(4)}")
print(f"Portfolio Historical Drowdawns\n{drawdowns.round(4)}")
print(f"Portfolio Annual Returns\n{aret.round(4)}")
print(f"Portfolio Monthly Returns\n{mret.round(4)}")
print(f"Portfolio Period Returns\n{pret.round(2)}")
print(f"Numbers of Shares Invested\n{nsh}")
print(f"Accontinf Info\n{acc}")

# Test using the Port_Rebalanced weights = ww (from above)
p2 = az.Port_Rebalanced(mktdata, pname='TestPort')
port2  = p2.set_model(ww)

# Compare - must be identical
port4.merge(port2, how='left', on='date').plot()

#=============================================================================
# Compare with second order Taylor approximation of Kelly problem
print(f"time Exp Cone full Kelly problem: {toc-tic:f}")

p5 = az.Port_Kelly(mktdata, pname='KellyApxPort-ecos')

tic = time.perf_counter()
port5 = p5.set_model(rtype='Order2')
toc = time.perf_counter()
print(f"time 2-nd order aprox Kelly problem with ecos: {toc-tic:f}")

# Compare with second order Taylor approximation of Kelly problem
p6 = az.Port_Kelly(mktdata, pname='KellyApxPort-cvxopt')

tic = time.perf_counter()
port6 = p6.set_model(rtype='Order2', method='cvxopt')
toc = time.perf_counter()
print(f"time 2-nd order aprox Kelly problem wint cvxopt: {toc-tic:f}")

# Compare with non-linear solution of full Kelly problem
p7 = az.Port_Kelly(mktdata, pname='KellyFull')

tic = time.perf_counter()
port7 = p7.set_model(rtype='Full')
toc = time.perf_counter()
print(f"time non-linear full Kelly problem: {toc-tic:f}")

# The results are very close
pp = az.Port_Simple([port4, port5, port6, port7])
_ = pp.set_model()
_ = pp.port_view_all(componly=True)


TOP