Discrete allocation: private API

PortfolioOptimisers.finite_sub_allocationFunction
finite_sub_allocation(w::VecNum, p::VecNum, cash::Number, bgt::Number,
                      sf::Option{<:NamedTuple}, da::DiscreteAllocation,
                      str_names::Bool = false)

Build and solve the discrete allocation MIP for one side, long or short, of the portfolio.

Implements the sub-problem of DiscreteAllocation. An empty w returns three empty vectors, the untouched cash, a zero fee, and nothing for both the return code and the model.

Arguments

  • w::VecNum: Target weights of this side, non-negative.
  • p::VecNum: Asset prices of this side, in the same order as w.
  • cash::Number: Cash allocated to this side.
  • bgt::Number: Budget of this side, used to rescale the realised weights.
  • sf::Option{<:NamedTuple}: This side's charge, of allocation_side_fees, or nothing.
  • da::DiscreteAllocation: Allocator carrying the solvers, the scales and the formulation wf.
  • str_names::Bool = false: Whether to give the JuMP variables string names.

Returns

  • shares::VecNum: Share count per asset, rounded to Int.
  • cost::VecNum: shares .* p.
  • aw::VecNum: Realised weights, rescaled to sum to bgt. All zero when nothing was bought.
  • acash::Number: Cash left over, cash less the cost of the shares and the fee.
  • fee::Number: The fee this side paid over the whole horizon.
  • res::OptimisationReturnCode: An OptimisationSuccess or an OptimisationFailure carrying the solver trials.
  • model::JuMP.Model: The solved model.

Details

  • The share vector is declared integer and non-negative, so a short side must be passed with its weights already negated.
  • set_discrete_error! adds the one constraint that da.wf selects. Everything else in the model is common to the four formulations.
  • set_allocation_fees! adds the fee. It enters the budget constraint and never the objective.
  • The fee this verb reports is allocation_fee of the realised shares, not the value of the model's own expression. The turnover term of that expression is an epigraph, and only the budget pushes it down, so a solver leaves it slack whenever the budget does not bind. A slack epigraph overstates the fee, so the allocation the model bought is affordable under the exact charge.
  • shares is read back with round(Int, ...), because a MIP solver returns an integer only to within its own tolerance. A fee larger than the cash makes the budget infeasible, and an infeasible model holds no finite value, so the book is then read as empty and res carries the failure.

Related

source
PortfolioOptimisers.set_discrete_error!Function
set_discrete_error!(model::JuMP.Model, w::VecNum, p::VecNum, cash::Number,
                    wf::JuMPWeightFinaliserFormulation) -> Nothing

Bound the model's auxiliary variable u by the allocation error that wf selects.

Adds the one constraint that separates the four formulations of DiscreteAllocation. The model already holds the share vector x and the auxiliary variable u; this method adds the cone that ties them together. The objective and the cash constraint are set by finite_sub_allocation and do not depend on wf.

Arguments

  • model::JuMP.Model: Model holding x, u and the constraint scale.
  • w::VecNum: Target weights of this sub-problem.
  • p::VecNum: Asset prices, in the same order as w.
  • cash::Number: Cash allocated to this sub-problem.
  • wf::JuMPWeightFinaliserFormulation: Selects the error. See the table in DiscreteAllocation.

Returns

  • nothing.

Details

  • The absolute formulations bound the error w * cash - x .* p; the relative ones bound (x * cash) ⊘ (w .* p) .- 1.
  • The unsquared formulations use a JuMP.MOI.NormOneCone; the squared ones use a JuMP.SecondOrderCone, which bounds the $\ell_2$ norm itself rather than its square.
  • The relative formulations replace a zero target weight with eps(eltype(w)) on a copy of w, so the caller's vector is untouched and the division is defined.

Related

source
PortfolioOptimisers.set_allocation_fees!Function
set_allocation_fees!(model::JuMP.Model, p::VecNum, cash::Number, sf::Option{<:NamedTuple})

Write one side's fee in the allocation model's own variables, and return it.

A fee is a cost of the portfolio the allocator actually buys. The model holds the share vector x and the prices p, so x .* p is the money in each position exactly. Every term is written against that money, and no weight and no price appears on its own.

sf is one side's charge, of allocation_side_fees. A nothing sf writes nothing and returns a zero expression, so the caller needs no branch.

Mathematical definition

\[\begin{align} \boldsymbol{m} &= \boldsymbol{x} \odot \boldsymbol{p}\,, \\ t_{i} &\geq \lvert m_{i} - m_{0,i} \rvert\,, \\ b_{i} &\leq x_{i} \leq \left\lfloor C / p_{i} \right\rfloor b_{i}\,, \\ F(\boldsymbol{x}) &= T \left( \boldsymbol{f}_{\text{p}}^\intercal \boldsymbol{m} + \boldsymbol{f}_{\text{Tn}}^\intercal \boldsymbol{t} \right) + \boldsymbol{f}_{\text{f}}^\intercal \boldsymbol{b} + F_{\text{lq}}\,. \end{align}\]

Where:

  • $\boldsymbol{m}$: Money in each position.
  • $\boldsymbol{m}_{0}$: Money in each position before the trade, sf.prev_money.
  • $\boldsymbol{t}$: Epigraph of the money traded.
  • $\boldsymbol{b}$: Binary saying whether the position is held at all.
  • $C$: Cash allocated to this sub-problem.
  • $T$: Horizon, in periods.
  • $\boldsymbol{f}_{\text{p}},\, \boldsymbol{f}_{\text{Tn}},\, \boldsymbol{f}_{\text{f}}$: Proportional, turnover and fixed rates of this side.
  • $F_{\text{lq}}$: Forced exit of allocation_liquidation_fee, sf.liq. It is a constant, because the assets it sells are not among the share counts this model solves for.

The rates l, s and tn charge on each of the T periods, and the fixed amounts fl and fs charge one time for the whole horizon. That is the rule calc_total_fees states, written in the model's own variables.

A binary is emitted only when the side states a fixed fee, so a problem that states none keeps the variable count it had. x is integer and non-negative, so b <= x and x <= ub * b make b the indicator of x > 0 exactly.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • p::VecNum: Asset prices of this side.
  • cash::Number: Cash allocated to this side. It bounds the shares a binary can switch on.
  • sf::Option{<:NamedTuple}: This side's charge, or nothing.

Returns

  • fee: The fee expression. It is registered as model[:fee].

Related

source