skfolio#
skfolio 是一个构建在 scikit-learn 之上的 Python 投资组合优化库。 它提供了一个统一的接口和与 scikit-learn 兼容的工具,用于构建、微调和交叉验证投资组合模型。
它在开源的三条款BSD许可证下分发。

安装#
skfolio
在 PyPI 上可用,安装方法如下:
$ pip install skfolio
关键概念#
自从马科维茨(1952年)提出现代投资组合理论以来,均值-方差优化(MVO)受到了相当大的关注。
不幸的是,它面临许多缺点,包括对输入参数(预期收益和协方差)的高敏感性、权重集中、高周转率和较差的样本外表现。
众所周知,简单分配 (1/N,反向波动率等) 在样本外往往优于均值方差优化 (DeMiguel, 2007)。
已经开发了许多方法来缓解这些缺点(收缩、附加约束、正则化、不确定性集、更高的矩、贝叶斯方法、一致性风险度量、左尾风险优化、分布鲁棒优化、因子模型、风险平价、层次聚类、集成方法、预选择等)。
鉴于方法数量众多,并且它们可以组合,迫切需要一个统一的框架,采用机器学习的方法来执行模型选择、验证和参数调整,同时减少数据泄漏和过拟合的风险。
该框架建立在 scikit-learn 的 API 之上。
可用模型#
- Portfolio Optimization:
- Naive:
等权重
逆波动性
随机(狄利克雷分布)
- Convex:
均值-风险
风险预算
最大化多样化
分布鲁棒的CVaR
- Clustering:
层级风险平价
层次等风险贡献
嵌套聚类优化
- Ensemble Methods:
堆叠优化
- Expected Returns Estimator:
经验
指数加权
平衡
收缩
- Covariance Estimator:
经验
格伯
去噪
去噪
指数加权
莱多伊特-沃尔夫
Oracle 近似收缩
收缩协方差
图形套索CV
隐含协方差
- Distance Estimator:
皮尔逊距离
肯德尔距离
斯皮尔曼距离
协方差距离(基于上述任意协方差估计器)
距离相关性
信息的变异
- Prior Estimator:
经验
黑色和利特曼
因子模型
- Uncertainty Set Estimator:
- On Expected Returns:
经验
循环引导
- On Covariance:
经验
循环引导
- Pre-Selection Transformer:
非支配选择
选择K极值(最佳或最差)
剔除高度相关的资产
选择非过期资产
选择完整资产(处理晚期成立、退市等)
- Cross-Validation and Model Selection:
与所有
sklearn
方法兼容(KFold 等)走向前方
组合清除交叉验证
- Hyper-Parameter Tuning:
与所有
sklearn
方法兼容(GridSearchCV, RandomizedSearchCV)
- Risk Measures:
方差
半方差
平均绝对偏差
首个下部分矩
条件价值风险 (Conditional Value at Risk)
EVaR (熵值风险)
最糟糕的认知
条件风险下的减值 (CDaR)
最大回撤
平均回撤
EDaR (风险下跌的熵)
溃疡指数
基尼均值差异
风险价值
风险回撤
熵风险度量
第四中心距
第四下部分矩
偏斜
峰度
- Optimization Features:
最小化风险
最大化回报
最大化效用
最大化比率
交易成本
管理费用
L1 和 L2 正则化
重量约束
组约束
预算约束
跟踪误差约束
营业额约束
基数和组基数约束
阈值(多头和空头)约束
快速入门#
下面的代码片段旨在介绍 skfolio
的功能,以便您可以快速开始使用它。它遵循与 scikit-learn 相同的 API。
欲了解更多详细信息,请参见 示例、用户指南 和 API参考。
导入#
from sklearn import set_config
from sklearn.model_selection import (
GridSearchCV,
KFold,
RandomizedSearchCV,
train_test_split,
)
from sklearn.pipeline import Pipeline
from scipy.stats import loguniform
from skfolio import RatioMeasure, RiskMeasure
from skfolio.datasets import load_factors_dataset, load_sp500_dataset
from skfolio.model_selection import (
CombinatorialPurgedCV,
WalkForward,
cross_val_predict,
)
from skfolio.moments import (
DenoiseCovariance,
DetoneCovariance,
EWMu,
GerberCovariance,
ShrunkMu,
)
from skfolio.optimization import (
MeanRisk,
NestedClustersOptimization,
ObjectiveFunction,
RiskBudgeting,
)
from skfolio.pre_selection import SelectKExtremes
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import BlackLitterman, EmpiricalPrior, FactorModel
from skfolio.uncertainty_set import BootstrapMuUncertaintySet
加载数据集#
prices = load_sp500_dataset()
训练/测试划分#
X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)
最小方差#
model = MeanRisk()
适合训练集#
model.fit(X_train)
print(model.weights_)
在测试集上进行预测#
portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)
print(portfolio.summary())
最大Sortino比率#
model = MeanRisk(
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
risk_measure=RiskMeasure.SEMI_VARIANCE,
)
去噪协方差与收缩预期收益#
model = MeanRisk(
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
prior_estimator=EmpiricalPrior(
mu_estimator=ShrunkMu(), covariance_estimator=DenoiseCovariance()
),
)
预期收益的不确定性集#
model = MeanRisk(
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
mu_uncertainty_set_estimator=BootstrapMuUncertaintySet(),
)
权重约束与交易成本#
model = 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)
风险平衡在条件风险价值(CVaR)上#
model = RiskBudgeting(risk_measure=RiskMeasure.CVAR)
风险平价与格伯协方差#
model = RiskBudgeting(
prior_estimator=EmpiricalPrior(covariance_estimator=GerberCovariance())
)
使用交叉验证和并行化的嵌套集群优化#
model = NestedClustersOptimization(
inner_estimator=MeanRisk(risk_measure=RiskMeasure.CVAR),
outer_estimator=RiskBudgeting(risk_measure=RiskMeasure.VARIANCE),
cv=KFold(),
n_jobs=-1,
)
L2范数的随机搜索#
randomized_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_)
嵌入参数的网格搜索#
model = 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_)
布莱克-利特曼模型#
views = ["AAPL - BBY == 0.03 ", "CVX - KO == 0.04", "MSFT == 0.06 "]
model = MeanRisk(
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
prior_estimator=BlackLitterman(views=views),
)
因子模型#
factor_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())
因子模型与协方差去噪#
model = MeanRisk(
prior_estimator=FactorModel(
factor_prior_estimator=EmpiricalPrior(covariance_estimator=DetoneCovariance())
)
)
黑色与利特曼因子模型#
factor_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),
),
)
预选择管道#
set_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折交叉验证#
model = 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()
组合性清除交叉验证#
model = 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())
识别#
我们要感谢所有对我们的直接依赖做出贡献的人,比如 scikit-learn 和 cvxpy,以及以下资源的贡献者,这些资源为我们提供了灵感:
* PyPortfolioOpt
* Riskfolio-Lib
* scikit-portfolio
* microprediction
* statsmodels
* rsome
* gautier.marti.ai
引用#
如果您在科学出版物中使用 skfolio
,我们将非常感谢您的引用:
Bibtex 条目:
@misc{skfolio,
author = {Hugo Delatte, Carlo Nicolini},
title = {skfolio},
year = {2023},
url = {https://github.com/skfolio/skfolio}