Greedy allocation

PortfolioOptimisers.GreedyAllocationResultType
struct GreedyAllocationResult{__T_retcode, __T_shares, __T_cost, __T_w, __T_cash, __T_fees, __T_fb} <: FiniteAllocationOptimisationResult

Result type for GreedyAllocation.

shares, cost and w are signed: a short position carries a negative share count, a negative cost and a negative weight. fees is the charge the two sides paid over the whole horizon, and it is never signed. cash is the cash left over after the long side is allocated.

Fields

  • retcode: Optimisation return code.
  • shares: Number of shares allocated per asset.
  • cost: Cost of the allocation.
  • w: Realised portfolio weights.
  • cash: Remaining uninvested cash after allocation.
  • fees: Fee the allocation paid over the whole horizon. It is the sum of the two sides' charges, and it is never signed.
  • fb: The fallback chain that answered this result: the (estimator, result) pair of every attempt optimise made before this one, in the order they ran, or nothing when the estimator it was asked of answered (see FbChain).

Constructors

GreedyAllocationResult(;    retcode::OptimisationReturnCode,    shares::VecNum,    cost::VecNum,    w::VecNum,    cash::Number,    fees::Number,    fb::Option{<:FOptE_FOpt_FbChain}) -> GreedyAllocationResult

Keywords correspond to the struct's fields.

Related

References

  • [145] R. A. Martin. PyPortfolioOpt: portfolio optimization in Python. Journal of Open Source Software 6, 3066 (2021).
source
PortfolioOptimisers.GreedyAllocationType
struct GreedyAllocation{__T_unit, __T_args, __T_kwargs, __T_fb} <: FiniteAllocationOptimisationEstimator

Greedy Allocation portfolio optimiser.

GreedyAllocation converts continuous portfolio weights to discrete share quantities using a greedy two-pass allocation: the first pass buys down the target weights in descending order, and the second pass spends the leftover cash on the asset whose realised weight falls furthest short of its target.

The long and the short side of a portfolio are allocated as two separate sub-problems. Each sub-problem receives its own share of the cash, and its own weights are renormalised to sum to one. The definition below describes one such sub-problem, whose targets $\boldsymbol{w}$ therefore satisfy $\sum_i w_i = 1$. Short shares are negated when the two sides are recombined.

Mathematical definition

Order the assets so that $w_1 \geq w_2 \geq \ldots \geq w_N$. The first pass walks that order and buys

\[\begin{align} x_i &= \mathrm{round}\!\left(\left\lfloor \frac{w_i C}{p_i \, \mathrm{unit}} \right\rfloor \mathrm{unit}\right)\,, \\ r &\leftarrow r - x_i p_i - \Delta F_i\,, \end{align}\]

starting from $r = C - F(\boldsymbol{0})$. The pass stops at the first asset it cannot afford, so every later asset in the order is left at zero for the second pass to reach.

The second pass repeats, while $r > 0$,

\[\begin{align} \boldsymbol{d} &= \boldsymbol{w} - \frac{\boldsymbol{x} \odot \boldsymbol{p}}{\sum_{j=1}^{N} x_j p_j}\,, \\ i^* &= \underset{i:\, p_i \, \mathrm{unit} + \Delta F_i \leq r}{\arg\max}\; d_i\,, \\ x_{i^*} &\leftarrow x_{i^*} + \mathrm{unit}\,, \\ r &\leftarrow r - p_{i^*} \mathrm{unit} - \Delta F_{i^*}\,. \end{align}\]

The pass stops when no affordable asset has a positive deficit. The selection is by deficit, not by target weight: an asset the first pass already filled has a small deficit however large its target weight is.

Where:

  • $\boldsymbol{x}$: Share allocation vector.
  • $r$: Cash not yet spent.
  • $\boldsymbol{w}$: Target weight vector of this sub-problem, renormalised to sum to one.
  • $C$: Cash allocated to this sub-problem.
  • $\boldsymbol{p}$: Asset price vector.
  • $\mathrm{unit}$: Minimum share purchase unit.
  • $\boldsymbol{d}$: Weight deficit, the target weight less the realised weight.
  • $F(\boldsymbol{x})$: Fee of this sub-problem, of allocation_fee. It is zero when the input states no fee, and it carries the constant forced exit of allocation_liquidation_fee when the universe lost an asset.
  • $\Delta F_i$: What buying the asset $i$ adds to the fee, of greedy_fee_delta.
  • $i^*$: Affordable asset with the largest weight deficit.
  • $\odot$: Element-wise (Hadamard) product.
  • $N$: Number of assets in this sub-problem.

