Exposure Constraints: private API

PortfolioOptimisers.factor_space_regressionFunction
factor_space_regression(re::Option{<:RegE_Reg}, rr::Option{<:AbstractLoadingsRegressionResult},
                        rd::Option{<:ReturnsResult})
    -> Option{<:AbstractLoadingsRegressionResult}

Apply resolve_factor_regression's precedence to the three carriers a FactorSpace can read its loadings from, and return nothing when none of them holds any.

The precedence is the one FactorRiskContribution already uses — a stated result, then the prior's own rr, then a refit — and the refit arm delegates to resolve_factor_regression rather than repeating it. What differs is the carriers: constraint generation is handed the loadings as rr, already read off the prior, so the prior arm is a plain argument rather than a field read, and both re and rd are optional here because a space that names no source and a route that carries no returns are both ordinary.

Returning nothing rather than throwing is deliberate: the diagnosis for "no basis anywhere" belongs to constraint_space_basis, which knows it is assembling a constraint.

Algorithm

  1. Return re when it is a precomputed Regression. The caller has stated the basis, and no data is needed.
  2. Otherwise return rr when the prior carries one. This is what a re === nothing always resolves to, and it is the behaviour of every FactorSpace written before the field existed.
  3. Otherwise return nothing when re is nothing too. No carrier holds a basis, and constraint_space_basis writes the diagnosis.
  4. Otherwise re is an estimator. Raise when rd is nothing, and return resolve_factor_regression(re, rd) when it is not, which refits the loadings from the returns.

Step 4 is a capability the field adds rather than a fallback: FactorSpace(; re = StepwiseRegression()) is a factor mandate on a prior that carries no loadings, which previously threw.

Arguments

  • re::Option{<:RegE_Reg}: The space's stated basis source, or nothing to read rr.
  • rr::Option{<:AbstractLoadingsRegressionResult}: The prior's loadings, or nothing.
  • rd::Option{<:ReturnsResult}: Returns to refit from, or nothing on the standalone route.

Validation

Returns

  • rr::Option{<:AbstractLoadingsRegressionResult}: The loadings the highest-ranked carrier holds, or nothing when none of the three holds any.

Related

source
PortfolioOptimisers.project_linear_constraintFunction
project_linear_constraint(lc::LinearConstraint, M::MatNum) -> LinearConstraint
project_linear_constraint(plc::PartialLinearConstraint, M::MatNum) -> PartialLinearConstraint

Re-base an already assembled constraint by projecting its coefficient matrix, A * transpose(M).

This is the precomputed-constraint half of the estimator path's per-term projection, and it is the one place linear_constraints stops being a pass-through for a LinearConstraint: a precomputed constraint reaching the optimiser through an ExposureConstraintEstimator was written in the wrapped basis and must still be re-based. The right-hand side is untouched — a change of basis acts on the row, not on the bound.

size(A, 2) == size(M, 2) is checked rather than assumed, because a precomputed constraint carries no names and nothing else would catch an asset-space matrix handed to a FactorSpace wrapper whose asset and factor counts happen to differ.

Algorithm

The PartialLinearConstraint method is the one that computes, and the LinearConstraint method is its fan-out over the two halves.

  1. Check that plc.A has one column per factor, size(plc.A, 2) == size(M, 2).
  2. Return a PartialLinearConstraint whose A is the re-based coefficient matrix and whose B is plc.B, taken over unchanged.
  3. For a whole LinearConstraint, apply steps 1 and 2 to ineq and to eq, skipping a half that is nothing, and rebuild the constraint from the two.

Arguments

Validation

  • size(plc.A, 2) == size(M, 2). Otherwise a DimensionMismatch names both counts.

Returns

  • lc::LinearConstraint or plc::PartialLinearConstraint: The same shape, over the assets. A is re-based and B is the input's own.

Related

source
PortfolioOptimisers.rebase_linear_constraintsFunction
rebase_linear_constraints(lce, sets::UniverseSets, basis, key; datatype, strict, bl_flag)

Re-base one wrapped shape. Dispatches on what ExposureConstraintEstimator is decorating:

Algorithm

The two single-shape methods reach the same row by two routes. The estimator route builds the row term by term, and the precomputed route projects a finished row.

  1. For a LinearConstraintEstimator, pick the key: lce.key when the wrapped estimator states one, and key otherwise.
  2. Call linear_constraints on lce.val against that key, with basis passed as rr. constraint_row_term then sums the selected columns of basis.M for each named term, which is the projection done one term at a time.
  3. For a precomputed LinearConstraint, call project_linear_constraint on it with basis.M, which projects the assembled coefficient matrix in one product. basis.M is the only field of the basis either route reads, so the regression's intercept never enters.
  4. For a vector, apply steps 1 to 3 to each element in turn and return the results in the order of the input.

Arguments

  • lce: The wrapped shape — a LinearConstraintEstimator, a LinearConstraint, or a vector of either.
  • sets: The declared universe the names resolve against.
  • basis: The loadings, as constraint_space_basis resolved them.
  • key: The key the names resolve against, when the wrapped estimator states none of its own.
  • datatype: Data type of the assembled row.
  • strict: If true, a name the universe does not resolve throws; if false, it warns and the term is dropped.
  • bl_flag: If true, enables Black-Litterman-style group expansion.

Returns

  • lc: An asset-space LinearConstraint, nothing when every row was dropped, or a vector of either in the order of lce.

Related

source