后期处理

在生成最佳权重后,往往需要做一些后期处理,才能实际使用。 特别是,你可能正在使用投资组合优化技术来生成一个投资组合分配,一个可以去经纪人那里购买的股票和相应的整数数量的列表。

然而,将连续权重(由我们的任何优化方法输出)转换为可操作的分配并不简单。 例如,让我们说我们有 $10,000 美元,我们想进行分配。 如果我们将权重乘以这个总的投资组合价值,结果将是每个资产的美元数额。 因此,如果苹果的最佳权重是 0.15,我们需要价值 $1500 美元的苹果股票。 然而,苹果公司的股票是不连续的单位(在撰写本文时为 $190 美元),所以我们将无法准确购买 $1500 美元的股票。 我们能做的最好的事情就是买入让我们最接近所需美元价值的股票数量。

PyPortfolioOpt 提供了两种解决这个问题的方法:一种是使用简单的贪婪算法,另一种是使用整数规划。

贪婪算法

DiscreteAllocation.greedy_portfolio() 分两轮 进行。 在第一轮中,我们尽可能多地买入每种资产的股票,而不超过所需的权重。 在苹果的例子中, \(1500/190 \approx 7.89\) ,所以我们以 $1330 美元的成本买入 7 股。 迭代完所有的资产后,我们会有很多剩余的钱(因为我们总是向下取舍)。

在第二轮中,我们计算当前的权重与每个资产的现有权重有多大的偏差。 我们希望苹果占投资组合的 15%(总价值 $10,000 美元),但我们只买了价值 $1330 美元的苹果股票,所以有一个偏差为 \(0.15 - 0.133\) 。 有些资产与理想值的偏差会更大,所以我们会先购买这些资产的股票。 然后我们重复这个过程,总是购买当前权重与理想权重相差最远的资产的股票。 虽然这种算法不会保证最优解,但我发现,它可以让我们产生离散的分配,而且剩下的钱很少(例如, $10,000 美元的投资组合剩下 $12 美元)。

也就是说,我们可以看到,在测试数据集上(对于一个标准的 max_sharpe 投资组合),分配方法可能与理想的权重有相当大的偏差,特别是对于股价高的公司(如 AMZN)。

Funds remaining: 12.15
MA: allocated 0.242, desired 0.246
FB: allocated 0.200, desired 0.199
PFE: allocated 0.183, desired 0.184
BABA: allocated 0.088, desired 0.096
AAPL: allocated 0.086, desired 0.092
AMZN: allocated 0.000, desired 0.072
BBY: allocated 0.064, desired 0.061
SBUX: allocated 0.036, desired 0.038
GOOG: allocated 0.102, desired 0.013
Allocation has RMSE: 0.038

整数规划

这种方法(归功于 Dingyuan Wang 的首次实现)将离散分配视为一个整数规划问题。 实际上,整数规划方法搜索可能的分配空间,以找到最接近我们所需权重的一个。我们将使用以下符号:

  • \(T \in \mathbb{R}\) 是要分配的美元总价值

  • \(p \in \mathbb{R}^n\) 是最新价格数组

  • \(w \in \mathbb{R}^n\) 是目标权重的集合

  • \(x \in \mathbb{Z}^n\) 是整数分配(即结果)

  • \(r \in \mathbb{R}\) 是剩余的未分配的价值,即 \(r = T - x \cdot p\)

然后,优化问题由以下公式给出:

\[\begin{split}\begin{equation*} \begin{aligned} & \underset{x \in \mathbb{Z}^n}{\text{minimise}} & & r + \lVert wT - x \odot p \rVert_1 \\ & \text{subject to} & & r + x \cdot p = T\\ \end{aligned} \end{equation*}\end{split}\]

这可以直接转化为成 cvxpy 优化。

备注

虽然 lp_portfolio() 产生的分配的RMSE较低,但一些测试表明,它比 greedy_portfolio() 慢100至1000倍。 这对于小的投资组合来说可能影响不大(应该仍然需要不到一秒钟的时间),但是整数规划的运行时间会随着股票数量的增加而呈指数级增长, 所以对于大的投资组合,你可能不得不使用 greedy_portfolio()

警告

PyPortfolioOpt 使用 ECOS_BB 作为整数规划的默认求解器。 ECOS_BB 有已知的正确性问题(见 这里 的讨论)。 另一个选择是使用 GLPK_MI ,它与 cvxopt 打包在一起。

处理空头

从 v0.4 开始, DiscreteAllocation 自动处理空头,为只做多的部分和只做空的部分找到单独的离散配置。 如果你的投资组合有空头,你应该传递一个空头比率。 默认是 0.30,对应于 130/30 的多空平衡。 实际上,这意味着你将做多一些股票的 $10000 美元,做空一些其他股票的 $3000 美元,然后用做空的收益再做多 $3000 美元。 因此,所形成的投资组合的总价值将是 $13,000 美元。

文档参考

discrete_allocation 模块包含 DiscreteAllocation 类,它提供了多种方法来从连续权重生成离散的投资组合分配。

class pypfopt.discrete_allocation.DiscreteAllocation(weights, latest_prices, total_portfolio_value=10000, short_ratio=None)[源代码]

从连续权重生成离散的投资组合配置

可选:

  • Inputs:

    • weights - 字典

    • latest_prices - pd.Series 或字典

    • total_portfolio_value - int 或 float

    • short_ratio- float

  • Output: allocation - dict

公共方法:

  • greedy_portfolio() - 使用贪婪算法

  • lp_portfolio() - 使用线性规划

__init__(weights, latest_prices, total_portfolio_value=10000, short_ratio=None)[源代码]
参数:
  • weights (dict) – 从 efficient_frontier 模块生成的连续权重

  • latest_prices (pd.Series) – 每项资产的最新价格

  • total_portfolio_value (int/float, optional) – 投资组合的预期总价值,默认为 10000

  • short_ratio (float, defaults to None.) – 做空比率,例如 0.3 对应于 130/30。如果 None,则默认为输入权重。

抛出:
  • TypeError – 如果 weights 非字典

  • TypeError – 如果 latest_prices 非 series

  • ValueError – 如果 short_ratio < 0

_allocation_rmse_error(verbose=True)[源代码]

计算和打印离散权重和连续权重之间的 RMSE 误差的实用函数。 使用 RMSE 而不是 MAE 是因为我们想对大的变化进行惩罚。

参数:

verbose (bool) – 是否输出权重差异

返回:

rmse 误差

返回类型:

float

static _remove_zero_positions(allocation)[源代码]

移除零仓位(即没有买入股票)的效用函数

greedy_portfolio(reinvest=False, verbose=False)[源代码]

使用贪婪的迭代方法将连续权重转换成离散的投资组合分配。

参数:
  • reinvest (bool, 默认为 False) – 是否对做空获得的现金进行再投资

  • verbose (bool, 默认为 False) – 是否详细输出

返回:

应该购买的每个股票的数量,以及剩余的资金数额。

返回类型:

(dict, float)

lp_portfolio(reinvest=False, verbose=False, solver='ECOS_BB')[源代码]

使用整数规划将连续权重转换为离散的投资组合分配。

参数:
  • reinvest (bool, 默认为 False) – 是否对做空获得的现金进行再投资

  • verbose (bool) – 是否详细输出

  • solver (str, 默认为 "ECOS_BB") – 使用的 CVXPY 求解器(必须支持混合整数规划)

返回:

应该购买的每个股票的数量,以及剩余的资金数额。

返回类型:

(dict, float)