后期处理
在生成最佳权重后,往往需要做一些后期处理,才能实际使用。 特别是,你可能正在使用投资组合优化技术来生成一个投资组合分配,一个可以去经纪人那里购买的股票和相应的整数数量的列表。
然而,将连续权重(由我们的任何优化方法输出)转换为可操作的分配并不简单。 例如,让我们说我们有 $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\) 。
然后,优化问题由以下公式给出:
这可以直接转化为成 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 或 floatshort_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
非 seriesValueError – 如果
short_ratio < 0
- _allocation_rmse_error(verbose=True)[源代码]
计算和打印离散权重和连续权重之间的 RMSE 误差的实用函数。 使用 RMSE 而不是 MAE 是因为我们想对大的变化进行惩罚。
- 参数:
verbose (bool) – 是否输出权重差异
- 返回:
rmse 误差
- 返回类型:
float