Calculate how many whole shares to buy of each asset to hold weights with a portfolio value, at the latest traded (split-adjusted) closing price. Weights rarely come out in whole shares, so some cash is left over and the weights held differ a little from the targets; the smaller the portfolio value compared to the share prices, the larger the difference. With t_i = w_i V the target amount of asset i and p_i its price:

  • method=”integer”: the number of shares n_i that minimizes the absolute deviation from the target amounts with the leftover cash c counted as a holding with a target of zero, min Σ |t_i - n_i p_i| + c with Σ n_i p_i + c = V and c ≥ 0, solved as an integer programme (Cornuejols, Peña and Tütüncü, 2018).
  • method=”greedy”: every position is first rounded down to whole shares and the leftover cash then buys one more share at a time of the asset furthest below its target amount, while its price fits the cash. Quick and close to the integer programme, which can do better by leaving out a share of one asset to buy more of another.

A short weight becomes a negative number of shares, with the proceeds funding the long positions: weights that sum to one keep a net investment of the portfolio value. Short positions are rounded to a larger short, so the leftover cash is never negative.

For more information, see the following book:

  • Cornuejols, G., Peña, J. and Tütüncü, R. (2018). “Optimization Methods in Finance.” 2nd edition, Cambridge University Press.

Also known as: share allocation, whole share rounding, lot sizing, order sizes.

Discrete Allocation in Python

Install the Finance Toolkit with:

pip install financetoolkit -U

Then call get_discrete_allocation 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",
)

weights = toolkit.portfolio.get_maximum_sharpe_weights()["Weight"]

toolkit.portfolio.get_discrete_allocation(
    weights=weights.drop("Portfolio").to_dict(), portfolio_value=25000
)

Which returns:

Asset Price Shares Amount Target Weight Weight
AAPL 271.86 1 271.86 0.014 0.0109
MSFT 483.62 16 7737.92 0.3121 0.3095
JNJ 206.95 24 4966.8 0.1998 0.1987
XOM 120.34 99 11913.7 0.4741 0.4765
Leftover Cash nan nan 109.76 0 0.0044
Total nan nan 25000 1 1

Parameters

get_discrete_allocation accepts the following parameters.

ParameterTypeDescriptionExample
weightsdict[str, float] | NoneThe weight per asset, e.g. those of one of the optimizers. Defaults to None, which uses the latest weights of the Portfolio the Toolkit came from, or equal weights.None, which uses the latest weights of the Portfolio the Toolkit came from, or equal weights
portfolio_valuefloatThe amount to invest, in the currency of the prices. Defaults to 10000.10000
methodstr“integer” or “greedy”. Defaults to “integer”."integer"
include_benchmarkboolWhether to include the benchmark as an asset. Defaults to False.True
roundingint | NoneThe number of decimals to round the results to. Defaults to None, which uses the Toolkit’s rounding.2
Share