Extra material: Optimal Growth Portfolios with Risk Aversion#

Among the reasons why Kelly was neglected by investors were high profile critiques by the most famous economist of the 20th Century, Paul Samuelson. Samuelson objected on several grounds, among them is a lack of risk aversion that results in large bets and risky short term behavior, and that Kelly’s result is applicable to only one of many utility functions that describe investor preferences. The controversy didn’t end there, however, as other academic economists, including Harry Markowitz, and practitioners found ways to adapt the Kelly criterion to investment funds.

This notebook presents solutions to Kelly’s problem for optimal growth portfolios using exponential cones. A significant feature of this notebook is the inclusion of a risk constraint recently proposed by Boyd and coworkers. These notes are based on recent papers such as Cajas (2021), Busseti, Ryu and Boyd (2016), Fu, Narasimhan, and Boyd (2017). Additional bibliographic notes are provided at the end of the notebook.

# install dependencies and select solver
%pip install -q amplpy numpy pandas matplotlib

SOLVER_CONIC = "mosek"  # ipopt, mosek, knitro
SOLVER_NLO = "ipopt"

from amplpy import AMPL, ampl_notebook

ampl = ampl_notebook(
    modules=["coin", "mosek"],  # modules to install
    license_uuid="default",  # license to use
)  # instantiate AMPL object and register notebook magic

Financial Data#

We begin by reading historical prices for a selected set of trading symbols using yfinance.

While it would be interesting to include an international selection of financial indices and assets, differences in trading and bank holidays would involve more elaborate coding. For that reason, the following cell has been restricted to indices and assets trading in U.S. markets.

# run this cell to install yfinance
%pip install yfinance --upgrade -q
import matplotlib.pyplot as plt
import numpy as np

import pandas as pd
import datetime
import yfinance as yf

# symbols as used by Yahoo Finance
symbols = {
    # selected indices
    "^GSPC": "S&P 500",
    "^IXIC": "Nasdaq",
    "^DJI": "Dow Jones Industrial",
    "^RUT": "Russell 2000",
    # selected stocks
    "AXP": "American Express",
    "AMGN": "Amgen",
    "AAPL": "Apple",
    "BA": "Boeing",
    "CAT": "Caterpillar",
    "CVX": "Chevron",
    "JPM": "JPMorgan Chase",
    "MCD": "McDonald's",
    "MMM": "3 M",
    "MSFT": "Microsoft",
    "PG": "Proctor & Gamble",
    "XOM": "ExxonMobil",
}

# years of testing and training data
n_test = 1
n_train = 2

# get today's date
today = datetime.datetime.today().date()

# Fix the day for consistent output and testing
today = datetime.datetime(2023, 6, 6).date()

# training data dates
end = today - datetime.timedelta(int(n_test * 365))
start = end - datetime.timedelta(int((n_test + n_train) * 365))

# get training data
S = yf.download(list(symbols.keys()), start=start, end=end, auto_adjust=True)["Close"]

# compute gross returns
R = S / S.shift(1)
R.dropna(inplace=True)
[*********************100%***********************]  16 of 16 completed
# plot
fig, ax = plt.subplots(2, 1, figsize=(8, 6), sharex=True)

S.divide(S.iloc[0] / 100).plot(ax=ax[0], grid=True, title="Normalized Prices")
ax[0].legend(loc="center left", bbox_to_anchor=(1.0, 0.5), prop={"size": 8})

R.plot(ax=ax[1], grid=True, title="Gross Returns", alpha=0.5).legend([])

fig.tight_layout()
../../_images/c0657e14a69577851037334ce5578eea63471fa2b14cefc952a5e00396300718.png

Portfolio Design for Optimal Growth#

Model#

Here we are examining a set \(N\) of financial assets trading in efficient markets. The historical record consists of a matrix \(R \in \mathbb{R}^{T\times N}\) of gross returns where \(T\) is the number of observations.

