Base search cross validation: private API

PortfolioOptimisers.finite_candidate_indexFunction
finite_candidate_index(
    scorer::Union{Function, CrossValidationSearchScorer},
    test_scores::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> Any

Choose the winning candidate of a search, and never let a failed one win.

A candidate that did not finish every fold carries a non-finite entry in its column of the score matrix, and argmax over [0.80, NaN, 0.50] returns the position of the NaN. So the scorer is handed the columns whose every entry is finite, and the index it returns — a position in the matrix it received — is mapped back to the grid through the list of those columns.

The scorer therefore never sees a failed candidate, and it may compute anything on the matrix it receives: a mean, a spread, a rank. A -Inf substitution would be safe only for a scorer that reads order alone, because a column holding -Inf gives NaN for a spread and the NaN wins again.

The raw matrix stays on the result, so its columns line up with the grid and a reader sees which fold failed.

Algorithm

  1. Mark the columns of test_scores whose every entry is finite.
  2. Throw an IsNonFiniteError when no column is finite.
  3. Call scorer on the view of test_scores at the finite columns.
  4. Return the finite column the index the scorer gave names.

Arguments

  • scorer: The search scorer (CrossValSearchScorer).
  • test_scores: The folds × candidates, or paths × candidates, score matrix.

Validation

  • At least one candidate must have finished every fold.

Returns

  • opt_idx::Integer: The position of the winning candidate in the parameter grid.

Related

source
PortfolioOptimisers.assert_search_entryFunction
assert_search_entry(est, cv)

Refuse an estimator that is not the configuration alone at the door of a search, once, before any candidate is built.

A search scores every candidate through the one fold loop, and a lens is applied before that loop's warm-up, so a cold estimator seeds one state per candidate and nothing is shared or reset. A warm one is refused by name here rather than inside the candidate loop, where the workers of gscv.ex would raise it up to once per candidate. Under every Fold Fit the walk is online_entry_state, because a search tunes the configuration alone whatever the scheme does with it; under an OnlineStep it is the whole assert_online_entry, so a schedule on a stateful field is refused at the door too.

Arguments

  • est: The estimator the search tunes.
  • cv: The scheme the search scores over.

Validation

  • No cache in the tree of est holds a state. An ArgumentError naming the field is thrown otherwise.
  • Under an OnlineStep, everything assert_online_entry refuses.

Related

source
PortfolioOptimisers.pin_drawFunction
pin_draw(cv::MultipleRandomised)
pin_draw(cv)

Fix the folds a search scores every candidate over.

A search splits its data once and scores every candidate against the same folds, so a scheme whose split draws from a random stream must draw the same folds for every candidate. A MultipleRandomised with no seed draws from its rng afresh at every split, and the fold loop splits once per candidate, so this draws one seed from that rng and returns the scheme with it set: every candidate's split then resolves the same stream, and so does the search's own split for the row layout. A seeded scheme, and every other scheme, is returned as it is, because its split is already a function of the data alone.

Arguments

  • cv: The search's scheme.

Returns

  • cv: The scheme, with a seed set when its split would otherwise draw afresh.

Related

source
PortfolioOptimisers.score_rowsFunction
score_rows(cvr::CrossValidationResult)
score_rows(cvr::MultipleRandomisedResult)

The rows of a search's score matrix that a candidate's predictions fill, in the order the fold loop returns them.

The score matrix is indexed by split: row j is the j-th fold the scheme enumerates, and a reader lines the rows up with split's enumeration. A contiguous scheme returns one MultiPeriodPredictionResult whose predictions are in that order, so its rows are every row in order. A MultipleRandomised returns one MultiPeriodPredictionResult per path, each sorted by the start of its test window through sort_predictions!, so its rows are one vector per path: the split rows of that path, in the order the path's predictions come back. write_candidate_scores! zips a candidate's predictions against these rows, so the matrix keeps split's order under either shape.

Arguments

  • cvr: The split the search scores over.

Returns

  • rows: The row indices, one range for a contiguous scheme, or one vector of indices per path for a multiple-randomised one.

Related

source
PortfolioOptimisers.write_candidate_scores!Function
write_candidate_scores!(test_scores::MatNum, train_scores::Option{<:MatNum}, i::Integer,
                        predictions::MultiPeriodPredictionResult, rows, r, sgn, kwargs)
write_candidate_scores!(test_scores::MatNum, train_scores::Option{<:MatNum}, i::Integer,
                        predictions::PopulationPredictionResult, rows, r, sgn, kwargs)

Write the per-fold scores of candidate i into column i of a search's score matrices.

The candidate's predictions are what fit_and_predict returned over the search's scheme, one per fold, and rows is score_rows of that scheme's split, so prediction and row are zipped and the column reads in split's order. A population is one series per path, and each path is written against its own rows. Each fold's test score is expected_risk of the fold's prediction, and its train score, written only when train_scores is a matrix, is the risk of the fold's fitted result over its own sample. Both are multiplied by sgn, 1 when bigger_is_better holds for r and -1 otherwise, so a higher score is always better whatever measure r is, which is the orientation a CrossValidationSearchScorer relies on. A fold that failed scores NaN, and finite_candidate_index keeps that column from the scorer.

Arguments

  • test_scores: The folds × candidates test score matrix.
  • train_scores: The train score matrix of the same shape, or nothing when the search records none.
  • i: The candidate's column.
  • predictions: The candidate's predictions over the scheme.
  • rows: The rows the predictions fill, from score_rows.
  • r: The risk measure the search scores with.
  • sgn: The sign that orients r so that higher is better.
  • kwargs: The keyword arguments forwarded to expected_risk.

Related

source
PortfolioOptimisers.parse_lensFunction
parse_lens(key::AbstractString)

Parse a hyperparameter key string into an Accessors.jl lens.

Converts a dotted string path (e.g., "opt.pe.ce") into a composable lens for getting and setting nested fields of an estimator object.

Arguments

  • key: Dotted field path string.

Validation

  • String keys longer than EQUATION_LIMITS[].max_length are rejected before Meta.parse.
  • Keys deeper than EQUATION_LIMITS[].max_depth are rejected before the lens-building walk. A string key is measured after Meta.parse, so one depth bound holds for every shape of key.

Returns

  • Composed Accessors.jl lens.

Related

source
PortfolioOptimisers.assert_search_grid_capFunction
assert_search_grid_cap(total::Integer, detail::AbstractString)
assert_search_grid_cap(factors::AbstractVector{<:Pair})
assert_search_grid_cap(estval::AbstractVector{<:Pair{<:Any, <:AbstractVector}})
assert_search_grid_cap(estval::AbstractDict{<:Any, <:AbstractVector})

Assert the total number of search-grid candidates does not exceed the active max_search_grid ceiling.

The grid is an Iterators.product materialised by collect, so k tuned parameters of N values each cost N^k candidates and N^k full cross-validated fits. A per-parameter check never sees that product — the same reasoning that gave the frontier sweep assert_frontier_sweep_cap — so the cap is asserted where the grid is formed, before the collect.

Concatenated parameter sets are a sum of products, not a product: each set is capped as it is built, and the concatenated total is capped again on the way out.

Arguments

  • total: Candidate count already computed (a BigInt on the product path, since k parameters at the ceiling overflow an Int64 before the check reads it).
  • detail: Phrase naming how total was made, interpolated into the message.
  • factors: key => value-count pairs, one per tuned parameter.
  • estval: The parameter grid itself, from which the factors are derived.

Returns

  • nothing.

Throws

  • DomainError if the count exceeds RESOURCE_LIMITS[].max_search_grid. The message names the count, the factors that made it, and the knob that raises the ceiling.

Related

source
PortfolioOptimisers._expr_to_lensFunction
_expr_to_lens(ex::Symbol)

Convert a bare symbol into a PropertyLens for field access.

Base case for the lens-building recursion: a bare symbol maps directly to an Accessors.PropertyLens.

Arguments

  • ex::Symbol: A field name symbol.

Returns

  • Accessors.PropertyLens for the symbol.

Related

source
PortfolioOptimisers._eval_indexFunction
_eval_index(x)

Evaluate a literal index node in the AST without runtime eval.

Converts integer, symbol, or vector expression AST nodes to concrete index values for use in Accessors.IndexLens.

Arguments

  • x::Integer: An integer index.
  • x::Symbol: A symbolic index.
  • ex::Expr: A vector expression (:vect head).

Returns

  • The evaluated index value.

Related

source
PortfolioOptimisers.expr_to_lens_chainFunction
expr_to_lens_chain(ex)

Convert a Julia expression to a chain of lens accessors.

Internal helper for parsing hyperparameter key strings into composable Accessors.jl lenses.

Arguments

  • ex: Julia expression representing a field access chain.

Returns

  • Composed lens.

Related

source
PortfolioOptimisers.pipeline_asset_viewFunction
pipeline_asset_view(data::AbstractReturnsResult, cols)
pipeline_asset_view(data::AbstractPricesResult, cols)

Return the asset-subset view of price- or returns-level data for a MultipleRandomised resampling path — all observations, only the columns cols.

The two levels index assets through different port_opt_view arities: returns take the two-argument asset form port_opt_view(rd, cols), prices the observation-then-asset form port_opt_view(pr, :, cols). This wrapper hides that asymmetry so pipeline_path_fit_and_predict stays level-agnostic.

Related

source