量化投资策略设计与回测
在前面几个章节中,我们通过构建不同的模型来预测了股价的对数收益率的变化。在现实的金融市场中,我们需要基于股票涨跌的判断来进行具体的量化投资策略的设计,从而试图在市场中获利。对应于机器学习中的训练阶段,量化投资策略也需要通过历史数据上的回测来进行验证。在这一章节,我们会简单介绍一下量化投资的概念,然后基于之前章节的内容设计交易信号,并且在一个简单的模拟交易中比较不同的交易信号,最后我们简单介绍一下资产组合优化的概念,并且演示如何用Python进行优化问题的求解。
1. 量化投资简介
量化投资就是利用计算机技术并且采用一定的数学模型去实践投资理念,实现投资策略的过程。价值投资和趋势投资(技术分析)是引领过去一个世纪的投资方法,随着计算机技术的发展,已有的投资方法和计算机技术相融合,产生了量化投资。
量化投资具有一系列优势:
- 纪律性:严格执行投资策略,不随投资者情绪的变化而随意更改。
- 系统性:量化投资的系统性特征包括多层次的量化模型、多角度的观察及海量数据的观察等。
- 及时性:及时快速地跟踪市场变化,不断发现能够提供超额收益的新的统计模型,寻找新的交易机会。
- 准确性:准确客观评价交易机会,克服主观情绪偏差,从而盈利。
- 分散化:在控制风险的条件下,量化投资可以充当分散化投资的工具。
2010年国内推出了股指期货,第一次有了可以做空市场的工具。从那之后,国内主流的量化投资策略也逐渐变得多样。目前国内主流的量化对冲策略主要包括市场中性策略、套利策略和CTA期货策略。
市场中性策略通过构造股票多空组合减少对市场波动等风险的暴露。最典型的是Alpha策略,通过构建相对价值策略来超越指数,通过指数期货或期权等风险管理工具消除投资组合的大部分或全部系统风险,获得额外收益。国内对冲策略产品大多采用买入现货、卖出期货的对冲策略,期货价格和现货价格之间的差异会影响策略的表现。
套利策略包括统计套利、期限套利、ETF套利、分级基金套利、事件驱动套利策略等,基本思路是利用捕捉当前错误的定价,等价格回归正常后获利。例如在统计套利中,在价格出现背离走势的时候买进表现相对差的,卖出表现相对好的,在未来当这种背离趋势得到纠正时获得相对稳定的收益。而如果市场并未按照预想出现价格回归,而是进一步扩大价差,统计套利策略会产生亏损的风险。类似的,期现套利利用的是期货与现货的价差,ETF套利利用的是ETF(Exchange Traded Fund)交易型开放式指数基金的基金净值和交易所交易价格之间的价差等。
CTA即Commdity Trading Advisor,直译为商品交易顾问。CTA期货策略从不同期货市场或是同一市场内不同期货合约间的价差中寻求利润,风险和套利策略类似。趋势交易策略目前CTA运用最广泛的,运用大量不同的指标去除市场噪音并寻找当前的市场趋势,然后建立头寸,从而从市场趋势的持续发展中获利。
量化投资中还有一种特殊的类别,高频交易,即通过高性能服务器进行快速的买入卖出操作,捕捉市场上稍纵即逝的盈利机会。这类交易对于网络的延迟、计算机的处理性能等技术要求非常高,不过由于国内市场T+1(投资者当天买入的股票或基金不能在当天卖出,需待第二天进行交割过户后方可卖出)等交易规则,在国内市场中的应用非常少。
2. 交易信号构建和评价
接下来我们基于技术指标构造交易信号。需要注意的是,这里我们基于今天收盘之后的信息设计第二天是否进行买卖的信号,然后需要等到第三天及之后才能知道买卖是否赚钱。也就是说:
- Day 1:我们利用今天及之前的收盘价等信息设计了交易信号;
- Day 2:我们利用昨晚计算的信号进行买入或卖出操作;
- Day 3:我们知道赚钱了还是亏钱了。
因此我们接下来都是以Day 3相对于Day 2的涨跌作为我们设计和评估不同交易信号的基准:
- 当预测Day 3相对于Day 2的收盘价上涨时,我们应该给出买入的信号(+1);
- 当预测Day 3相对于Day 2的收盘价下跌时,我们应该给出卖出的信号(-1)。
首先读取我们要使用的平安银行的历史价格数据:
import pandas as pd import numpy as np import matplotlib.pyplot as plt import math %config InlineBackend.figure_format = 'svg' import warnings warnings.filterwarnings('ignore') # 忽略一些warnings import talib df_pingan = pd.read_csv('1.csv', dtype={'trade_date': str}) df_pingan['trade_date'] = pd.to_datetime(df_pingan['trade_date'], format='%Y%m%d') df_pingan = df_pingan.set_index('trade_date', drop=False) # 指明drop=False保留trade_dt这一列 df_pingan = df_pingan.sort_index() # 按照时间顺序排序 # 计算log return df_pingan['close1'] = df_pingan['close'].shift(-1) # 1天之后的收盘价 df_pingan['log_ret'] = np.log(df_pingan['close1'] / df_pingan['close']) # 1天之后的log return # 当我们考察今天的signal时,我们假定明天才交易,后天才能看到是否上涨或者下跌 df_pingan['log_ret'] = df_pingan['log_ret'].shift(-1) # 不使用最后一天的数据 df_pingan.dropna(inplace=True) print(df_pingan.shape)
输出为:
(2359, 13)
2.0 交易信号评价
我们利用两种方式评价我们设计的各种交易信号。
2.0.1 获胜率
交易信号的方向(+1或-1)与Day 2到Day 3的收盘价变化方向一致时称为获胜,获胜率就是指的获胜的情况占总的交易信号的比例。
计算获胜率的函数定义如下:
def cal_winrate(df, signal_col, ret_col): # 如果signal和return变化方向一致,则认为买卖点预测正确 win = df.loc[(df[signal_col] * df[ret_col]) > 0, 'log_ret'] loss = -df.loc[(df[signal_col] * df[ret_col]) < 0, 'log_ret'] total = len(win) + len(loss) winrate = len(win)/total return winrate, win, loss
2.0.2 模拟交易
我们设计一个简单的模拟交易来评估不同的交易信号。假设初始现金为20万,一次我们只交易100股,无论买入或卖出,同时不考虑交易的手续费。一开始我们只有现金,当出现建仓时机,也就是当价格连续两天上升且交易信号没有显示卖出时,第一次开账户持有股票;当昨天的交易信号显示卖出且持有的股数大于0时,按当天的收盘价卖出100股;当昨天的交易信号显示买入且持有的现金足够时,按当天的收盘价买入100股。
我们会评估不同的交易策略对应的资产的变化情况,也就是现金和持有的股票的价值的相加。然后我们会评估资产的回报率,也就是在评估阶段最后的资产相对于我们最开始的20万,赚了还是亏了;同时我们也会考察资产变化过程中的最大回撤率,也就是总资产亏得最惨的一段时间内亏掉了多少。
模拟交易的函数定义如下:
def trade_sim(df, signal_col, close_col): signal = df[signal_col] close = df[close_col] cash = pd.Series(0.0, index=df.index) asset = pd.Series(0.0, index=df.index) share = pd.Series(0.0, index=df.index) # 我们根据前一天的交易信号判断今天的交易 trade_signal = signal.shift(1) trade_unit = 100 # 首先建仓 # 当价格连续两天上升且交易信号没有显示卖出时 # 第一次开账户持有股票 entry = 3 cash[:entry] = 200000 while entry < len(close): cash[entry] = cash[entry-1] if close[entry-1] >= close[entry-2] and close[entry-2] >= close[entry-3] and signal[entry-1] != -1: share[entry] = trade_unit cash[entry] = cash[entry] - trade_unit * close[entry] break entry += 1 i = entry + 1 while i < len(trade_signal): cash[i] = cash[i - 1] share[i] = share[i - 1] # 遇到买入信号 if trade_signal[i] == 1: if cash[i] >= trade_unit * close[i]: # cash足够,成功买入 share[i] = share[i] + trade_unit cash[i] = cash[i] - trade_unit * close[i] # 遇到卖出信号 if trade_signal[i] == -1: if share[i] >= trade_unit: # share 足够,成功卖出 share[i] = share[i] - trade_unit cash[i] = cash[i] + trade_unit * close[i] i += 1 # 按收盘价计算买卖的cash asset = cash + share * close return cash, share, asset
计算最大回撤和回报率的函数定义如下:
def max_drawdown_slow(X): drawdown_max=0 for i in range(len(X)): for j in range(i, len(X)): drawdown=(X[i] - X[j])/X[i] drawdown_max=max(drawdown_max, drawdown) return drawdown_max * 100 def max_drawdown(X): i = np.argmax((np.maximum.accumulate(X) - X)/np.maximum.accumulate(X)) # end of the period j = np.argmax(X[:i]) # start of period return (1-X[i]/X[j]) * 100 def asset_evaluate(asset): ret_ratio = (asset[-1] - 200000) / 200000 * 100 maxdd = max_drawdown(asset) return maxdd, ret_ratio
这里我们利用循环定义了一个计算复杂度更高、运行时间更长的max_drawdown_slow函数,大家有兴趣可以跟基于NumPy的实现max_drawdown进行运算时间上的对比。
2.1 基于动量的交易信号
动量效应指的是当股票价格上涨,则其可能有继续上涨的动量;而当股票价格下跌,则其可能有继续下跌的动量。
基于动量指标设计交易信号的基本思路为:
- 当35日动量为正值时,市场可能还存在上升的能量,我们推荐第2期可以进行买入操作;
- 当35日动量为负值时,我们预期未来价格可能要下跌,第2期可以进行卖出操作。
代码实现如下:
def momentum_signal(df, timeperiod=35): # 默认按照35天计算动量 df['mom35'] = talib.MOM(df['close'], timeperiod=timeperiod) df.fillna(0, inplace=True) df['mom_signal'] = 0 # 35日动量值为正值时,signal取值为1,表示买入 df.loc[df['mom35'] > 0, 'mom_signal'] = 1 # 35日动量值为负值时,signal取值为-1,表示卖出 df.loc[df['mom35'] < 0, 'mom_signal'] = -1 return df df_pingan = momentum_signal(df_pingan)
可以画出35日动量值的变化曲线:
df_pingan[['mom35']].plot()
结果如图所示:
我们也可以统计一下买入和卖出的信号数量:
print(df_pingan.loc[df_pingan['mom_signal'] == 1, :].shape) print(df_pingan.loc[df_pingan['mom_signal'] == -1, :].shape)
输出为:
(1143, 15) (1175, 15)
也就是有1143个买入和1175个卖出信号。
我们首先评估交易信号的获胜率:
winrate_pingan, win_pingan, loss_pingan = cal_winrate(df_pingan, 'mom_signal', 'log_ret') print(winrate_pingan)
输出为:
0.5013297872340425
略高于50%。然后再来评估交易信号在模拟交易中的表现:
cash_pingan_mom, share_pingan_mom, asset_pingan_mom = trade_sim(df_pingan, 'mom_signal', 'close')
可以看一下资产的变化:
asset_pingan_mom.plot(title='asset')
结果如图所示:
尽管中间资产一度有增长,大的趋势还是在下跌的。
2.2 基于相对强弱指标的交易信号
对于相对强弱指标(RSI)而言,当短期RSI线向上穿过长期RSI线时,股票近期买入的力量较强,价格上涨的力量很大,其释放出一个较强的买入信号,这个信号被称为“黄金交叉”。而当短期RSI线向下跌破长期RSI线时,股票近期卖出的力量较强,价格下跌的力量很大,其释放出一个较强的卖出信号,这个信号被称为“死亡交叉”。
基于相对强弱指标设计交易信号的基本思路为:
- 计算RSI6和RSI24的值;
- 当RSI6>80或者RSI6向下穿过RSI24时,为卖出信号;
- 当RSI6<20或者RSI6向上穿过RSI24时,为买入信号。
代码实现如下:
def rsi_signal(df, timeperiod_short=
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> 在这个专刊中,我们会学习Python在金融中的应用: ·掌握Python进行数据分析和处理的方法; ·了解Python在量化投资中的应用; ·转行到金融数据分析和量化交易领域的基础等内容。 这个专刊适合: ·想要学习Python编程的在校学生; ·想要转行到金融领域的在职人士; ·想要利用业余时间进行量化投资的在职人士等。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p>