The weights \(w_n \geq 0\) for \(n\in N\) denote the fraction of the portfolio invested in asset \(n\). Any portion of the portfolio not invested in traded assets is assumed to have a gross risk-free return \(R_f = 1 + r_f\), where \(r_f\) is the return on a risk-free asset.

Assuming the gross returns are independent and identically distributed random variables, and the historical data set is representative of future returns, the investment model becomes

\[\begin{split} \begin{align} \max_{w_n \geq 0}\quad & \frac{1}{T} \sum_{t\in T} \log(R_t) \\ \text{s.t.}\quad \\ & R_t = R_f + \sum_{n\in N} w_n (R_{t, n} - R_f) & \forall t\in T\\ \end{align} \end{split}\]

Note this formulation allows the sum of weights \(\sum_{n\in N} w_n\) to be greater than one. In that case the investor would be investing more than the value of the portfolio in traded assets. In other words the investor would be creating a leveraged portfolio by borrowing money at a rate \(R_f\). To incorporate a constraint on the degree of leveraging, we introduce a constraint

\[\sum_{n\in N} w_n \leq E_M\]

where \(E_M\) is the “equity multiplier.” A value \(E_M \leq 1\) restricts the total investment to be less than or equal to the equity available to the investor. A value \(E_M > 1\) allows the investor to leverage the available equity by borrowing money at a gross rate \(R_f = 1 + r_f\).

Using techniques demonstrated in other examples, this model can be reformulated with exponential cones.

\[\begin{split} \begin{align} \max_{w_n}\quad & \frac{1}{T} \sum_{t\in T} q_t \\ \text{s.t.}\quad & (R_f + \sum_{n\in N}w_n (R_{t,n} - R_f), 1, q_t) \in K_{exp} & \forall t \in T \\ & \sum_{n\in N} w_n \leq E_M \\ & w_n \geq 0 & \forall n\in N \\ \end{align} \end{split}\]

For the risk constrained case, we consider a constraint

\[\mathbb{E}[R^{-\lambda}] \leq R_f^{-\lambda}\]

where \(\lambda\) is a risk aversion parameter. Assuming the historical returns are equiprobable

\[\frac{1}{T} \sum_{t\in T} R_t^{-\lambda} \leq R_f^{-\lambda}\]

The risk constraint is satisfied for any \(w_n\) if the risk aversion parameter \(\lambda=0\). For any value \(\lambda > 0\) the risk constraint has a feasible solution \(w_n=0\) for all \(n \in N\). Recasting as a sum of exponentials,

\[\frac{1}{T} \sum_{t\in T} e^{- \lambda\log(R_t)} \leq R_f^{-\lambda}\]

Using the \(q_t \leq \log(R_t)\) as used in the examples above, and \(u_t \geq e^{- \lambda q_t}\), we get the risk constrained model optimal log growth.

Given a risk-free rate of return \(R_f\), a maximum equity multiplier \(E_M\), and value \(\lambda \geq 0\) for the risk aversion, risk constrained Kelly portfolio is given the solution to

\[\begin{split} \begin{align} \max_{w_n, q_t, u_t}\quad & \frac{1}{T} \sum_{t\in T} q_t \\ \text{s.t.}\quad & \frac{1}{T} \sum_{t\in T} u_t \leq R_f^{-\lambda} \\ & (u_t, 1, \lambda q_t) \in K_{exp} & \forall t\in T \\ & (R_f + \sum_{n\in N}w_n (R_{t,n} - R_f), 1, q_t) \in K_{exp} & \forall t \in T \\ & \sum_{n\in N} w_n \leq E_M \\ & w_n \geq 0 & \forall n \in N \\ \end{align} \end{split}\]

The following cells demonstrate an AMPL implementation of the model with the Mosek solver.

AMPL Implementation#

The AMPL implementation for the risk-constrained Kelly portfolio accepts three parameters, the risk-free gross returns \(R_f\), the maximum equity multiplier, and the risk-aversion parameter.

