代码详解:基于均值-方差优化法的Python算法交易
全文共6852字,预计学习时长14分钟
图源:pixabay
本文旨在展示如何用马科维茨(Markowitz)的投资组合优化法和现代资产组合理论(MPT)来生成交易策略。
本文首先对均值-方差优化法进行简介及提示,然后介绍如何将其应用到交易策略中。如前所述,将使用zipline框架对它们进行回测。
设置
本文将使用以下数据库:
zipline 1.3.0
matplotlib 3.0.0
json 2.0.9
empyrical 0.5.0
numpy 1.14.6
pandas 0.22.0
pyfolio 0.9.2
均值-方差优化法的前身
1952年,哈里·马科维茨发表了“投资组合理论(Portfolio Selection)”,其中介绍了现名为现代资产组合理论(简称MPT)的投资理论。其要点有:
• 投资组合收益是各个投资组合构成部分的加权平均值,但波动性也受到资产间相关性的影响
• 投资者不应分开评估资产效益,而应了解它们将如何影响投资组合的效益
• 多样化(将总资产调配到多项资产上,而不是集中在一项或极少数的资产上)可以极大地降低投资组合的波动性
本文不深入MPT假设,但其要点是所有投资者都有一个共同的目标,即在尽可能避免风险的同时,实现投资回报最大化,他们可以以无风险利率(不受限制)借贷资金,并且不考虑交易成本。
在此基础上,均值-方差分析法是找到一个最优资产配置方案,它能最好权衡预期收益和风险(作为收益的方差)。与均值-方差分析法相关的一个关键概念是有效前沿(Efficient Frontier),它是一组为制定风险水平提供最高预期投资组合回报,或以不同的方式对其进行构建的最优投资组合,为预期投资组合回报提供最低风险水平。
有效前沿可视化 — 图源:维基百科
用数学公式表示如下:
其中w为权重向量,μ为资产收益向量,Σ为协方差矩阵,μ_p为目标预期投资组合收益。其中两个限制因素是:
• 禁止非负权重为0的卖空
• 权重总和必须达到1,禁止使用杠杆
为了解决这一问题并得到有效边界(Efficient frontier),可以界定一个预期投资组合收益的可能范围,然后为每个值找到使方差最小化的权重。幸运的是,有一个数据库能很大程度上简化这个过程。
用PyPortfolioOpt,只需几行代码就可以解决整个优化问题。本文中将创建这样的投资组合:要么最大化预期的夏普比率(Sharpe ratio,投资组合每单位风险的超额回报),要么最小化整体波动性。这两种投资组合都依赖于有效前沿。
下面的简短示例中,将介绍如何使用pypfopt。首先,使用yahoofinancials下载历史股价。
risky_assets = ['TSLA', 'MSFT', 'FB', 'TWTR']
yahoo_financials = YahooFinancials(risky_assets)
json_results = yahoo_financials.get_historical_price_data('2019-01-01', '2019-09-30', 'daily')
prices_list = []
for asset in risky_assets:
x = pd.DataFrame(json_results[asset]['prices'])[['formatted_date', 'adjclose']]
x.set_index('formatted_date', inplace=True)
x = x.adjclose
x.rename(asset)
prices_list.append(x)
prices_df = pd.concat(prices_list, axis=1)
prices_df.columns = risky_assets
prices_df.head()
pypfopt可以轻易地从价格中计算预期回报和协方差矩阵,无需事先转换为收益。
# calculate expected returns and sample covariance amtrix
avg_returns = expected_returns.mean_historical_return(prices_df)
cov_mat = risk_models.sample_cov(prices_df)
通过运行以下代码来获得使夏普比率最大化的权重:
# get weights maximizing the Sharpe ratio
ef = EfficientFrontier(avg_returns, cov_mat)
weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
cleaned_weights
得到以下权重:
{'FB': 0.03787, 'MSFT': 0.83889, 'TSLA': 0.0, 'TWTR': 0.12324}
方便起见,使用clean_weights(),因为它将非常小的权重截断为零,并将其余的权重四舍五入。
策略
本文将设定以下条件:
• 投资者的资本为50000美元
• 投资期限为2016年至2017年
• 投资者只能投资以下股票: 特斯拉、微软、脸书、推特
• 假设没有交易成本
• 没有卖空 (投资者只能卖其现有资产)
• 在执行优化时,投资者考虑过去252个交易日来计算历史收益和协方差矩阵
• 第一次交易确定于12月的最后一天,但订单执行于2016年1月的第一个交易日
基准1/n策略
首先创建一个简单的基准策略:**1/n投资组合**。这个想法非常简单。测试的第一天将总资本的1/n%分配给每个考虑在内的n项资产。简单起见,不做任何再平衡。
在实践中经常发生的情况是,投资组合每隔X天再平衡一次,使分配回到1/n。为什么?可以想象持有一个由X和Y两个资产组成的投资组合,在投资期开始时,分配比例是50-50。一个多月来,X的价格急剧上升,而Y的价格却下降了。因此,资产X占投资组合价值的65%,而Y仅占35%。这时可能想通过卖出一些X,买进更多的Y来重新平衡到50-50。
%%zipline --start 2015-12-31 --end 2017-12-31 --capital-base 50000.0 -o benchmark.pkl
# imports
from zipline.api import order_percent, symbols
from zipline.finance import commission
import numpy as np
import pandas as pd
def initialize(context):
context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))
context.assets = symbols('TSLA', 'MSFT', 'FB', 'TWTR')
context.n_assets = len(context.assets)
context.has_position = False
def handle_data(context, data):
if not context.has_position:
for asset in context.assets:
order_percent(asset, 1/context.n_assets)
context.has_position = True
from mpl_toolkits.mplot3d import Axes3D
下图显示了该策略的累积收益。
保存一些结果以与其他策略进行比较。
benchmark_perf = qf.get_performance_summary(returns)
夏普比率(Sharpe Ratio)投资组合最大化-每30天再平衡一次
在该策略中,投资者选择使投资组合的预期夏普比率最大化的权重。投资组合每30个交易日再平衡一次。
通过对当前交易日的编号(存储在context.time中)使用模数运算(在Python中为%)来确定制定日期是否为再平衡日。当除以30后提示为0时,就会进行再平衡。
%%zipline --start 2015-12-31 --end 2017-12-31 --capital-base 50000.0 -o max_sharpe_30_days.pkl
# imports
from zipline.api import symbols, record, order_target_percent
from zipline.finance import commission
import numpy as np
import pandas as pd
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
def initialize(context):
context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))
context.assets = symbols('TSLA', 'MSFT', 'FB', 'TWTR')
context.n_assets = len(context.assets)
context.window = 252
context.rebalance_period = 30
context.time = 0
def handle_data(context, data):
cleaned_weights = []
if context.time == 0 or (context.time % context.rebalance_period == 0):
# extract prices
prices = data.history(context.assets, fields='price',
bar_count=context.window + 1, frequency='1d')
# calculate expected returns and sample covariance amtrix
avg_returns = expected_returns.mean_historical_return(prices)
cov_mat = risk_models.sample_cov(prices)
# get weights maximizing the Sharpe ratio
ef = EfficientFrontier(avg_returns, cov_mat)
weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
# submit orders
for asset in context.assets:
order_target_percent(asset, cleaned_weights[asset])
record(weights=cleaned_weights)
context.time += 1
本文最后将对所有策略结果进行检验。然而,观察权重分配随着时间变化会十分有趣。
本图的一些见解:
• 在这一策略中几乎没有对推特的投资。
• 有时会跳过整整几个月,如2016年1月或2016年4月。这是因为再平衡的周期为每30个交易日,而平均每月有21个交易日。
夏普比率的投资组合最大化-每月再平衡
这种策略与前一种策略非常相似,仍然选择使投资组合的预期夏普比率最大化的权重。不同的是再平衡方案。首先,定义rebalance法来计算最优权重并执行相应命令。然后,使用schedule_function对其进行安排。在当前设置中,再平衡发生在市场关闭(time_rules.market_close)后一个月的最后一个交易日(date_rules.month_end)。
%%zipline --start 2015-12-31 --end 2017-12-31 --capital-base 50000.0 -o max_sharpe_monthly.pkl
# imports
from zipline.api import (symbols, record, order_target_percent,
schedule_function, date_rules, time_rules)
from zipline.finance import commission
import numpy as np
import pandas as pd
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
def initialize(context):
context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))
context.assets = symbols('TSLA', 'MSFT', 'FB', 'TWTR')
context.n_assets = len(context.assets)
context.window = 252
schedule_function(rebalance,
date_rules.month_end(),
time_rules.market_close())
def rebalance(context, data):
cleaned_weights = []
# extract prices
prices = data.history(context.assets, fields='price',
bar_count=context.window + 1, frequency='1d')
# calculate expected returns and sample covariance amtrix
avg_returns = expected_returns.mean_historical_return(prices)
cov_mat = risk_models.sample_cov(prices)
# get weights maximizing the Sharpe ratio
ef = EfficientFrontier(avg_returns, cov_mat)
weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
# submit orders
for asset in context.assets:
order_target_percent(asset, cleaned_weights[asset])
record(weights=cleaned_weights)
观察时间与权重的关系:
当每月都再平衡时,确实所有月份都有收益。在这种情况下,2017年年中对推特也进行了一些小规模投资。
波动的投资组合最小化-每月再平衡
这一次,投资者通过最小化波动来选择投资组合权重。幸亏有PyPortfolioOpt,这与将前面的代码片断中的 weights = ef.max_sharpe()改为weights = ef.min_volatility()一样简单。
最小波动策略产生的权重在一段时间内绝对是最稳定的,因为在两个连续的周期之间没有太多的再平衡。当计算交易成本时,这一点当然很重要。
效益比较
通过下面的比较,可以看到,在回测时,最小化波动的策略获得的收益最佳,同时投资组合波动也最低。它的效益也比使夏普比率最大化的策略要好得多。
另一个观察结果也很有趣:所有使用优化法创建的自定义策略的绩效都优于简单的1/n分配和买入持有相结合的组合。
结语
本文展示了如何将zipline和pypfopt结合起来,以便基于均值-方差优化法对交易策略进行回测。本文只讨论了最大化夏普比率或最小化整体波动的投资组合,不过,肯定还有更多可能性。
未来可能的方向:
• 在优化方案中,考虑分配中的最大潜在变化。在零佣金的情况下,这不是问题。但在存在交易成本的情况下,如果每隔X天完全再平衡,那么最好避免过多花销。
• 允许卖空
• 在优化问题中使用自定义目标函数——使用不同的评估指标进行优化
重要的是要记住,过去的策略执行得很好并不能保证今后还会发生同样的情况。
GitHub:https://github.com/erykml/medium_articles/blob/master/Quantitative Finance/technical_analysis_strategies.ipynb
留言 点赞 关注
我们一起分享AI学习与发展的干货
如需转载,请后台留言,遵守转载规范