sklearn & skfolio interop¶
Every estimator-shaped class in Fundcloud is a drop-in for both sklearn and skfolio — no adapters required. The same FeaturePipeline, MeanRisk, PurgedKFold, and Portfolio objects you'd use in a notebook are the ones you compose inside a sklearn.pipeline.Pipeline or nest under GridSearchCV. Fit/predict semantics, parameter-name conventions (__ separator for nested access), and get_params / set_params behaviour all match sklearn's estimator_checks suite, which means joblib serialisation, grid search, and cross-validated scoring round-trip cleanly.
Why this matters for research-to-production
The path from a notebook backtest to a scheduled production pipeline is almost always the place where ad-hoc code accumulates. By keeping everything on the sklearn estimator contract, the same object graph survives the move — the notebook code becomes the production code, not a rewrite of it.
Contracts¶
| Component | Base class | Implements |
|---|---|---|
Transformer (IndicatorSpec, FeaturePipeline) |
sklearn.base.TransformerMixin, BaseEstimator |
fit(X, y=None), transform(X) |
CV splitter (PurgedKFold, EmbargoedKFold) |
sklearn.model_selection.BaseCrossValidator |
split, get_n_splits |
Optimiser (MeanRisk, HRP, EqualWeighted, MVO) |
skfolio BaseOptimization when [pf] is installed; sklearn BaseEstimator otherwise |
fit(X), predict(X) -> Portfolio, score(X) |
Nested pipelines¶
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
from fundcloud.features import FeaturePipeline
from fundcloud.features.indicators import RSI, SMA
from fundcloud.optimize import MeanRisk, RiskMeasure
from fundcloud.validate import EmbargoedKFold
pipe = Pipeline([
("features", FeaturePipeline([
("rsi", RSI(timeperiod=14)),
("sma", SMA(timeperiod=20)),
])),
("optim", MeanRisk(risk_measure=RiskMeasure.CVAR)),
])
search = GridSearchCV(
pipe,
param_grid={"optim__min_weights": [0.0, 0.02, 0.05]},
cv=EmbargoedKFold(n_splits=5, purge=3, embargo=1),
)
search.fit(returns_panel)
skfolio round-trip¶
from fundcloud.portfolio import Portfolio
# Fundcloud → skfolio
sk_pf = fc_pf.to_skfolio()
# skfolio → Fundcloud
fc_pf = Portfolio.from_skfolio(sk_pf)
Portfolio.from_skfolio preserves returns + weights (when skfolio exposes
a per-period weights matrix), and Portfolio.to_skfolio builds a fresh
skfolio.Portfolio with the correct shape.
Without the [pf] extra¶
If skfolio isn't installed:
fundcloud.optimize.EqualWeighted/InverseVolatilityresolve to the pure-Python fallback class (samefit/predictcontract, long-only- fully-invested constraints,
scipy.optimize.minimize). fundcloud.optimize.MVOis always available as the pure-Python MVO.- Every other skfolio optimiser raises
ImportErrorwith a clear install hint when first attribute-accessed.
The fallbacks also pass sklearn.utils.estimator_checks.check_estimator,
so Pipeline / GridSearchCV round-trips are unchanged.