%%writefile kelly_portfolio.mod

param Rf;
param EM;
param lambd;

# index lists
set T;
set N;
param Rloc{T, N};

# decision variables
var q{T};
var w{N} >= 0;

# objective
maximize ElogR:
    sum {t in T} q[t] / card(T);

# conic constraints on return
var R{t in T}
    = Rf + sum {n in N} w[n]*(Rloc[t, n] - Rf);
s.t. C{t in T}:
    R[t] >= exp(q[t]);

# risk constraints
var u{T};
s.t. USum:
    sum {t in T} u[t] / card(T) <= Rf**(-lambd);
s.t. RT{t in T}:
    u[t] >= exp( -lambd*q[t] );

# equity multiplier constraint
s.t. WSum:
    sum {n in N} w[n] <= EM;
Overwriting kelly_portfolio.mod
def kelly_portfolio(R, Rf=1, EM=1, lambd=0):
    ampl = AMPL()
    ampl.read("kelly_portfolio.mod")

    ampl.param["Rf"] = Rf
    ampl.param["EM"] = EM
    ampl.param["lambd"] = lambd

    # index lists
    ampl.set["T"] = [str(t) for t in R.index]
    ampl.set["N"] = [str(n) for n in R.columns]
    ampl.param["Rloc"] = {
        (str(t), str(n)): R.at[t, n]
        for i, t in enumerate(R.index)
        for j, n in enumerate(R.columns)
    }

    ampl.solve(solver=SOLVER_CONIC)
    assert ampl.solve_result == "solved", ampl.solve_result

    return ampl
def kelly_report(ampl):
    # print report
    s = f"""
Risk Free Return = {100*(np.exp(252*np.log(ampl.get_value('Rf'))) - 1):0.2f}
Equity Multiplier Limit = {ampl.get_value('EM'):0.5f}
Risk Aversion = {ampl.get_value('lambd'):0.5f}

Portfolio
"""
    w = ampl.get_variable("w").to_dict()
    Rvar = ampl.get_variable("R").to_dict()

    s += "\n".join([f"{n:8s} {symbols[n]:30s}  {100*w[n]:8.2f} %" for n in w.keys()])
    s += f"""
{'':8s} {'Risk Free':30s}  {100*(1 - sum(w[n] for n in w.keys())):8.2f} %

Annualized return = {100*(np.exp(252*ampl.get_value('ElogR')) - 1):0.2f} %
"""
    print(s)

    df = pd.DataFrame(
        pd.Series([Rvar[str(t)] for t in R.index]), columns=["Kelly Portfolio"]
    )
    df.index = R.index  ## original index are the DateTimeStamps

    fix, ax = plt.subplots(1, 1, figsize=(8, 8))
    S.divide(S.iloc[0] / 100).plot(
        ax=ax,
        logy=True,
        grid=True,
        title="Normalized Prices",
        alpha=0.6,
        lw=0.4,
        ls="--",
    )
    df.cumprod().multiply(100).plot(ax=ax, lw=3, grid=True)
    ax.legend(
        [symbols[n] for n in R.columns] + ["Kelly Portfolio"],
        bbox_to_anchor=(1.05, 1.05),
    )

    d = S.index[-1]
    print(d)

    for n in w.keys():
        y = 100 * S[n].iloc[-1] / S[n].iloc[0]
        print(n, 100 * S[n].iloc[-1] / S[n].iloc[0])
        ax.text(d, y, n)
# parameter values
Rf = np.exp(np.log(1.0) / 252)
EM = 1
lambd = 10

m = kelly_portfolio(R, Rf, EM, lambd)
kelly_report(m)
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060801
0 simplex iterations
23 barrier iterations

Risk Free Return = 0.00
Equity Multiplier Limit = 1.00000
Risk Aversion = 10.00000

