Black-Litterman Views Generation: private API

PortfolioOptimisers.get_black_litterman_viewsFunction
get_black_litterman_views(lcs::PR_VecPR, sets::UniverseSets,
                          key::Option{<:AbstractString} = nothing;
                          datatype::DataType = Float64, strict::Bool = false)

Convert parsed Black-Litterman view equations into a BlackLittermanViews object.

get_black_litterman_views takes one or more ParsingResult objects (as produced by parse_equation), expands variable names using the provided UniverseSets, and assembles the canonical views matrix P and expected returns vector Q for Black-Litterman prior construction. The result is a BlackLittermanViews object suitable for use in portfolio optimisation routines.

key selects which universe the view names resolve against, exactly as it does for get_linear_constraints; nothing means sets.xkey. A view is never re-based — the estimator that owns it decides which distribution it lands on, and passes the matching key — so the assembled P is one row per view over length(sets.dict[key]) columns, and the message an unresolved name produces names the axis via universe_axis.

A view that is dropped is dropped, not refused. Its index joins excl, the remaining rows keep their order, and remove_excl_views drops the matching entry of a per-view confidence vector. When every view is dropped there is no row left and the return is nothing; bl_preroll is the caller that decides what that means.

A row is the unit of a drop. A view is a joint statement over several names with one right-hand side, so a name this function cannot resolve takes the whole row with it rather than only its own term: a + c == 0.05 assembled without c would fit a == 0.05, a different and stronger claim than the caller wrote, and one the update would blend the prior against. What the name's failure was decides only whether the drop is reported. A name on the counterpart axis — read with counterpart_axis_names, and in practice the Non-Investable Axis a door minted with investable_views — is dropped in silence under both settings of strict, because it was a correct name over the universe the caller was handed and the data moved it; the departure is announced once, by the door, through announce_non_investable. A name on neither axis is a typo, and is reported exactly as before.

Algorithm

  1. When lcs is a vector, check that it is not empty.
  2. Resolve the universe key k, which is key when it is given and sets.xkey otherwise. Read the universe nx = sets.dict[k], read its axis with universe_axis, giving axis for the diagnostic messages, and read the counterpart axis with counterpart_axis_names.
  3. For each parsed view lc, in the order the caller wrote them, run steps 4 to 7 over the row accumulator At, which holds length(nx) coefficients and starts at zero, and start that view not dropped.
  4. For each variable-coefficient pair (v, c) of lc, build the indicator Ai = (nx .== v). When Ai selects no entry, mark the row dropped; report the unresolved name through strict_diagnostic unless it names the counterpart axis, and record the row into ledger with record_non_investable_drop! when it does. Otherwise add Ai * c to At. Every name of the row is still visited, so a row carrying two typos names both.
  5. When the row was marked dropped, push the view's index i onto excl and go on to the next view.
  6. When At is still all zeros, every name of this view resolved and the row still summed to zero. Report it through strict_diagnostic, push i onto excl, and go on to the next view without writing a row.
  7. Append At to P and lc.rhs to Q.
  8. When P holds at least one row, reshape it to length(nx) rows and transpose it, so P is one view per row. Return a BlackLittermanViews over P, Q and excl, where an empty excl is passed as nothing. When P is empty, return nothing.

Arguments

  • lcs: A single ParsingResult or a vector of such objects, representing parsed Black-Litterman view equations.
  • sets: A UniverseSets object specifying the universes and groupings.
  • key: Key to specify the universe in sets.dict that names resolve against. If nothing, the key is taken from sets.xkey — or, where the caller is written against another declared axis, from that axis' key.
  • datatype: Numeric type for coefficients and expected returns.
  • strict: If true, throws an error if a variable or group is not found in sets; if false, issues a warning.
  • ledger: The door's ledger of departure casualties, or nothing when nobody is collecting. A row dropped for a name on the counterpart axis is recorded into it through record_non_investable_drop!.

Validation

  • When lcs is a vector, !isempty(lcs).
  • A name that matches no entry of sets.dict[key] and none of the counterpart axis raises through strict_diagnostic when strict is true, and warns otherwise. The message names the axis. The row is dropped either way.
  • A name on the counterpart axis drops its row in silence, under both settings of strict.
  • A view whose names all resolve and whose coefficients cancel raises the same way, and is dropped when strict is false.
  • The assembled pair passes the BlackLittermanViews constructor's own checks.

