Growth Optimal (Kelly)
Calculate the weights of the growth optimal (Kelly) portfolio: the fully invested portfolio with the highest expected logarithmic growth of wealth, which in the long run ends up with more wealth than any other strategy with near certainty. Because the full Kelly portfolio is volatile and sensitive to errors in the expected returns, investors often hold a fraction of it (fractional Kelly) and keep the rest in cash.
The “exact” method maximizes the average log return over the historical returns, (1/T) Σ ln(1 + r_t’w). The “approximate” method maximizes its second-order approximation w’μ - ½ w’Σw, which is a mean-variance portfolio with a risk aversion of one; it uses the expected returns and covariance matrix instead of the returns themselves. Both keep the weights summing to one and between the minimum and maximum weight. The weights are then multiplied by the fraction and the remainder, 1 - fraction, is held in cash at the risk free rate (borrowed when the fraction is above one).
For more information, see the following papers:
- Kelly, J.L. (1956). “A New Interpretation of Information Rate.” Bell System Technical Journal, 35(4), 917-926.
- Breiman, L. (1961). “Optimal Gambling Systems for Favorable Games.” Proceedings of the Fourth Berkeley Symposium on Mathematical Statistics and Probability, 1, 65-78.
- Thorp, E.O. (2006). “The Kelly Criterion in Blackjack, Sports Betting and the Stock Market.” Handbook of Asset and Liability Management, 1, 385-428.
Also known as: Kelly portfolio, log-optimal portfolio, maximum geometric return, fractional Kelly.
Growth Optimal (Kelly) in Python or via the MCP
Install the Finance Toolkit with:
pip install financetoolkit -U
Then call get_growth_optimal_weights as shown below.
from financetoolkit import Toolkit
toolkit = Toolkit(
["AAPL", "MSFT", "JNJ", "XOM"],
api_key="FINANCIAL_MODELING_PREP_KEY",
start_date="2021-01-01",
end_date="2025-12-31",
)
toolkit.portfolio.get_growth_optimal_weights(fraction=0.5, maximum_weight=0.4)
Or, after setting up the Finance Toolkit MCP, ask:
What does half-Kelly growth-optimal allocation put in Apple, Microsoft, Johnson & Johnson, ExxonMobil and cash, based on 2021 to 2025, with at most 40% in a stock?
Which returns:
| Weight | Expected Return | Volatility | Sharpe Ratio | Risk Contribution | Log Growth | |
|---|---|---|---|---|---|---|
| AAPL | 0.1 | 0.1933 | 0.2786 | 0.5727 | 0.2141 | 0.1547 |
| MSFT | 0.2 | 0.2017 | 0.2572 | 0.6532 | 0.4118 | 0.1687 |
| JNJ | 0 | 0.0984 | 0.1673 | 0.3867 | 0 | 0.0844 |
| XOM | 0.2 | 0.291 | 0.2702 | 0.9522 | 0.374 | 0.2544 |
| Cash | 0.5 | 0.0337 | 0 | nan | 0 | 0.0337 |
| Portfolio | 1 | 0.1347 | 0.0959 | 1.0531 | 1 | 0.1301 |
Parameters
get_growth_optimal_weights accepts the following parameters.
| Parameter | Type | Description | Example |
|---|---|---|---|
method | str | “exact” (the historical log returns) or “approximate” (the mean-variance approximation). Defaults to “exact”. | "exact" |
fraction | float | The share of the growth optimal portfolio to hold, e.g. 0.5 for half Kelly; the rest is cash. Defaults to 1. | 1 |
period | str | The frequency of the returns (daily, weekly, monthly, quarterly or yearly). The log growth compounds per period, so it needs non-overlapping returns; daily returns keep every period’s wealth positive. Defaults to “daily”. | "weekly" |
covariance | str | The estimator of the covariance matrix, used by the “approximate” method and for the volatility and risk contributions: “sample” (ddof=1), “ledoit_wolf” (towards the scaled identity, Ledoit and Wolf, 2004), “ledoit_wolf_constant_correlation” (towards the average correlation, Ledoit and Wolf, 2004), “ledoit_wolf_single_factor” (towards the covariance that the benchmark as a single factor implies, Ledoit and Wolf, 2003), “oas” (Chen et al., 2010), “semicovariance” (the co-movement below a zero return, Estrada, 2007), “exponential” (recent periods weigh most, RiskMetrics) or “denoised” (the eigenvalues of the correlation matrix that random returns could produce averaged, Laloux et al., 2000). Defaults to “sample”. | "sample" |
covariance_span | int | The span in periods of the “exponential” covariance; the weight of a period halves after about span / 2.9 periods. Defaults to 180. | 180 |
expected_returns | str | The estimator of the expected returns: “mean” (the historical average), “exponential” (the exponentially weighted average, recent periods weigh most), “capm” (the returns the Capital Asset Pricing Model implies from each asset’s beta to the benchmark, Sharpe, 1964) or “james_stein” (the averages shrunk towards that of the minimum variance portfolio, Jorion, 1986). Defaults to “mean”. | "mean" |
expected_returns_span | int | The span in periods of the “exponential” expected returns. Defaults to 180. | 180 |
minimum_weight | float | dict[str, float] | The lowest weight of every asset, or per asset, e.g. {“AAPL”: 0.05} (the others keep 0); a negative value allows short positions. Defaults to 0. | 0 |
maximum_weight | float | dict[str, float] | The highest weight of every asset, or per asset, e.g. {“AAPL”: 0.3} (the others keep 1). Defaults to 1. | 1 |
sector_bounds | dict[str, tuple[float, float]] | None | The minimum and maximum total weight per sector, e.g. {“Technology”: (0, 0.4)}, with the sector of each asset from its company profile. Defaults to None. | None |
country_bounds | dict[str, tuple[float, float]] | None | The minimum and maximum total weight per country, named in full or by its two-letter code, e.g. {“United States”: (0, 0.6)}, with the country of each asset from its company profile. Defaults to None. | None |
net_exposure | float | The sum of the weights: 1 for a fully invested portfolio, 0 for a market neutral one (which needs a negative minimum_weight). Defaults to 1. | 1 |
gross_exposure | float | None | The highest sum of the absolute weights, which limits the leverage of a long-short portfolio, e.g. 1.6 for a 130/30 portfolio. Defaults to None, no limit. | None, no limit |
include_benchmark | bool | Whether to include the benchmark as an asset. Defaults to False. | True |
rounding | int | None | The number of decimals to round the results to. Defaults to None, which uses the Toolkit’s rounding. | 2 |