Discrete Allocation
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.
| Parameter | Type | Description | Example |
|---|---|---|---|
weights | dict[str, float] | None | The 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_value | float | The amount to invest, in the currency of the prices. Defaults to 10000. | 10000 |
method | str | “integer” or “greedy”. Defaults to “integer”. | "integer" |
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 |