Portfolio
AAPL     Apple                              57.78 %
AMGN     Amgen                               0.00 %
AXP      American Express                    0.00 %
BA       Boeing                              0.00 %
CAT      Caterpillar                        18.98 %
CVX      Chevron                             0.00 %
JPM      JPMorgan Chase                      0.00 %
MCD      McDonald's                          0.00 %
MMM      3 M                                 0.00 %
MSFT     Microsoft                           0.00 %
PG       Proctor & Gamble                    0.00 %
XOM      ExxonMobil                          0.00 %
^DJI     Dow Jones Industrial                0.00 %
^GSPC    S&P 500                             0.00 %
^IXIC    Nasdaq                              0.00 %
^RUT     Russell 2000                        0.00 %
         Risk Free                          23.25 %

Annualized return = 32.44 %

2022-06-03 00:00:00
AAPL 313.3368156025014
AMGN 154.57372350246345
AXP 143.59717759289532
BA 40.08344979462141
CAT 193.133774726556
CVX 168.9876904185413
JPM 130.3193283875675
MCD 129.63433059501864
MMM 97.33191412826508
MSFT 211.75258119575005
PG 144.29055289517913
XOM 159.3967047379955
^DJI 126.61551678143734
^GSPC 142.9882963168018
^IXIC 155.1611360900202
^RUT 124.34379721298355
../../_images/2506138552dc149ec48c4a06fe48a43093587edc5450c7cc8689d99a8dae6f12.png
S.head()
Ticker AAPL AMGN AXP BA CAT CVX JPM MCD MMM MSFT PG XOM ^DJI ^GSPC ^IXIC ^RUT
Date
2019-06-07 45.724628 147.421524 112.017525 347.400238 109.405884 94.290375 92.583420 179.858047 111.265190 124.590446 94.340652 56.604912 25983.939453 2873.340088 7742.100098 1514.390015
2019-06-10 46.308960 147.480316 113.451149 347.498444 110.531052 94.919075 93.584206 176.199234 112.387108 125.728264 94.297279 56.855377 26062.679688 2886.729980 7823.169922 1523.560059
2019-06-11 46.845192 146.673965 113.978355 343.108063 111.884758 94.049751 93.872597 177.914856 112.801163 125.254158 94.869720 56.802242 26048.509766 2885.719971 7822.569824 1519.109985
2019-06-12 46.696110 147.497131 112.868446 340.849030 111.726555 93.289101 92.676712 179.376587 112.867958 124.675797 95.129921 56.187469 26004.830078 2879.840088 7792.720215 1519.790039
2019-06-13 46.686489 148.429443 112.711205 342.646423 111.788078 93.847946 92.905716 178.991501 112.761093 125.462769 96.196762 56.680809 26106.769531 2891.639893 7837.129883 1535.800049

Effects of the Risk-Aversion Parameter#

lambd = 10 ** np.linspace(0, 3)

