Package: skfolio
skfolioskfoliois a Python library for portfolio optimization built on top of scikit-learn.
It offers a unified interface and tools compatible with scikit-learn to build, fine-tune,
and cross-validate portfolio models.It is distributed under the open source 3-Clause BSD license.Important linksDocumentation:https://skfolio.org/Examples:https://skfolio.org/auto_examples/User Guide:https://skfolio.org/user_guide/GitHub Repo:https://github.com/skfolio/skfolio/Installationskfoliois available on PyPI and can be installed with:pip install -U skfolioDependenciesskfoliorequires:python (>= 3.10)numpy (>= 1.23.4)scipy (>= 1.8.0)pandas (>= 1.4.1)cvxpy (>= 1.4.1)scikit-learn (>= 1.3.2)joblib (>= 1.3.2)plotly (>= 5.15.0)Key ConceptsSince the development of modern portfolio theory by Markowitz (1952), mean-variance
optimization (MVO) has received considerable attention.Unfortunately, it faces a number of shortcomings, including high sensitivity to the
input parameters (expected returns and covariance), weight concentration, high turnover,
and poor out-of-sample performance.It is well known that naive allocation (1/N, inverse-vol, etc.) tends to outperform
MVO out-of-sample (DeMiguel, 2007).Numerous approaches have been developed to alleviate these shortcomings (shrinkage,
additional constraints, regularization, uncertainty set, higher moments, Bayesian
approaches, coherent risk measures, left-tail risk optimization, distributionally robust
optimization, factor model, risk-parity, hierarchical clustering, ensemble methods,
pre-selection, etc.).With this large number of methods, added to the fact that they can be composed together,
there is a need for a unified framework with a machine learning approach to perform
model selection, validation, and parameter tuning while reducing the risk of data
leakage and overfitting.This framework is built on scikit-learn’s API.Available modelsPortfolio Optimization:Naive:Equal-WeightedInverse-VolatilityRandom (Dirichlet)Convex:Mean-RiskRisk BudgetingMaximum DiversificationDistributionally Robust CVaRClustering:Hierarchical Risk ParityHierarchical Equal Risk ContributionNested Clusters OptimizationEnsemble Methods:Stacking OptimizationExpected Returns Estimator:EmpiricalExponentially WeightedEquilibriumShrinkageCovariance Estimator:EmpiricalGerberDenoisingDetoningExponentially WeightedLedoit-WolfOracle Approximating ShrinkageShrunk CovarianceGraphical Lasso CVDistance Estimator:Pearson DistanceKendall DistanceSpearman DistanceCovariance Distance (based on any of the above covariance estimators)Distance CorrelationVariation of InformationPrior Estimator:EmpiricalBlack & LittermanFactor ModelUncertainty Set Estimator:On Expected Returns:EmpiricalCircular BootstrapOn Covariance:EmpiricalCircular bootstrapPre-Selection Transformer:Non-Dominated SelectionSelect K Extremes (Best or Worst)Drop Highly Correlated AssetsCross-Validation and Model Selection:Compatible with allsklearnmethods (KFold, etc.)Walk ForwardCombinatorial Purged Cross-ValidationHyper-Parameter Tuning:Compatible with allsklearnmethods (GridSearchCV, RandomizedSearchCV)Risk Measures:VarianceSemi-VarianceMean Absolute DeviationFirst Lower Partial MomentCVaR (Conditional Value at Risk)EVaR (Entropic Value at Risk)Worst RealizationCDaR (Conditional Drawdown at Risk)Maximum DrawdownAverage DrawdownEDaR (Entropic Drawdown at Risk)Ulcer IndexGini Mean DifferenceValue at RiskDrawdown at RiskEntropic Risk MeasureFourth Central MomentFourth Lower Partial MomentSkewKurtosisOptimization Features:Minimize RiskMaximize ReturnsMaximize UtilityMaximize RatioTransaction CostsManagement FeesL1 and L2 RegularizationWeight ConstraintsGroup ConstraintsBudget ConstraintsTracking Error ConstraintsTurnover ConstraintsQuickstartThe code snippets below are designed to introduce the functionality ofskfolioso you
can start using it quickly. It follows the same API as scikit-learn.Importsfromsklearnimportset_configfromsklearn.model_selectionimport(GridSearchCV,KFold,RandomizedSearchCV,train_test_split,)fromsklearn.pipelineimportPipelinefromscipy.statsimportloguniformfromskfolioimportRatioMeasure,RiskMeasurefromskfolio.datasetsimportload_factors_dataset,load_sp500_datasetfromskfolio.model_selectionimport(CombinatorialPurgedCV,WalkForward,cross_val_predict,)fromskfolio.momentsimport(DenoiseCovariance,DetoneCovariance,EWMu,GerberCovariance,ShrunkMu,)fromskfolio.optimizationimport(MeanRisk,NestedClustersOptimization,ObjectiveFunction,RiskBudgeting,)fromskfolio.pre_selectionimportSelectKExtremesfromskfolio.preprocessingimportprices_to_returnsfromskfolio.priorimportBlackLitterman,EmpiricalPrior,FactorModelfromskfolio.uncertainty_setimportBootstrapMuUncertaintySetLoad Datasetprices=load_sp500_dataset()Train/Test splitX=prices_to_returns(prices)X_train,X_test=train_test_split(X,test_size=0.33,shuffle=False)Minimum Variancemodel=MeanRisk()Fit on Training Setmodel.fit(X_train)print(model.weights_)Predict on Test Setportfolio=model.predict(X_test)print(portfolio.annualized_sharpe_ratio)print(portfolio.summary())Maximum Sortino Ratiomodel=MeanRisk(objective_function=ObjectiveFunction.MAXIMIZE_RATIO,risk_measure=RiskMeasure.SEMI_VARIANCE,)Denoised Covariance & Shrunk Expected Returnsmodel=MeanRisk(objective_function=ObjectiveFunction.MAXIMIZE_RATIO,prior_estimator=EmpiricalPrior(mu_estimator=ShrunkMu(),covariance_estimator=DenoiseCovariance()),)Uncertainty Set on Expected Returnsmodel=MeanRisk(objective_function=ObjectiveFunction.MAXIMIZE_RATIO,mu_uncertainty_set_estimator=BootstrapMuUncertaintySet(),)Weight Constraints & Transaction Costsmodel=MeanRisk(min_weights={"AAPL":0.10,"JPM":0.05},max_weights=0.8,transaction_costs={"AAPL":0.0001,"RRC":0.0002},groups=[["Equity"]*3+["Fund"]*5+["Bond"]*12,["US"]*2+["Europe"]*8+["Japan"]*10,],linear_constraints=["Equity <= 0.5 * Bond","US >= 0.1","Europe >= 0.5 * Fund","Japan <= 1",],)model.fit(X_train)Risk Parity on CVaRmodel=RiskBudgeting(risk_measure=RiskMeasure.CVAR)Risk Parity & Gerber Covariancemodel=RiskBudgeting(prior_estimator=EmpiricalPrior(covariance_estimator=GerberCovariance()))Nested Cluster Optimization with Cross-Validation and Parallelizationmodel=NestedClustersOptimization(inner_estimator=MeanRisk(risk_measure=RiskMeasure.CVAR),outer_estimator=RiskBudgeting(risk_measure=RiskMeasure.VARIANCE),cv=KFold(),n_jobs=-1,)Randomized Search of the L2 Normrandomized_search=RandomizedSearchCV(estimator=MeanRisk(),cv=WalkForward(train_size=252,test_size=60),param_distributions={"l2_coef":loguniform(1e-3,1e-1),},)randomized_search.fit(X_train)best_model=randomized_search.best_estimator_print(best_model.weights_)Grid Search on Embedded Parametersmodel=MeanRisk(objective_function=ObjectiveFunction.MAXIMIZE_RATIO,risk_measure=RiskMeasure.VARIANCE,prior_estimator=EmpiricalPrior(mu_estimator=EWMu(alpha=0.2)),)print(model.get_params(deep=True))gs=GridSearchCV(estimator=model,cv=KFold(n_splits=5,shuffle=False),n_jobs=-1,param_grid={"risk_measure":[RiskMeasure.VARIANCE,RiskMeasure.CVAR,RiskMeasure.VARIANCE.CDAR,],"prior_estimator__mu_estimator__alpha":[0.05,0.1,0.2,0.5],},)gs.fit(X)best_model=gs.best_estimator_print(best_model.weights_)Black & Litterman Modelviews=["AAPL - BBY == 0.03 ","CVX - KO == 0.04","MSFT == 0.06 "]model=MeanRisk(objective_function=ObjectiveFunction.MAXIMIZE_RATIO,prior_estimator=BlackLitterman(views=views),)Factor Modelfactor_prices=load_factors_dataset()X,y=prices_to_returns(prices,factor_prices)X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.33,shuffle=False)model=MeanRisk(prior_estimator=FactorModel())model.fit(X_train,y_train)print(model.weights_)portfolio=model.predict(X_test)print(portfolio.calmar_ratio)print(portfolio.summary())Factor Model & Covariance Detoningmodel=MeanRisk(prior_estimator=FactorModel(factor_prior_estimator=EmpiricalPrior(covariance_estimator=DetoneCovariance())))Black & Litterman Factor Modelfactor_views=["MTUM - QUAL == 0.03 ","SIZE - TLT == 0.04","VLUE == 0.06"]model=MeanRisk(objective_function=ObjectiveFunction.MAXIMIZE_RATIO,prior_estimator=FactorModel(factor_prior_estimator=BlackLitterman(views=factor_views),),)Pre-Selection Pipelineset_config(transform_output="pandas")model=Pipeline([("pre_selection",SelectKExtremes(k=10,highest=True)),("optimization",MeanRisk()),])model.fit(X_train)portfolio=model.predict(X_test)K-fold Cross-Validationmodel=MeanRisk()mmp=cross_val_predict(model,X_test,cv=KFold(n_splits=5))# mmp is the predicted MultiPeriodPortfolio object composed of 5 Portfolios (1 per testing fold)mmp.plot_cumulative_returns()print(mmp.summary()Combinatorial Purged Cross-Validationmodel=MeanRisk()cv=CombinatorialPurgedCV(n_folds=10,n_test_folds=2)print(cv.get_summary(X_train))population=cross_val_predict(model,X_train,cv=cv)population.plot_distribution(measure_list=[RatioMeasure.SHARPE_RATIO,RatioMeasure.SORTINO_RATIO])population.plot_cumulative_returns()print(population.summary())RecognitionWe would like to thank all contributors behind our direct dependencies, such as
scikit-learn and cvxpy, but also the contributors of the following resources that were a
source of inspiration:PyPortfolioOptRiskfolio-Libscikit-portfoliomicropredictionstatsmodelsrsomegautier.marti.aiCitationIf you useskfolioin a scientific publication, we would appreciate citations:Bibtex entry:@misc{skfolio,
      author = {Hugo Delatte, Carlo Nicolini},
      title = {skfolio},
      year  = {2023},
      url   = {https://github.com/skfolio/skfolio}