优化#

优化模块实现了一套用于投资组合优化的方法。 它们遵循与scikit-learn的 estimator 相同的API: fit 方法接受X 作为资产收益,并将投资组合权重存储在其 weights_ 属性中。

X 可以是任何类数组结构(numpy 数组、pandas DataFrame 等)。

朴素分配#

单纯模块实现了一组常用的单纯分配,通常用作比较不同模型的基准:

示例:

简单的逆波动率分配:

from sklearn.model_selection import train_test_split

from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import InverseVolatility
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

model = InverseVolatility()
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)

均值-风险优化#

MeanRisk 估计量可以解决以下 4 个目标函数:

  • 降低风险:

\[\begin{split}\begin{cases} \begin{aligned} &\min_{w} & & risk_{i}(w) \\ &\text{s.t.} & & w^T\mu \ge min\_return \\ & & & A w \ge b \\ & & & risk_{j}(w) \le max\_risk_{j} \quad \forall \; j \ne i \end{aligned} \end{cases}\end{split}\]
  • 最大化预期收益:

\[\begin{split}\begin{cases} \begin{aligned} &\max_{w} & & w^T\mu \\ &\text{s.t.} & & risk_{i}(w) \le max\_risk_{i} \\ & & & A w \ge b \\ & & & risk_{j}(w) \le max\_risk_{j} \quad \forall \; j \ne i \end{aligned} \end{cases}\end{split}\]
  • 最大化效用:

\[\begin{split}\begin{cases} \begin{aligned} &\max_{w} & & w^T\mu - \lambda \times risk_{i}(w)\\ &\text{s.t.} & & risk_{i}(w) \le max\_risk_{i} \\ & & & w^T\mu \ge min\_return \\ & & & A w \ge b \\ & & & risk_{j}(w) \le max\_risk_{j} \quad \forall \; j \ne i \end{aligned} \end{cases}\end{split}\]
  • 最大化比率:

\[\begin{split}\begin{cases} \begin{aligned} &\max_{w} & & \frac{w^T\mu - r_{f}}{risk_{i}(w)}\\ &\text{s.t.} & & risk_{i}(w) \le max\_risk_{i} \\ & & & w^T\mu \ge min\_return \\ & & & A w \ge b \\ & & & risk_{j}(w) \le max\_risk_{j} \quad \forall \; j \ne i \end{aligned} \end{cases}\end{split}\]

其中\(risk_{i}\) 是一种风险度量:

  • 方差

  • 半方差

  • 标准差

  • 半偏差

  • 平均绝对偏差

  • 首个下部分矩

  • 条件价值风险 (Conditional Value at Risk)

  • EVaR (熵值风险)

  • 最糟糕的实现(最糟糕的回报)

  • 条件风险下的减值 (CDaR)

  • 最大回撤

  • 平均回撤

  • EDaR (风险下跌的熵)

  • 溃疡指数

  • 基尼均值差异

它支持以下参数:

  • 重量约束

  • 预算约束

  • 组约束

  • 交易成本

  • 管理费用

  • L1 和 L2 正则化

  • 营业额约束

  • 跟踪误差约束

  • 预期收益的不确定性集

  • 协方差的不确定性集合

  • 预期回报约束

  • 风险度量约束

  • 自定义目标

  • 自定义约束

  • 先验估计器

示例:

最大夏普比率投资组合:

from sklearn.model_selection import train_test_split

from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns

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(
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    risk_measure=RiskMeasure.VARIANCE,
)
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.sharpe_ratio)

之前的估计器#

每个投资组合优化都有一个名为 prior_estimator 的参数。 先验估计器 拟合一个 PriorModel,其中包含 对资产预期收益、协方差矩阵、收益和协方差的Cholesky分解的估计。它代表了投资者对 用于估计这种分布的模型的先验信念。

可用的先验估计量有:

示例:

使用因子模型的最小方差投资组合:

from sklearn.model_selection import train_test_split

from skfolio.datasets import load_factors_dataset, load_sp500_dataset
from skfolio.optimization import MeanRisk
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import FactorModel

prices = load_sp500_dataset()
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.annualized_sharpe_ratio)

组合先验估计器#

可以将先前的估计量组合在一起,从而设计复杂的模型:

