Discrete allocation

PortfolioOptimisers.DiscreteAllocationResultType
struct DiscreteAllocationResult{__T_retcode, __T_s_retcode, __T_l_retcode, __T_shares, __T_cost, __T_w, __T_cash, __T_fees, __T_s_model, __T_l_model, __T_fb} <: FiniteAllocationOptimisationResult

Result type for DiscreteAllocation.

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 sub-problems paid over the whole horizon, and it is never signed. retcode is a failure when either sub-problem failed; s_retcode and l_retcode carry the short-side and long-side return codes on their own, and s_model and l_model carry the two JuMP models when save is true.

Fields

  • retcode: Optimisation return code.
  • s_retcode: Return code for the short allocation sub-problem.
  • l_retcode: Return code for the long allocation sub-problem.
  • 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.
  • s_model: JuMP model for the short allocation.
  • l_model: JuMP model for the long allocation.
  • 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

DiscreteAllocationResult(;    retcode::OptimisationReturnCode,    s_retcode::Option{<:OptimisationReturnCode},    l_retcode::Option{<:OptimisationReturnCode},    shares::VecNum,    cost::VecNum,    w::VecNum,    cash::Number,    fees::Number,    s_model::Option{<:JuMP.Model},    l_model::Option{<:JuMP.Model},    fb::Option{<:FOptE_FOpt_FbChain}) -> DiscreteAllocationResult

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.DiscreteAllocationType
struct DiscreteAllocation{__T_slv, __T_sc, __T_so, __T_wf, __T_fb} <: FiniteAllocationOptimisationEstimator

Discrete Allocation portfolio optimiser.

DiscreteAllocation allocates a portfolio by solving a Mixed-Integer Programming (MIP) problem to find the optimal number of shares for each asset, minimising the deviation between the target continuous weights and the realised discrete allocation.

The long and the short side of a portfolio are allocated as two separate MIP sub-problems, each with its own share of the cash and its own budget. Each sub-problem holds a non-negative share vector, and the short side is negated when the two are recombined. s_retcode and l_retcode of the result carry the two return codes.

Mathematical definition

One sub-problem, under the default AbsoluteErrorWeightFinaliser:

\[\begin{align} \underset{\boldsymbol{x} \in \mathbb{Z}_{\geq 0}^N}{\min} \quad & u + r\,, \\ \text{s.t.} \quad & u \geq \lVert \boldsymbol{w} C - \boldsymbol{x} \odot \boldsymbol{p} \rVert_1\,, \\ & r = C - \boldsymbol{x}^\intercal \boldsymbol{p}\,, \\ & r - F(\boldsymbol{x}) \geq 0\,. \end{align}\]

Where:

  • $\boldsymbol{x}$: Integer share vector.
  • $u$: Tracking error auxiliary variable.
  • $r$: Residual cash.
  • $F(\boldsymbol{x})$: Fee of this sub-problem, of set_allocation_fees!. It is zero when the input states no fee.
  • $\boldsymbol{w}$: Target weight vector of this sub-problem.
  • $C$: Cash allocated to this sub-problem.
  • $\boldsymbol{p}$: Asset price vector.
  • $\odot$: Element-wise (Hadamard) product.
  • $N$: Number of assets in this sub-problem.

The fee enters the budget and never the objective. The objective reads track well, and leave no capital idle, so a fee added to $r$ and minimised would reward the model for paying more fees: a larger fee shrinks the leftover. The budget instead states that the fee must be affordable. The objective still pushes $\boldsymbol{x}^\intercal \boldsymbol{p}$ up, so every unit of fee competes with a unit of position, and the model drops a position whose fixed fee buys too little tracking.

wf selects the deviation that $u$ bounds. The objective, the integrality and the cash constraint do not change with it.

wfConstraint on $u$
AbsoluteErrorWeightFinaliser$u \geq \lVert \boldsymbol{w} C - \boldsymbol{x} \odot \boldsymbol{p} \rVert_1$
SquaredAbsoluteErrorWeightFinaliser$u \geq \lVert \boldsymbol{w} C - \boldsymbol{x} \odot \boldsymbol{p} \rVert_2$
RelativeErrorWeightFinaliser$u \geq \lVert \boldsymbol{x} C \oslash (\boldsymbol{w} \odot \boldsymbol{p}) - \boldsymbol{1} \rVert_1$
SquaredRelativeErrorWeightFinaliser$u \geq \lVert \boldsymbol{x} C \oslash (\boldsymbol{w} \odot \boldsymbol{p}) - \boldsymbol{1} \rVert_2$

Where $\oslash$ is element-wise division, and $\boldsymbol{1}$ is the vector of ones.

Note

The two Squared formulations bound the $\ell_2$ norm itself, not its square: they build a JuMP.SecondOrderCone over $[u;\, \cdot]$. The square is monotonic on a non-negative norm, so the minimiser is the one a squared objective would give, but the objective value is the norm. The two relative formulations replace a zero target weight with eps so that the division is defined.

Fields

  • slv: Solver or vector of solvers.
  • sc: Constraint scale factor.
  • so: Objective scale factor.
  • wf: Weight finaliser.
  • fb: Fallback result or estimator.

Constructors

DiscreteAllocation(;    slv::Slv_VecSlv,    sc::Number = 1,    so::Number = 1,    wf::JuMPWeightFinaliserFormulation = AbsoluteErrorWeightFinaliser(),    fb::Option{<:FOptE_FOpt} = GreedyAllocation()) -> DiscreteAllocation

Keywords correspond to the struct's fields.

Validation

  • If slv is a vector: !isempty(slv).
  • sc > 0, so > 0.

Examples

julia> DiscreteAllocation(; slv = Solver(; solver = nothing))DiscreteAllocation  slv ┼ Solver      │          name ┼ String: ""      │        solver ┼ nothing      │      settings ┼ nothing      │     check_sol ┼ @NamedTuple{}: NamedTuple()      │   add_bridges ┴ Bool: true   sc ┼ Int64: 1   so ┼ Int64: 1   wf ┼ AbsoluteErrorWeightFinaliser()   fb ┼ 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(da::DiscreteAllocation{<:Any, <:Any, <:Any, <:Any, Nothing},
         fai::FiniteAllocationInput; str_names::Bool = false,
         save::Bool = true, kwargs...) -> DiscreteAllocationResult

Run the Discrete Allocation portfolio optimisation.

Arguments

  • da: The discrete allocation optimiser to use.
  • fai: The FiniteAllocationInput carrying the target weights, prices, cash budget, and optional horizon and fees.
  • str_names: Whether to use string names for the assets in the optimisation.
  • save: Whether to save the JuMP model in the optimisation result.
  • kwargs: Additional keyword arguments passed to the optimisation function.

Returns

  • res::DiscreteAllocationResult: The realised allocation. retcode is an OptimisationFailure when either sub-problem failed, and each failure raises a warning naming the side.

Related

source

References

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