results = [kelly_portfolio(R, Rf=1, EM=1, lambd=_) for _ in lambd]
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514731492
0 simplex iterations
11 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514730187
0 simplex iterations
11 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514731888
0 simplex iterations
12 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514731608
0 simplex iterations
12 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514732546
0 simplex iterations
13 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514731939
0 simplex iterations
13 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514731605
0 simplex iterations
13 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514731601
0 simplex iterations
13 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514732507
0 simplex iterations
14 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514730943
0 simplex iterations
14 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514732006
0 simplex iterations
19 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514731812
0 simplex iterations
18 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001514729572
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001510095399
0 simplex iterations
25 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001437622365
0 simplex iterations
27 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001295715689
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001158395481
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001032064659
0 simplex iterations
22 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0009166803764
0 simplex iterations
30 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0008119743789
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0007174838671
0 simplex iterations
27 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0006326321364
0 simplex iterations
28 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0005567613417
0 simplex iterations
49 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0004891745945
0 simplex iterations
47 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0004291645079
0 simplex iterations
30 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.0003760312865
0 simplex iterations
47 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0003291069177
0 simplex iterations
23 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.000287751695
0 simplex iterations
45 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.0002513775284
0 simplex iterations
45 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.0002194326441
0 simplex iterations
46 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0001914349962
0 simplex iterations
38 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0001669044571
0 simplex iterations
71 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.0001454336267
0 simplex iterations
58 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.0001266789991
0 simplex iterations
66 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.0001102974057
0 simplex iterations
63 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 9.600494691e-05
0 simplex iterations
79 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 8.353972326e-05
0 simplex iterations
87 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 7.267451687e-05
0 simplex iterations
102 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 6.32076036e-05
0 simplex iterations
107 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 5.496260076e-05
0 simplex iterations
119 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 4.777968898e-05
0 simplex iterations
119 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 4.153924901e-05
0 simplex iterations
179 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 3.611700615e-05
0 simplex iterations
142 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 3.137646861e-05
0 simplex iterations
182 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 2.728202317e-05
0 simplex iterations
111 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 2.369919078e-05
0 simplex iterations
135 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 2.057749488e-05
0 simplex iterations
274 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 1.789499907e-05
0 simplex iterations
90 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 1.554970864e-05
0 simplex iterations
245 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 1.350726194e-05
0 simplex iterations
206 barrier iterations
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(2, 1, figsize=(8, 4), sharex=True)

ax[0].semilogx(
    [m.get_value("lambd") for m in results],
    [100 * (np.exp(252 * m.get_value("ElogR")) - 1) for m in results],
)
ax[0].set_title("Portfolio Return vs Risk Aversion")
ax[0].set_ylabel("annual %")
ax[0].grid(True)

ax[1].semilogx(
    [m.get_value("lambd") for m in results],
    [
        [w[str(n)] for n in R.columns]
        for m in results
        for w in [m.get_variable("w").to_dict()]
    ],
)
ax[1].set_ylabel("weights")
ax[1].set_xlabel("risk aversion $\lambda$")
ax[1].legend([symbols[n] for n in R.columns], bbox_to_anchor=(1.05, 1.05))
ax[1].grid(True)
ax[1].set_ylim(0, EM)

fig.tight_layout()
../../_images/e08796500a120a7e4a8c04062c4bd399d23a036f2d6e87814b3a951d245bcb7c.png

Effects of the Equity Multiplier Parameter#

EM = np.linspace(0.0 + 1e-1, 2.0)

results = [kelly_portfolio(R, Rf=1, EM=_, lambd=10) for _ in EM]
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0001732027883
0 simplex iterations
11 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.000239066576
0 simplex iterations
12 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0003041982755
0 simplex iterations
12 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0003686092995
0 simplex iterations
12 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0004322931973
0 simplex iterations
14 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0004952508982
0 simplex iterations
14 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0005574813011
0 simplex iterations
14 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.000618989247
0 simplex iterations
15 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0006797698871
0 simplex iterations
15 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0007398243108
0 simplex iterations
18 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0007991521321
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0008577548616
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0009156308846
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.0009727827525
0 simplex iterations
24 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001029206061
0 simplex iterations
24 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001068590686
0 simplex iterations
24 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00109662602
0 simplex iterations
26 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001114149454
0 simplex iterations
25 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115058531
0 simplex iterations
30 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              3E-06           3E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115061865
0 simplex iterations
24 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060787
0 simplex iterations
35 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              3E-06           3E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115058815
0 simplex iterations
35 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115063664
0 simplex iterations
23 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059963
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060287
0 simplex iterations
34 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115064309
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060034
0 simplex iterations
19 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115061039
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059523
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115065681
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059088
0 simplex iterations
30 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              4E-06           4E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115064196
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060093
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115064296
0 simplex iterations
24 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115062649
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060345
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115058752
0 simplex iterations
38 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              4E-06           4E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059807
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115065009
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115065225
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115061304
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115065279
0 simplex iterations
24 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059316
0 simplex iterations
42 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059421
0 simplex iterations
23 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059066
0 simplex iterations
42 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              4E-06           4E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115058823
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115061931
0 simplex iterations
19 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060531
0 simplex iterations
19 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115058979
0 simplex iterations
20 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115059652
0 simplex iterations
20 barrier iterations
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(2, 1, figsize=(8, 4), sharex=True)

