用Python和Tensorflow进行加密投资组合优化-矩阵微积分方法2
如果您没有阅读上一篇文章(使用Python和Tensorflow进行加密投资组合优化-矩阵微积分方法),我强烈建议你先阅读。
作为回顾,我们使用Python和Tensorflow构建了一个投资组合优化器,给定一组加密货币,它获取历史价格,计算方差 - 协方差矩阵,相关矩阵,然后探索资产组合,以便最大化全局夏普比率。
今天,我们将探索一些我们可以遵循的方法来改进我们的工具并使其更加一致。
更多数据
在上篇文章中,我们解释了找到k个变量方程的绝对最小值的问题是NP问题,其呈指数增长并且可能需要数千和数千个世纪来计算(通常)。但是,机器学习允许我们在非常合理的时间内找到最佳解决方案的良好近似值。
因此,如果加密资产的数量不是我们的约束,我们首先要做的是为我们的投资组合添加更多资产。
更广泛的资产倾向于最小化风险,但它也可能抑制我们的预期回报。但是,我们的优化器可以帮助我们找到可以提高上一次夏普比率的平衡点。
在我们之前的代码之上运行具有7个资产的AdamOptimizer的5000次迭代需要4.1几秒钟才能完成。加起来最多23后,优化器只需要4.58!
所以,让我们肯定使用更大的集合并继续。
替代优化器
在第一部分中,我们的优化器快速地融合到一个6x全局返回场景中,并且再也没有离开过这个位置。换句话说,它“学得太快”。
在探索搜索空间时,当fast-learning优化器找到合理的好方法时,它们往往会丢弃新的分支。 Slow-learning有机会评估最初可能被撤销的方法,但最终会产生更好的结果。
为了检查我们的变化是否有所改善,我们需要进行比较。为了进行比较,视觉指标对我们有很大帮助。
让我们定义几个助手来描绘我们训练的演变:
#PLOTTING
import matplotlib.pyplot as plt
def line_plot(line1, label1=None, units='', title=''):
fig, ax = plt.subplots(1, figsize=(16, 9))
ax.plot(line1, label=label1, linewidth=2)
ax.set_ylabel(units, fontsize=14)
ax.set_title(title, fontsize=18)
ax.legend(loc='best', fontsize=18)
def lines_plot(line1, line2, label1=None, label2=None, units='', title=''):
fig, ax = plt.subplots(1, figsize=(16, 9))
ax.plot(line1, label=label1, linewidth=2)
ax.plot(line2, label=label2, linewidth=2)
ax.set_ylabel(units, fontsize=14)
ax.set_title(title, fontsize=18)
ax.legend(loc='best', fontsize=18)
让我们从AdagradOptimizer开始,学习率为0.0005和10 000次迭代:
AdagradOptimizer,学习率为0.0005,进行10 000次迭代
到目前为止,我们正在将夏普比率提高到27,但如果我们允许更高的学习率或更多的迭代,那么夏普比率的值可能会继续增加。
在第一种情况下它会如何表现?
AdagradOptimizer,学习率为0.01,进行10 000次迭代
看起来真好!我们的优化器几乎可以立即达到9.3倍的回报率和36.5的夏普比率!让我们绝对保持这种组合:
在撰写本文时,它基本上告诉我们投资EOS,Stellar Lumens,The Verge和Binance Coin,尽管它仍然学得如此之快。
注意:随着时间的推移,市场状况会发生变化,因此会产生其他结果。
我们继续讲更多的算法。让我们尝试AdamOptimizer:
optimize_op = tf.train.AdamOptimizer(learning_rate=0.0005, beta1=0.9, beta2=0.999, epsilon=1e-08, use_locking=False)...
AdamOptimizer,学习率为0.0005,进行10 000次迭代
一开始稍微好一些,但之后基本上是一样的。在最初的高点之后,夏普比率略有下降,可能我们过度训练了。不管怎样,让我们用学习率来做实验:
AdamOptimizer,10 000次迭代的学习率为0.05
显然如此之快。一个更快的学习率在一开始就发现了一个局部的高点,停滞不前并且拒绝探索新的变体。因此,产出结果更糟。还需要注意的是,夏普比率在很大的区间内保持振荡。
让我们以一个更慢的速度来尝试:
AdamOptimizer,学习率为0.00005,进行10 000次迭代
没什么新鲜的。让我们看看我们可以用另一种优化算法做些什么。
GradientDescentOptimizer的时间:
GradientDescentOptimizer,学习率为0.0005,可进行10 000次迭代
比目前为止我们最好的方法好10%。正如我们所看到的那样,它如此迅速地达到顶峰,所以也许我们应该调整学习率,看看速度慢的是否为新方法打开了大门:
GradientDescentOptimizer,学习率为0.000005,进行10 000次迭
我们取得了同样的成绩,只是进步了一些。
到目前为止,你已经明白了。您可以尝试任何可用的优化器,看看哪些优化器可以很好地解决您的问题,并根据您的优势调整学习速度和所有特定的参数。
约束
在第一篇文章中,我们定义了一组三个约束,使负值变为0,大于1的值变为1,其权重之和为1。
然而,在前两个约束中,我们基本上是在裁剪优化器正在探索的平衡。如果两个coin的权重在给定的点是1.2和1.5,那么我们的约束将把它们都设为1,并可能减少一个好的搜索空间。为了避免掺假权重,我们可以使用绝对值除以权重和的绝对值:
weights_sum = tf.reduce_sum(coin_weights)
constraints_op = coin_weights.assign(tf.divide(tf.abs(coin_weights), tf.abs(weights_sum) ))
但现在又出现了一个新问题:如果我们可以卖空加密资产,为什么不允许出现负值呢?
投资组合优化通常适用于中期投资,在中期投资中你要做多。做空通常意味着支付经纪人费用,因为我们正在出售一些我们还不拥有的东西。
在短时间内看空市场,做空可能相当有利可图,但要记住,长时间做空可能比你最终赚到的钱还要多。
卖空
出于教育的目的,我们将探讨空头头寸的情况。我们现在需要的唯一约束是权值的绝对值之和总是1。
weights_sum = tf.reduce_sum(tf.abs(coin_weights))
constraints_op = coin_weights.assign(tf.divide(coin_weights, tf.abs(weights_sum) ))
结果是,我们实现了一半的回报,一半的波动性,夏普比率保持不变。
但是正如我们前面所说的,这个方法不是针对这类问题的,所以您可能应该保留确保coin权重在0到1之间的约束。
选择最小化
如果您还记得上一篇文章中的内容,那么可以使用的优化器没有提供最大化输出值的函数。相反,我们选择最小化夏普比率的负值。
这是,而不是试图最小化价值+ 0,诀窍是瞄准-∞。但是,如果我们试图降低绝对值(模拟误差函数)会发生什么情况?
在学习速度为0.0001时,按梯度下降优化程序进行10000次迭代(-sharpe_ratio):
GradientDescentOptimizer,学习率为0.0001,进行10 000次迭代
让我们看看当我们尝试最小化时会发生什么,1 / sharpe_ratio而不是-sharpe_ratio :
GradientDescentOptimizer,学习率0.0001,10 000次迭代
乍一看似乎夏普比率不太好,但图形告诉我们输出还没有稳定。如果我们允许更快的学习率怎么办?约0.002:
我们得到的结果和改变前差不多,但真的没有什么区别吗?如果我们画出两种情况的饼图呢?
def pie_chart(values, padding = 0.05, labels=None, title=''):
fig, ax = plt.subplots(1, figsize=(12, 12))
ax.pie(values, np.full(len(coins), padding), labels)
ax.set_title(title, fontsize=18)
Coin weights obtained minimizing by negative Sharpe Ratio and inverse Sharpe Ratio:
也许差异不大,但仍值得一提。如您所见, - Sharpe ratio chart features仅包含三个资产。然而,1 /Sharpe ratio chart features具有5-6个资产,同时保持相等的夏普比率(39.18vs 38.99)。
在投资方面,多样化是一个关键的规则。当我们解释这两个投资组合时,要知道两者具有相同的表现,我们绝对应该选择最多样化的投资组合。
随意进行自己的测试,并选择最适合您的方法。在这种情况下,我们将继续通过夏普比率的倒数最小化,但请记住,这种变化不一定要找到更多样化的投资组合。
每日回报模拟
比较和评估投资组合的一个好方法是模拟他们的行为。具有一致积极结果的投资组合将比另一个具有持续下降和零星绿色峰值的投资组合更有趣。
让我们将Python代码重构为可重用的函数,以便我们可以生成许多场景来进行模拟和比较。
def get_portfolio_volatility(coin_weights):
weighted_std_devs = tf.multiply(coin_weights, std_deviations)
product_1 = tf.transpose(weighted_std_devs)
product_2 = tf.matmul(product_1, correlation_matrix)
variance = tf.matmul(product_2, weighted_std_devs)
volatility = tf.sqrt(variance)
return tf.reduce_sum(volatility)
def get_portfolio_return(coin_weights):
returns = np.full((len(coins), 1), 0.0) # same as coin_weights
for coin_idx in range(0, len(coins)):
returns[coin_idx] = cumulative_returns[coins[coin_idx]]
p_return = tf.multiply(coin_weights, returns)
return tf.reduce_sum(p_return)
def ensure_constraints_op(coin_weights):
# all values positive, with unity sum
weights_sum = tf.reduce_sum(coin_weights)
constraints_op = coin_weights.assign(tf.divide(tf.abs(coin_weights), tf.abs(weights_sum) ))
return constraints_op
def generate_max_sharpe_portfolio(learning_rate = 0.0001, steps = 2000, verbose = False):
coin_weights = tf.Variable(tf.random_uniform((len(coins), 1), dtype=tf.float64)) # our variables
portfolio_volatility = get_portfolio_volatility(coin_weights)
portfolio_return = get_portfolio_return(coin_weights)
sharpe_ratio = tf.divide(portfolio_return, portfolio_volatility)
constraints_op = ensure_constraints_op(coin_weights)
# Training using Gradient Descent to minimize cost
optimize_op = tf.train.GradientDescentOptimizer(learning_rate, use_locking=True).minimize(tf.divide(1.0, sharpe_ratio))
init_op = tf.global_variables_initializer()
with tf.Session() as sess:
ratios = np.zeros(steps)
returns = np.zeros(steps)
sess.run(init_op)
for i in range(steps):
sess.run(optimize_op)
sess.run(constraints_op)
ratios[i] = sess.run(sharpe_ratio)
returns[i] = sess.run(portfolio_return) * 100
if i % 500 == 0 and verbose :
sess.run(constraints_op)
print("[round {:d}]".format(i))
# print("Coin weights", sess.run(coin_weights))
print("Volatility", sess.run(portfolio_volatility))
print("Return {:.2f} %".format(sess.run(portfolio_return)*100))
print("Sharpe ratio", sess.run(sharpe_ratio))
print("")
sess.run(constraints_op)
if verbose :
line_plot(ratios, "Sharpe Ratio")
line_plot(returns, "Return")
# print("Coin weights", sess.run(coin_weights))
print("Volatility", sess.run(tf.reduce_sum(portfolio_volatility)))
print("Return {:.2f} %".format(sess.run(portfolio_return)*100))
print("Sharpe ratio", sess.run(tf.reduce_sum(sharpe_ratio)))
return sess.run(coin_weights), sess.run(sharpe_ratio), sess.run(portfolio_return), sess.run(portfolio_volatility)
# example invocation
weights1, sharpe1, return1, volatility1 = generate_max_sharpe_portfolio(learning_rate = 0.0015, steps = 10000, verbose = False)
weights2, sharpe2, return2, volatility2 = generate_max_sharpe_portfolio(learning_rate = 0.005, steps = 2000, verbose = False)
我们还需要一个函数,该函数给定一个Coin权重数组,生成一个表格,其中包含投资组合可能实现的每日回报率。Python代码如下:
def get_cumulative_ratios_by_coin(weights):
result = np.zeros((hist_length, len(coins)))
result[0] = np.full(len(coins), 1)
for i in range(1, hist_length):
for idx, coin in enumerate(coins):
result[i][idx] = result[i-1][idx] * (coin_history[coin].iloc[i]['return'] + 1)
return result
def get_global_daily_valuation(weights):
cumulative_ratios = get_cumulative_ratios_by_coin(weights)
daily_cumulative_ratios = np.zeros(hist_length)
for i in range(hist_length):
daily_cumulative_ratios[i] = np.matmul(cumulative_ratios[i], weights)[0]
return pd.DataFrame(daily_cumulative_ratios, columns = ["Cumulative return"], index = coin_history[coins[0]].index)
# Generate and plot
initial_capital = 1000
weights1, s1, r1, v1 = generate_min_sharpe_portfolio(learning_rate = 0.0015, steps = 20000)
weights2, s2, r2, v2 = generate_min_sharpe_portfolio(learning_rate = 0.005, steps = 2000)
returns1 = get_global_daily_valuation(weights1) * initial_capital
returns2 = get_global_daily_valuation(weights2) * initial_capital
lines_plot(returns1, returns2, "Scenario 1", "Scenario 2", "USD", "Historic valuation")
所以,现在我们就拿几个场景与不同学习速度和迭代数量比较。
几个注意事项:
- 训练较少的场景(2)拥有更多样化的投资组合,但无论如何与场景1相似
- 两种场景都有相同的模式。
- 情形1比情形1更善于选择上升的峰值
- 在2017年10月的初始投资1000美元,到1月底估值将达到3万美元左右。
- 不过,这两种情况都可能导致这种估值在短短两个月内跌至1万美元左右。
即使加密资产可能随着实力的不同而涨跌,我们也应该记住加密货币是一个整体。
如果我们观察同一时期的总市值的变化,我们会发现它几乎和上面的图表完全一样。
因此,我们投资组合的余额模仿了整个加密货币的信心程度,而不是代表明星coin。
然而,请注意,虽然投资组合实验估值可能为30倍,但当时全球市值仅增加了5.3倍。
这种结果非常不稳定且非常短期。工具主要针对中长期情景,基于趋势稳定性而非持续中断。
分析传统资产
这是最明显的事情,分析亚马逊,苹果,谷歌等的股票价格。您已经拥有了工具和流程。您可以随意从另一个API端点获取并自定义历史价格,以包含非加密投资组合的价格。
由于我们正在研究,我们可能会以非显而易见的方式继续,看看可以学到什么。
仅通过波动来优化
在上一篇文章的原型设计阶段,我们计算了波动率并用它测试了我们的第一个优化器。但是之后,我们将预期回报添加到等式中,因为最稳定的情况是仅投资稳定币。
但是,如果我们摆脱什么stablecoin,通过波动再次最小化,并强制优化coin的其余部分中进行选择?让我们生成两个新场景并进行比较:
所以这里是我们的方差最小化器生成的权重数组。无论产出波动如何,我们都看到自训练开始以来第一种情景的夏普比率一直在下降。到目前为止还不乐观。
但是,他们实际上是如何表现的呢?
根据图片,我们应该说“绿色场景(2500次迭代的0.005学习率)看起来比x, y, z更一致…”真的吗?
目前的优化器并没有被鼓励支持具有高预期回报的解决方案,而只是低波动性。预期回报是独立的,可以是高,低或其他任何东西。
如果我们生成两个场景,它们看起来将与上面的场景完全不同。如果我们等待6个月并再次评估绿色情景,我们应该看到低波动性......但也是一个完全随机的回报。
有效前沿
回到现代投资组合理论,我们还可以使用另一个可视化工具来分析一组资产的潜在投资组合。
当没有其他投资组合在相同风险水平下获得更高的预期收益时,投资组合被认为是“有效的”。这可能看起来和参考最高的夏普比率一样,但有一个区别:几个投资组合可以在更高或更低的回报率/风险下实现相同的夏普比率。
这就是说,我们有一个可以跨越有效投资组合的自由程度。让我们生成一套组合,并在分散图中展示它们,以更好地理解它,Python代码如下:
def generate_portfolios(count=30):
coins_data = {
"return": [],
"volatility": []
}
portfolios_data = {
#"weights": [], # uncomment if you want to retrieve the coin weights
"return": [],
"volatility": []
}
# An entry for every coin alone
for idx, coin in enumerate(coins):
coins_data["volatility"].append(std_deviations[idx][0])
coins_data["return"].append(cumulative_returns[coin])
# N portfolios with their iterations
for i in range(0, count):
print("Generating portfolio ", i + 1)
data = generate_portfolio_set(learning_rate = 0.01 * np.random.sample(), steps = int(15000 * np.random.sample()))
#portfolios_data["weights"].extend(data['weights'])
portfolios_data["return"].extend(data['return'])
portfolios_data["volatility"].extend(data['volatility'])
return coins_data, portfolios_data
def plot_efficient_frontier(coins_data, portfolios_data):
plt.scatter(x=coins_data["volatility"], y=coins_data["return"], s=80, c="r", alpha=0.5)
plt.scatter(x=portfolios_data["volatility"], y=portfolios_data["return"], s=30, c="b", alpha=0.002)
plt.xlabel("Std Deviation")
plt.ylabel("Return")
plt.show()
def generate_portfolio_set(learning_rate = 0.0001, steps = 6000, verbose = False):
result = {
"weights": [],
"return": [],
"volatility": []
}
coin_weights = tf.Variable(tf.random_uniform((len(coins), 1), dtype=tf.float64)) # our variables
portfolio_volatility = get_portfolio_volatility(coin_weights)
portfolio_return = get_portfolio_return(coin_weights)
sharpe_ratio = tf.divide(portfolio_return, portfolio_volatility)
inv_sharpe_ratio = tf.divide(portfolio_volatility, portfolio_return)
constraints_op = ensure_constraints_op(coin_weights)
# Training using Gradient Descent to minimize cost
optimize_op = tf.train.GradientDescentOptimizer(learning_rate, use_locking=True).minimize(inv_sharpe_ratio)
init_op = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init_op)
for i in range(steps):
sess.run(optimize_op)
sess.run(constraints_op)
result["return"].append(sess.run(portfolio_return))
result["volatility"].append(sess.run(portfolio_volatility))
result["weights"].append(sess.run(coin_weights))
if i % 500 == 0 and verbose :
sess.run(constraints_op)
print("[round {:d}]".format(i))
#print("Coin weights", sess.run(coin_weights))
print("Volatility", sess.run(portfolio_volatility))
print("Return {:.2f} %".format(sess.run(portfolio_return)*100))
print("Sharpe ratio", sess.run(sharpe_ratio))
print("")
sess.run(constraints_op)
if verbose :
line_plot(ratios, "Sharpe Ratio")
line_plot(returns, "Return")
# print("Coin weights", sess.run(coin_weights))
print("Volatility", sess.run(tf.reduce_sum(portfolio_volatility)))
print("Return {:.2f} %".format(sess.run(portfolio_return)*100))
print("Sharpe ratio", sess.run(tf.reduce_sum(sharpe_ratio)))
return result
coins_data, portfolios_data = generate_portfolios()
plot_efficient_frontier(coins_data, portfolios_data)
上面的Python代码生成一个散点图,如下所示。我们通过随机化一些参数来生成最佳投资组合。
以个人加密为基础的有效前沿和30个投资组合
有几点需要注意:
- 红点表示各个加密资产的回报/风险。
- 蓝点代表我们刚刚生成的投资组合的回报/风险,以及在训练期间发现的临时项目。注意它们留下的尾部,因为优化器可以提高性能。
- 最不稳定的资产位于右侧,而盈利能力最强的资产位于顶部。
- 蓝点往往形成双曲线,如下面的绿色和红色部分。
积极倾斜的绿色部分是有效前沿
向下倾斜的绿色部分是有效边界。
- 它的右上角将是回报率最高但只适用于愿意承担高风险的投资者的方案。最终应该避免只投资于顶部的coin,因为这将不利于最佳的多元化操作。
- 绿色部分的左下方是最安全的情况。预期收益逐渐降低,直到发现最小风险。
- 在这一点以下,即使预期回报继续下降,风险也不会再降低。这对应于红色部分,投资这些投资组合根本不会有趣。
正如你所看到的,多样化的投资组合比单独的资产表现更好,要么通过提供更高的回报,要么通过冒险。
下面是两个单独资产的示例。请注意,在每一种情况下,我们都可以通过一个低得多的风险投资组合获得相同的预期收益,在绿色箭头的末尾。此外,如果我们的投资组合位于黄色箭头顶部,我们可以获得更高的回报。
将个别资产与相同风险或回报的有效投资组合进行比较
通过一系列有效的投资组合,确定其中一个投资组合取决于您愿意接受的风险程度。
以上信息绝不是购买或出售的建议。投资加密货币意味着相当大的损失风险。做自己的研究,筛选出任何单点不一致的投资,不要盲目追随任何建议。