代码详解:基于均值-方差优化法的Python算法交易

437 阅读8分钟

全文共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学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”


(添加小编微信:dxsxbb,加入读者圈,一起讨论最新鲜的人工智能科技哦~)