ax[0].plot(
    [m.get_value("EM") for m in results],
    [100 * (np.exp(252 * m.get_value("ElogR")) - 1) for m in results],
)
ax[0].set_title("Portfolio Return vs Equity Multiplier")
ax[0].set_ylabel("annual return %")
ax[0].grid(True)

ax[1].plot(
    [m.get_value("EM") for m in results],
    [
        [w[str(n)] for n in R.columns]
        for m in results
        for w in [m.get_variable("w").to_dict()]
    ],
)
ax[1].set_ylabel("weights")
ax[1].set_xlabel("Equity Multiplier")
ax[1].legend([symbols[n] for n in R.columns], bbox_to_anchor=(1.05, 1.05))
ax[1].grid(True)
ax[1].set_ylim(
    0,
)

fig.tight_layout()
../../_images/63541e45b5fe5de82e5e712650ace5eb0fd9d39a80170fd3a48f4cbca343e0d8.png

Effect of Risk-free Interest Rate#

Rf = np.exp(np.log(1 + np.linspace(0, 0.20)) / 252)

results = [kelly_portfolio(R, Rf=_, EM=1, lambd=10) for _ in Rf]
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001115060801
0 simplex iterations
23 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001109228211
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001103650865
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001098313599
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001093234382
0 simplex iterations
25 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001088400666
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00108380326
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001079451663
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001075343846
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00107146447
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001067825938
0 simplex iterations
19 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001064422018
0 simplex iterations
20 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001061243774
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001058294723
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001055570044
0 simplex iterations
21 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001053079456
0 simplex iterations
23 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              4E-06           4E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001050802581
0 simplex iterations
21 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001048746383
0 simplex iterations
19 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00104690878
0 simplex iterations
20 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001045286967
0 simplex iterations
27 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001043883875
0 simplex iterations
20 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00104268816
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001041702662
0 simplex iterations
30 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001040928306
0 simplex iterations
64 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001040365066
0 simplex iterations
23 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001039997391
0 simplex iterations
47 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00103983771
0 simplex iterations
24 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001039878034
0 simplex iterations
44 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001040120206
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001040556835
0 simplex iterations
27 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001041190809
0 simplex iterations
26 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001042019276
0 simplex iterations
25 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00104304136
0 simplex iterations
23 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001044250378
0 simplex iterations
32 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001045652236
0 simplex iterations
23 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001047238046
0 simplex iterations
22 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001049011788
0 simplex iterations
23 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001050971276
0 simplex iterations
24 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.00105310613
0 simplex iterations
34 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001055426043
0 simplex iterations
36 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001057925134
0 simplex iterations
24 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001060600799
0 simplex iterations
62 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              1E-06           1E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001063452647
0 simplex iterations
30 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001066481758
0 simplex iterations
50 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              4E-06           4E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001069678872
0 simplex iterations
58 barrier iterations
 
------------ WARNINGS ------------
WARNING:  "Tolerance violations"
  Type                         MaxAbs [Name]   MaxRel [Name]
* expr '_expcone'              2E-06           2E-06         
*: Using the solver's aux variable values.
Documentation: mp.ampl.com/modeling-tools.html#automatic-solution-check.
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.001073048338
0 simplex iterations
55 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001076589791
0 simplex iterations
31 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal, stalling; objective 0.001080297284
0 simplex iterations
66 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001084144128
0 simplex iterations
18 barrier iterations
MOSEK 11.0.8: MOSEK 11.0.8: optimal; objective 0.001088100781
0 simplex iterations
20 barrier iterations
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(2, 1, figsize=(8, 4), sharex=True)

