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_validationMethod
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), and the candidate is scored through cross_val_predict(pipe_i, data, gscv.cv; ex = SequentialEx()), the one fold loop every cross-validation entry point runs. So the candidate runs the scheme it declared: every fold fits the whole workflow on its training window and scores it on its test window, a walk-forward threads the previous fold's weights through the scheme's pws, and a TimeDependent schedule resolves per fold against the fold's TimeDependentContext, sized to the scheme's fold count and asserted per candidate, because a grid value may swap a whole schedule in or out. Lenses need no schedule-specific semantics: naming the step swaps the whole schedule as a grid value, and raw property paths address entries. Candidates run in parallel over gscv.ex, the folds inside one in sequence. One row per fold, in split's order, through write_candidate_scores! and score_rows; a scheme whose split draws at random is fixed once through pin_draw. The scorer picks the winner among the candidates that finished every fold, through finite_candidate_index, so a candidate that failed a fold never wins. The randomised form samples the grid and delegates, exactly as for plain optimisers.

A scheme that declares a Fold Fit runs every candidate through the Pipeline's online step: each candidate is warmed up once and folded fold by fold through partial_fit!, and read out through fit(pipe) where a refit would have run, so the search picks the candidate the batch search picks over the same steps. A warm pipeline is refused once, before the grid, through assert_search_entry; the refit route Online(pipe) is not a search root, because the grid's lenses address the pipeline's steps and not a wrapper's.

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

source
PortfolioOptimisers.search_cross_validationMethod
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, through finite_candidate_index, so a candidate that failed a path never wins.

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

source