示例:

这个例子是 故意复杂 的,以展示如何将多个估计器结合在一起。

下面的模型是一个使用因子模型的最大夏普比率优化,用于估计资产的预期收益和协方差矩阵。使用Black & Litterman模型来估计因子的预期收益和协方差矩阵,结合了分析师对因子的观点。最后,Black & Litterman先验的预期收益是使用风险厌恶系数为2的等权重市场均衡和去噪的先验协方差矩阵进行估计的:

from sklearn.model_selection import train_test_split

from skfolio.datasets import load_factors_dataset, load_sp500_dataset
from skfolio.moments import DenoiseCovariance, EquilibriumMu
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import BlackLitterman, EmpiricalPrior, FactorModel

prices = load_sp500_dataset()
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)

factor_views = ["MTUM - QUAL == 0.0003 ",
                "SIZE - USMV == 0.0004",
                "VLUE == 0.0006"]

model = MeanRisk(
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    prior_estimator=FactorModel(
        factor_prior_estimator=BlackLitterman(
            prior_estimator=EmpiricalPrior(
                mu_estimator=EquilibriumMu(risk_aversion=2),
                covariance_estimator=DenoiseCovariance()
            ),
            views=factor_views)
    )
)

model.fit(X_train, y_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)

自定义估计器#

使用自定义实现来进行矩估计是非常常见的。例如,您可能希望使用内部的协方差估计或者期望收益的预测模型。

以下是如何实现自定义协方差估计器的简单示例。对于更复杂的情况和估计器,请查看API 参考

import numpy as np

from skfolio.datasets import load_sp500_dataset
from skfolio.moments import BaseCovariance
from skfolio.optimization import MeanRisk
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import EmpiricalPrior

prices = load_sp500_dataset()
X = prices_to_returns(prices)


class MyCustomCovariance(BaseCovariance):
    def __init__(self, my_param=0):
        super().__init__()
        self.my_param = my_param

    def fit(self, X, y=None):
        X = self._validate_data(X)
        # Your custom implementation goes here
        covariance = np.cov(X.T, ddof=self.my_param)
        self._set_covariance(covariance)
        return self


model = MeanRisk(
    prior_estimator=EmpiricalPrior(covariance_estimator=MyCustomCovariance(my_param=1)),
)
model.fit(X)

最坏情况优化#

使用mu_uncertainty_set_estimator参数,资产的预期回报通过椭球形不确定集合进行建模。该方法称为最坏情况优化,属于稳健优化的一类。它减轻了由于预期回报估计误差引起的不稳定性。

示例:

最坏情况下的最大均值/CDaR比率(风险条件下的回撤)与资产预期收益的椭球形不确定性集:

from sklearn.model_selection import train_test_split

from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns
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(
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    risk_measure=RiskMeasure.CDAR,
    mu_uncertainty_set_estimator=BootstrapMuUncertaintySet(confidence_level=0.9),
)
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)
print(portfolio.cdar_ratio)

进一步探索#

您可以探索其余参数(约束,L1和L2正则化,成本,周转率,跟踪误差等),通过均值-风险示例MeanRisk API。

风险预算#

RiskBudgeting 解决以下凸问题:

\[\begin{split}\begin{cases} \begin{aligned} &\min_{w} & & risk_{i}(w) \\ &\text{s.t.} & & b^T log(w) \ge c \\ & & & w^T\mu \ge min\_return \\ & & & A w \ge b \\ & & & w \ge0 \end{aligned} \end{cases}\end{split}\]

其中 \(b\) 是风险预算向量,\(c\) 是对数障碍的辅助变量。

并且 \(risk_{i}\) 是以下的风险度量:

  • 方差

  • 半方差

  • 标准差

  • 半偏差

  • 平均绝对偏差

  • 首个下部分矩

  • 条件价值风险 (Conditional Value at Risk)

  • EVaR (熵值风险)

  • 最糟糕的实现(最糟糕的回报)

  • 条件风险下的减值 (CDaR)

  • 最大回撤

  • 平均回撤

  • EDaR (风险下跌的熵)

  • 溃疡指数

  • 基尼均值差异

  • 首个下部分矩