Rf = np.exp(252 * np.log(np.array([_.get_value("Rf") for _ in results])))
ax[0].plot(Rf, [100 * (np.exp(252 * m.get_value("ElogR")) - 1) for m in results])
ax[0].set_title("Portfolio Return vs Risk-free Rate")
ax[0].set_ylabel("annual return %")
ax[0].grid(True)

ax[1].plot(
    Rf,
    [
        [w[str(n)] for n in R.columns]
        for m in results
        for w in [m.get_variable("w").to_dict()]
    ],
)
ax[1].set_ylabel("weights")
ax[1].set_xlabel("Risk-free Rate")
ax[1].legend([symbols[n] for n in R.columns], bbox_to_anchor=(1.05, 1.05))
ax[1].grid(True)
ax[1].set_ylim(
    0,
)

fig.tight_layout()
../../_images/7ceb6905c4bd1cffb739261200ab61914d4106d431810d0c0d0dd7c64275e35a.png

Extensions#

  1. The examples cited in this notebook assume knowledge of the probability mass distribution. Recent work by Sun and Boyd (2018) and Hsieh (2022) suggest models for finding investment strategies for cases where the distributions are not perfectly known. They call them “distributionally robust Kelly gambling.” A useful extension to this notebook would be to demonstrate a robust solution to one or more of the examples.

Bibliographic Notes#

Thorp, E. O. (2017). A man for all markets: From Las Vegas to wall street, how i beat the dealer and the market. Random House.

Thorp, E. O. (2008). The Kelly criterion in blackjack sports betting, and the stock market. In Handbook of asset and liability management (pp. 385-428). North-Holland. https://www.palmislandtraders.com/econ136/thorpe_kelly_crit.pdf

MacLean, L. C., Thorp, E. O., & Ziemba, W. T. (2010). Good and bad properties of the Kelly criterion. Risk, 20(2), 1. https://www.stat.berkeley.edu/~aldous/157/Papers/Good_Bad_Kelly.pdf

MacLean, L. C., Thorp, E. O., & Ziemba, W. T. (2011). The Kelly capital growth investment criterion: Theory and practice (Vol. 3). world scientific. https://www.worldscientific.com/worldscibooks/10.1142/7598#t=aboutBook

Carta, A., & Conversano, C. (2020). Practical Implementation of the Kelly Criterion: Optimal Growth Rate, Number of Trades, and Rebalancing Frequency for Equity Portfolios. Frontiers in Applied Mathematics and Statistics, 6, 577050. https://www.frontiersin.org/articles/10.3389/fams.2020.577050/full

The utility of conic optimization to solve problems involving log growth is more recent. Here are some representative papers.

Cajas, D. (2021). Kelly Portfolio Optimization: A Disciplined Convex Programming Framework. Available at SSRN 3833617. https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3833617

Busseti, E., Ryu, E. K., & Boyd, S. (2016). Risk-constrained Kelly gambling. The Journal of Investing, 25(3), 118-134. https://arxiv.org/pdf/1603.06183.pdf

Fu, A., Narasimhan, B., & Boyd, S. (2017). CVXR: An R package for disciplined convex optimization. arXiv preprint arXiv:1711.07582. https://arxiv.org/abs/1711.07582

Sun, Q., & Boyd, S. (2018). Distributional robust Kelly gambling. arXiv preprint arXiv: 1812.10371. https://web.stanford.edu/~boyd/papers/pdf/robust_kelly.pdf

The recent work by CH Hsieh extends these concepts in important ways for real-world implementation.

Hsieh, C. H. (2022). On Solving Robust Log-Optimal Portfolio: A Supporting Hyperplane Approximation Approach. arXiv preprint arXiv:2202.03858. https://arxiv.org/pdf/2202.03858