Returns

  • blv::Option{<:BlackLittermanViews}: The assembled views matrix P, one row per view over length(sets.dict[key]) columns, the expected returns vector Q, and the indices excl of the views that resolved no name. nothing when no view resolved.

Examples

julia> sets = UniverseSets(; xkey = "nx", dict = Dict("nx" => ["A", "B", "C"]));julia> lcs = parse_equation(["A + B == 0.05", "C == 0.02"]);julia> PortfolioOptimisers.get_black_litterman_views(lcs, sets)BlackLittermanViews     P ┼ 2×3 LinearAlgebra.Transpose{Float64, Matrix{Float64}}     Q ┼ Vector{Float64}: [0.05, 0.02]  excl ┴ nothing

Related

source
PortfolioOptimisers.assert_bl_views_confFunction
assert_bl_views_conf(::Nothing, args...)
assert_bl_views_conf(views_conf::Number, ::EqnType)
assert_bl_views_conf(views_conf::VecNum, val::EqnType)
assert_bl_views_conf(views_conf::Num_VecNum, views::LinearConstraintEstimator)
assert_bl_views_conf(views_conf::Num_VecNum, views::BlackLittermanViews)

Validate Black-Litterman view confidence specification.

assert_bl_views_conf checks that the view confidence parameter(s) provided for Black-Litterman prior construction are valid. It supports scalar and vector confidence values, and works with views specified as equations, constraint estimators, or canonical views objects. The function enforces that confidence values are strictly between 0 and 1, and that the number of confidence values matches the number of views when a vector is given.

A scalar confidence is one confidence for every view, whatever the number of views. Only a vector states one confidence per view, so only a vector is counted against the views. Both shapes reach the same calc_omega answer: a scalar $v$ and the constant vector of $v$ give the same $\mathbf{\Omega}$.

The unit-interval bound is load-bearing rather than cosmetic. calc_omega maps a confidence $v$ to the scale $1/v - 1$, which is negative for every $v > 1$ and for every $v < 0$. A view uncertainty matrix with a negative diagonal entry is not a covariance, and the estimator returns an answer built from it rather than raising.

Arguments

  • views_conf: Scalar or vector of confidence values.
  • views: Black-Litterman views, which may be equations.
  • val: The equations of a LinearConstraintEstimator, unwrapped.

Validation

Each method selects one shape of views_conf and one shape of views, and refuses what that pair does not admit.

  • (::Nothing, args...): no confidence was given, so nothing is checked.
  • (::Number, ::EqnType): 0 < views_conf < 1, through assert_unit_interval. The count is not checked, because one scalar covers any number of equations.
  • (::VecNum, ::EqnType): when val is a vector of equations, length(val) == length(views_conf); when val is one equation, length(views_conf) == 1. Then all(x -> 0 < x < 1, views_conf).
  • (::Num_VecNum, ::LinearConstraintEstimator): selects nothing itself, and forwards views.val to the two methods above.
  • (::Num_VecNum, ::BlackLittermanViews): when views_conf is a vector, length(views_conf) == length(views.Q). Then all(x -> 0 < x < 1, views_conf). This is the only site that sees the confidences of a precomputed pair, because such a pair resolves no name.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_blFunction
assert_bl(
    views::Union{BlackLittermanViews, LinearConstraintEstimator},
    sets::Union{Nothing, UniverseSets},
    views_conf::Union{Nothing, Number, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    tau::Union{Nothing, Number}
)

Assert that the Black-Litterman prior's views, sets, view confidences, and blending parameter are valid.

This is the one guard every Black-Litterman constructor calls, so the four families refuse the same input. It does not look at the returns matrix, which the constructor never sees; prior checks the universe against X and bl_preroll checks the width of P against the covariance.

Validation

  • When views is a LinearConstraintEstimator, !isnothing(sets), because the names of such a view resolve against a universe.
  • views_conf is checked by assert_bl_views_conf, against the shape of views.
  • When tau is given, tau > 0. A nothing is admitted, and bl_preroll resolves it to 1/T.

Related

source