Search cross-validation
Tuning a pipeline widens the search boundary to the entire workflow: preprocessing hyperparameters (imputation statistics, missing-data thresholds) are searched jointly with prior, constraint, and optimiser hyperparameters, and every candidate is fitted per fold so stateful preprocessing never sees the test window. Lens keys address steps by name ("impute.stat"), by position (an integer key swaps the whole step), or by raw property path ("steps[2].stat").
PortfolioOptimisers.search_cross_validation — Method
search_cross_validation(pipe::Pipeline, gscv::GridSearchCrossValidation, data::Prices_RR)
search_cross_validation(pipe::Pipeline, rscv::RandomisedSearchCrossValidation, data::Prices_RR)Tune a Pipeline by grid (or randomised) search cross-validation on price- or returns-level input data.
The input is split into contiguous observation windows by gscv.cv (price-level splits keep stateful preprocessing inside the fold); for each candidate the lens grid is applied to the pipeline (keys resolved by pipeline_lens, so step names, step positions, and raw property paths all address steps), the whole workflow is fitted on the training window and scored on the test window via fit_and_score, and the scorer picks the winner. The randomised form samples the grid and delegates, exactly as for plain optimisers.
TimeDependent schedules resolve against the tuning folds: when a candidate is time-dependent, its schedules are sized to the tuning scheme's fold count (asserted per candidate — a grid value may swap a whole schedule in or out), and tuning fold j swaps in entry j via the pipeline-level update_time_dependent_estimator before fit_and_score runs. Lenses need no schedule-specific semantics: naming the step swaps the whole schedule as a grid value, and raw property paths address entries.
Arguments
pipe: The pipeline to tune.gscv/rscv: The search cross-validation estimator.data: Price- or returns-level input data (Prices_RR).
Returns
res::SearchCrossValidationResult: The tuned pipeline (res.opt), score matrices, lens/value grids, and selected index.
Related
PortfolioOptimisers.search_cross_validation — Method
search_cross_validation(pipe::Pipeline, gscv::GridSearchCrossValidation{<:Any, <:CombinatorialCrossValidation}, data::AbstractReturnsResult)Grid search cross-validation of a Pipeline over a CombinatorialCrossValidation scheme.
Combinatorial recombines its disjoint test groups into full-length backtest paths, so — like the plain-optimiser combinatorial method — scoring is per-path, not per-split: scoring a split in isolation would mix groups belonging to different paths. For each candidate the whole workflow runs through cross_val_predict (splits fitted, groups recombined by sort_predictions! into a PopulationPredictionResult), and expected_risk yields one score per path; the score matrix is therefore n_paths × n_candidates and the scorer selects across candidates as usual.
train_scores (only when gscv.train_score) keeps every per-fold in-sample score: a Vector of n_paths matrices, one per path, each folds_in_path × n_candidates (test scores stay one-per-path because a path's out-of-sample returns pool into one series, while its folds train on distinct in-sample windows).
Combinatorial runs at both levels here. At the price level a split's training rows are non-contiguous (gaps where the held-out groups sit), so the fold's PricesToReturns produces one spurious return per gap boundary — an accepted approximation in exchange for the combinatorial paths (see cross_val_predict(pipe::Pipeline, data::Prices_RR, cv::CombinatorialCrossValidation)). The randomised form delegates here through its grid.
Related
PortfolioOptimisers.fit_and_score — Method
fit_and_score(pipe::Pipeline,
scv::Union{<:GridSearchCrossValidation{<:Any, <:Any},
<:RandomisedSearchCrossValidation{<:Any, <:Any}},
cv::CrossValidationResult, rd::Prices_RR, i::Integer)Fit a Pipeline on the training window and score it on the test window for search cross-validation.
The whole workflow is fitted per fold: stateful preprocessing (universe, imputation parameters) is learned on the training window only, and predict replays it on the test window before the score is computed — no test information leaks into the preprocessing, which is the point of the pipeline (ADR 0028).
Arguments
pipe: The pipeline candidate.scv: The search cross-validation estimator carrying the risk measure, options, and train-score flag.data: Price- or returns-level input data.train_idx: Observation indices of the training window.test_idx: Observation indices of the test window.
Returns
(test_score, train_score): Signed scores;train_scoreisnothingunless requested.
Related
PortfolioOptimisers.pipeline_lens — Function
pipeline_lens(pipe::Pipeline, key) -> lensResolve a tuning key into an Accessors.jl lens on a Pipeline.
A leading step name resolves to the step's position (name → index → property path): "impute.stat" targets the stat field of the step named "impute", and a bare step name ("impute", :impute) or an integer position targets the whole step — swapping entire estimators as grid values needs no extra syntax. Keys whose leading segment is not a step name fall through to parse_lens, so raw property paths ("steps[2].stat", "steps[2]") and prebuilt lenses keep working.
A key that misses the step-name table and carries no lens structure at all (see has_lens_structure) is rejected instead — "imputer" is a typo, not a path, and reinterpreting it as a property access on the Pipeline struct tunes nothing at best and writes into a real field (names, steps) at worst. The Symbol arm fails closed on the same rule, reading a dot alone as the structure, because a symbol key never reaches Meta.parse.
Arguments
pipe: The pipeline being tuned.key: AGSCVKey: step name with optional trailing property path, integer step position, raw property path,Expr/Symbol, or a prebuilt lens.
Returns
lens: A composed Accessors.jl lens rooted at the pipeline.
Related
PortfolioOptimisers.has_lens_structure — Function
has_lens_structure(key::AbstractString) -> BoolReturn true when key carries lens structure — a dotted path ("impute.stat") or an index ("steps[1]").
The predicate the AbstractString arm of pipeline_lens uses to separate a typo from a raw property path when the leading segment misses the step-name table. A key with no structure at all is a bare identifier, which can only have been meant as a step name; a structured key is the documented raw-path form and falls through to parse_lens.
The Symbol arm tests for a dot alone, and that is not the same asymmetry the guard exists to remove: a string key is run through Meta.parse, where steps[1] is an index, while a symbol key is not, so an index in a symbol is a character in a property name.
Related
PortfolioOptimisers.pipeline_lens_val_grid — Function
pipeline_lens_val_grid(
pipe::Pipeline,
estval::AbstractVector{<:Pair{<:Any, <:AbstractVector}}
) -> Tuple{Any, Any}
Build the (lens, value) grid for tuning a Pipeline — the pipeline-aware counterpart of lens_val_grid, resolving keys through pipeline_lens so step names and positions address steps.
Arguments
pipe: The pipeline being tuned.estval: The parameter grid:key => valuespairs, a dict, or a vector of either (independent grids concatenated).
Validation
- The candidate count must not exceed
RESOURCE_LIMITS[].max_search_grid, asserted byassert_search_grid_capbefore the product is materialised.
Returns
(lenses, vals): Per-candidate lens vectors and value tuples.
Related
PortfolioOptimisers.pipeline_data_view — Function
pipeline_data_view(pr::AbstractPricesResult, idx) -> Any
pipeline_data_view(
pr::AbstractPricesResult,
idx,
idx2
) -> Any
Return the observation-window view of price- or returns-level data used by pipeline search cross-validation.
Arguments
data: The input data (AbstractPricesResultorAbstractReturnsResult).idx: Observation window into the rows ofdata.
Returns
data′: The windowed data at the same level.
Related
PortfolioOptimisers.cv_data_eltype — Function
cv_data_eltype(rd::AbstractReturnsResult) -> Any
Return the element type search-CV score matrices use for the given data level.
Related