skfolio#

skfolio 是一个构建在 scikit-learn 之上的 Python 投资组合优化库。 它提供了一个统一的接口和与 scikit-learn 兼容的工具,用于构建、微调和交叉验证投资组合模型。

它在开源的三条款BSD许可证下分发。

examples

安装#

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}