它支持以下参数:

  • 重量约束

  • 预算约束

  • 组约束

  • 交易成本

  • 管理费用

  • 预期回报约束

  • 自定义目标

  • 自定义约束

  • 先验估计器

对某些约束施加限制,例如仅限于长头寸的权重,以确保问题保持凸性。

示例:

条件价值风险 (CVaR) 风险均衡投资组合:

from sklearn.model_selection import train_test_split

from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import RiskBudgeting
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

model = RiskBudgeting(risk_measure=RiskMeasure.CVAR)
model.fit(X_train)
print(model.weights_)

portfolio_train = model.predict(X_train)
print(portfolio_train.annualized_sharpe_ratio)
print(portfolio_train.contribution(measure=RiskMeasure.CVAR))

portfolio_test = model.predict(X_test)
print(portfolio_test.annualized_sharpe_ratio)
print(portfolio_test.contribution(measure=RiskMeasure.CVAR))

最大化多样性#

MaximumDiversification 最大化多样化比率, 该比率是加权波动率与总波动率的比例。

示例:

from sklearn.model_selection import train_test_split

from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MaximumDiversification
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

model = MaximumDiversification()
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.diversification)

分布式稳健条件在险价值#

可分布鲁棒CVaR构造了一个在多变量和非离散概率分布空间中的Wasserstein球,该球以训练样本上的均匀分布为中心,并找到最小化此Wasserstein球内最坏情况下分布的CVaR的分配。Esfahani和Kuhn证明,对于分段线性目标函数(即CVaR的情况),Wasserstein球上的可分布鲁棒优化问题可以重构为有限凸规划。

一个像 Mosek 这样的求解器,能够处理大量约束是更受欢迎的。

示例:

from sklearn.model_selection import train_test_split

from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import DistributionallyRobustCVaR
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X = X["2020":]
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

model = DistributionallyRobustCVaR(wasserstein_ball_radius=0.01)
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.cvar)

层次风险平价#

HierarchicalRiskParity (HRP) 是一种由马尔科斯·洛佩斯·德·普拉多开发的投资组合优化方法。

该算法使用距离矩阵来计算层次聚类,采用层次树聚类算法,然后利用排序重新排列树状图中的资产,最小化叶子之间的距离。

最后一步是递归二分法,其中每个集群在两个子集群之间被拆分,起始于最上面的集群并以自上而下的方式遍历。对于每个子集群,我们计算逆风险分配的总集群风险。然后从这两个子集群风险中计算出一个权重因子,该因子用于更新集群的权重。

注意

原始论文使用方差作为风险度量,并采用单链接法进行层次树聚类算法。在这里,我们将其推广到多种风险度量和链接方法。 默认的链接方法设定为沃德方差最小化算法,这比单链接法更稳定,具有更好的属性。

它支持所有 先前的估计量风险度量 以及权重约束。

它还通过距离估计器支持所有distance_estimator参数。它为共同依赖性和用于计算链接矩阵的距离矩阵拟合了一个距离模型:

示例:

层次风险平价,使用半(下行)标准差作为风险度量,互信息作为距离估计器:

from sklearn.model_selection import train_test_split

from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.distance import MutualInformation
from skfolio.optimization import HierarchicalRiskParity
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

model = HierarchicalRiskParity(
    risk_measure=RiskMeasure.SEMI_DEVIATION, distance_estimator=MutualInformation()
)
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)
print(portfolio.contribution(measure=RiskMeasure.SEMI_DEVIATION))

层次性平等风险贡献#

HierarchicalEqualRiskContribution (HERC) 是由 Thomas Raffinot 开发的投资组合优化方法。

该算法使用距离矩阵来计算层次聚类,使用层次树聚类算法。然后,它为每个簇计算逆风险分配的总簇风险。

最后一步是对树状图进行自顶向下的递归划分,其中在簇内使用简单风险平价更新资产权重。

它通过利用树状图形状在自顶向下的递归划分过程中,而不是将其对半切分,从而与层级风险平价有所不同。

注意

默认的连接方法设置为沃德方差最小化算法,该算法比单链接方法更加稳定,并具有更好的特性。

它支持所有 先前的估计量风险度量 以及权重约束。

