Algostream_optimization.EnsembleCombining strategies, and measuring whether the combination actually diversifies.
Rolling weights, not full-sample weights. Fitting minimum-variance weights on the whole sample and reporting the resulting Sharpe is in-sample optimization wearing a diversification costume — the covariance you optimized against is the one you measured. rolling_combine re-estimates on a trailing window and applies the weights forward, which is the version whose numbers mean something. combine exists for the one-shot case and says so.
module Metrics = Algostream_performance.Metricstype weighting = | Equal| Inverse_volatility| Sharpe_weightedproportional to positive Sharpe; a member with negative Sharpe gets zero
*)| Risk_parityequal marginal risk contribution, by fixed-point iteration
*)| Min_varianceanalytic Σ⁻¹1 / 1'Σ⁻¹1 via Cholesky, then clipped to long-only and renormalized.
The clip makes it approximate. An exact long-only minimum-variance portfolio is a quadratic program, and adding a QP solver for one function was not worth a new dependency. When the unconstrained solution is already non-negative — common for weakly correlated members — the clip does nothing and the answer is exact.
*)| Custom of float arraytype result = {weights : (string * float) array;combined : Metrics.t;diversification_ratio : float;weighted average volatility / portfolio volatility; > 1 is the point
*)effective_n : float;1 / Σwᵢ²; how many members you are *effectively* holding
avg_pairwise_correlation : float;marginal_risk_contribution : (string * float) array;incremental_sharpe : (string * float) array;change in combined Sharpe from dropping each member. Negative means the member is subtracting value even if its standalone Sharpe is positive.
*)correlation_matrix : float array array;}val combine :
members:member array ->
weighting:weighting ->
periods_per_year:float ->
?risk_free_rate_ann:float ->
unit ->
(result, error) Stdlib.resultOne-shot combination over the full sample. In-sample by construction — see the header.
val rolling_combine :
members:member array ->
weighting:weighting ->
lookback:int ->
rebalance_every:int ->
periods_per_year:float ->
?risk_free_rate_ann:float ->
unit ->
(result, error) Stdlib.resultWeights re-estimated on a trailing lookback window and rebalanced every rebalance_every periods, then applied forward. The honest version.
Greedily select members whose pairwise correlation with those already chosen stays below max_corr, up to max_n. A cheap pre-filter before weighting.
val result_to_string : result -> string