The rounding is a floor to a multiple of unit, followed by Base.round under args and kwargs. See roundmult: it is not a round to the nearest multiple.

Fields

  • unit: Minimum purchase unit (e.g., price per share or lot size).
  • args: Additional positional arguments forwarded to round.
  • kwargs: Additional keyword arguments.
  • fb: Fallback result or estimator.

Constructors

GreedyAllocation(;    unit::Number = 1,    args::Tuple = (),    kwargs::NamedTuple = (;),    fb::Option{<:FOptE_FOpt} = nothing) -> GreedyAllocation

Keywords correspond to the struct's fields.

Validation

  • unit > 0.

Examples

julia> GreedyAllocation()GreedyAllocation    unit ┼ Int64: 1    args ┼ Tuple{}: ()  kwargs ┼ @NamedTuple{}: NamedTuple()      fb ┴ nothing

Related

References

  • [145] R. A. Martin. PyPortfolioOpt: portfolio optimization in Python. Journal of Open Source Software 6, 3066 (2021).
source
PortfolioOptimisers.factoryMethod
factory(a::Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                 <:AbstractResult}, args...; kwargs...) -> a
factory(a::AbstractVector{<:Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                                  <:AbstractResult}}, args...; kwargs...) -> Vector

No-op factory function for constructing objects with a uniform interface.

Defining methods which dispatch on the first argument allows for a consistent factory interface across different types.

factory and port_opt_view are the two propagation mechanisms in this library. They are duals: factory threads runtime values (prior moments, observation weights, previous portfolio weights) down through a composed struct tree; port_opt_view threads an index selection (a subset of assets or observations) down through the same tree.

The vector method is the one forwarding contract for every vector-valued propagation field: it applies factory to each element and forwards args... and kwargs... unchanged, so a family that admits a vector of estimators, algorithms, or results needs no method of its own. A family that needs more than the forward, such as a concrete element type (concrete_typed_array_if_abstract), defines its own more specific method.

Algorithm

The scalar method:

  1. Return a unchanged, and drop args... and kwargs.... This method is the leaf of the recursion, and it is what makes an untagged type safe to call the verb on.

The vector method:

  1. For each element ai of a, call factory on ai, and forward args... and kwargs... unchanged.
  2. Collect the results into a new vector, in the order of a, and return it.

A @propagatable struct with at least one @fprop- or @wprop-tagged field carries a generated method that dominates the scalar method. That method rebuilds the struct with its keyword constructor, sending each @fprop field through factory_child and each @wprop field through _wprop.

Arguments

  • a: Indicates no object should be constructed, or a vector whose elements are rebuilt one by one.
  • args...: Arbitrary positional arguments (ignored by the scalar method, forwarded by the vector method).
  • kwargs...: Arbitrary keyword arguments (ignored by the scalar method, forwarded by the vector method).

Returns

  • a: The input unchanged.
  • v::Vector: The element-wise rebuilds, for the vector method.

Examples

julia> factory(nothing, 1, 2; x = 3)julia> factory(MeanValue())MeanValue  w ┴ nothing

Related

source
factory(res::FiniteAllocationOptimisationResult, fb::Option{<:FOptE_FOpt_FbChain})

Rebuild a finite allocation result with an updated fallback record fb.

Like the continuous-result generic, every finite allocation result carries fb as its last field, so the rebuild copies all fields unchanged except the trailing fb. Concrete result types may override this method when rebuilding requires more than swapping fb. optimise is the one caller, and it hands in the FbChain it walked.

Related

source
PortfolioOptimisers.optimiseMethod
optimise(ga::GreedyAllocation{<:Any, <:Any, <:Any, Nothing},
         fai::FiniteAllocationInput; kwargs...) -> GreedyAllocationResult

Run the Greedy Allocation portfolio optimisation.

Arguments

  • ga: The greedy allocation optimiser to use.
  • fai: The FiniteAllocationInput carrying the target weights, prices, cash budget, and optional horizon and fees.
  • kwargs: Additional keyword arguments passed to the optimisation function.

Returns

  • res::GreedyAllocationResult: The realised allocation. retcode is always an OptimisationSuccess, because the greedy passes cannot fail.

Related

source

References

[145]
R. A. Martin. PyPortfolioOpt: portfolio optimization in Python. Journal of Open Source Software 6, 3066 (2021).