它还支持所有 距离估计器 通过 distance_estimator 参数。它为代码依赖性估计和用于计算连接矩阵的距离矩阵拟合距离模型:

示例:

层次等风险贡献,使用CVaR(条件风险值)作为风险度量,使用互信息作为距离估计器:

from sklearn.model_selection import train_test_split

from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.distance import MutualInformation
from skfolio.optimization import HierarchicalEqualRiskContribution
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

model = HierarchicalEqualRiskContribution(
    risk_measure=RiskMeasure.CVAR,
    distance_estimator = MutualInformation()
)
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)
print(portfolio.contribution(measure=RiskMeasure.CVAR))

嵌套集群优化#

NestedClustersOptimization》(NCO)是一种由Marcos Lopez de Prado开发的投资组合优化方法。

它使用距离矩阵通过聚类算法(层次树聚类、KMeans等)计算聚类。对于每个聚类,内部聚类权重是通过使用整个训练数据在每个聚类上拟合内部估计器计算的。然后通过使用内部估计器的交叉验证的样本外估计训练外部估计器来计算外部聚类权重。最后,最终的资产权重是内部权重和外部权重的点积。

注意

原始论文使用 KMeans 作为聚类算法,最小方差作为内部估计器,外部估计器则为等权重。这里我们将其推广至所有 sklearnskfolio 聚类算法(层次树聚类、KMeans 等)、所有投资组合优化(均值-方差、HRP 等)以及风险度量(方差、CVaR 等)。为了避免外部估计器的数据泄漏,我们使用样本外估计来拟合外部估计器。

它支持所有 距离估计器聚类估计器(包括 skfoliosklearn

示例:

使用KMeans作为聚类算法、Kendall距离作为距离估计器、最小半方差作为内部估计器,以及CVaR风险平价作为外部(元)估计器,在KFolds交叉验证的样本外估计值上进行训练,并使用并行化运行的嵌套聚类优化:

from sklearn.cluster import KMeans
from sklearn.model_selection import KFold, train_test_split

from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.distance import KendallDistance
from skfolio.optimization import MeanRisk, NestedClustersOptimization, RiskBudgeting
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

model = NestedClustersOptimization(
    inner_estimator=MeanRisk(risk_measure=RiskMeasure.SEMI_VARIANCE),
    outer_estimator=RiskBudgeting(risk_measure=RiskMeasure.CVAR),
    distance_estimator=KendallDistance(),
    clustering_estimator=KMeans(n_init="auto"),
    cv=KFold(),
    n_jobs=-1,
)
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)
print(portfolio.contribution(measure=RiskMeasure.CVAR))

参数 cv 也可以是组合交叉验证,例如 CombinatorialPurgedCV,在这种情况下,每个集群的样本外输出是多个路径的集合,而不是单一路径。在这个路径集合中选择的样本外路径是根据 quantilequantile_measure 参数选择的。

堆叠优化#

StackingOptimization 是一种集成方法,它将各个投资组合优化的结果进行堆叠,并应用最终的投资组合优化。

权重是单个优化权重与最终优化权重的点积。

堆叠允许通过将每个单独投资组合优化的结果作为最终投资组合优化的输入,来利用它们的优势。

为避免数据泄露,使用样本外估计来拟合外部优化。

示例:

使用最小半方差和CVaR风险平价进行堆叠优化 使用最小方差作为最终的(元)估计量进行堆叠。

from sklearn.model_selection import KFold, train_test_split

from skfolio import RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk, RiskBudgeting, StackingOptimization
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

estimators = [
    ('model1', MeanRisk(risk_measure=RiskMeasure.SEMI_VARIANCE)),
    ('model2', RiskBudgeting(risk_measure=RiskMeasure.CVAR))
]

model = StackingOptimization(
    estimators=estimators,
    final_estimator=MeanRisk(),
    cv=KFold(),
    n_jobs=-1
)
model.fit(X_train)
print(model.weights_)

portfolio = model.predict(X_test)
print(portfolio.annualized_sharpe_ratio)

参数 cv 也可以是组合交叉验证,例如 CombinatorialPurgedCV,在这种情况下,每个样本外输出都是多条路径的集合,而不是单条路径。在这些路径的集合中选择的样本外路径是根据 quantilequantile_measure 参数选择的。