01
前言
最近市场很是动荡,我一直在想能不能为大家提供一点投资建议。但奈何个人修为不够,根本看不透股市未来的走势。于是我干脆直接做了一个数据驱动的model,信号由model给出,避免掺杂任何主观判断,至于怎么用这些信号就靠大家自由发挥了。
之前的推文或多或少有一丝学术气息,今天这篇推送和以往的风格都不一样,完全以应用为导向,给出全部代码,模型原理简单,可操作性也比较高。既适合入门级量化爱好者拿来练手,同时又具备一定的实用价值。
02
北上资金简介
本文提出的择时模型是基于北上资金的流向数据构建的。北上资金即通过沪港通和深港通流入A股的资金,也是我们平时所讲的“外资”。通常而言,北上资金都被当做典型的“聪明钱”,也就是在满地韭菜中的一把镰刀,其一举一动也备受国内投资者关注。
另外,正因为北上资金的大幅流入和流出经常被分析师解读为重大的利好或利空,其异常动向也会迅速带动国内大量的跟风盘,进而正反馈作用于市场,推动市场出现拐点。因此我们可以合理猜测,北上资金流向对A股具有一定的择时效果。
03
模型构建
#导入常用数据分析包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
sns.set()
#导入数据
import akshare as ak
sh_df = ak.stock_em_hsgt_hist(symbol='沪股通')
sz_df = ak.stock_em_hsgt_hist(symbol='深股通')
hs300 = ak.stock_zh_index_daily(symbol='sh000300')
sh_df 和 sz_df 分别是沪股通和深股通的历史资金流数据,其中“当日资金流入”和“当日成交净买额”这两列容易混淆,在这里稍作解释:当日资金流入=当日成交净买额+已申报未成交买单金额。本文使用当日资金流入数据,大家也可以自行尝试使用当日成交净买额数据的效果怎么样。
sh_df = sh_df.set_index('日期').sort_index()
sz_df = sz_df.set_index('日期').sort_index()
north_money = pd.DataFrame(index=sz_df.index)
north_money['当日资金流入'] = sz_df['当日资金流入']+sh_df['当日资金流入']
north_money['涨跌幅'] = hs300['close'].pct_change()
可以查看一下我们得到的北向资金每日流入数据的分布情况:
north_money['当日资金流入'].plot.hist(figsize=(8,6),bins=40)
plt.show()
#北上资金布林带策略
def boll_trade_model(flow_df,start,end,N,Z_1,Z_2):
df = flow_df[['当日资金流入','涨跌幅']]
df['mean'] = df['当日资金流入'].rolling(N).mean()
df['std'] = df['当日资金流入'].rolling(N).std()
df['上轨'] = df['mean'] + Z_1*df['std']
df['下轨'] = df['mean'] - Z_2*df['std']
df = df.loc[start:end]
#绘制轨线图
df[['当日资金流入','mean','上轨','下轨']].plot(figsize=(15,6))
plt.legend(['当日资金流入','MD','UP','DN'])
plt.show()
df['signal'] = np.nan
df['signal'][df['当日资金流入']>=df['上轨']] = 1
df['signal'][df['当日资金流入']<=df['下轨']] = 0
long_sig_dates = df[df['signal']==1].index
short_sig_dates = df[df['signal']==0].index
df = df.fillna(method='ffill')
df['signal'] = df['signal'].shift(2)
df = df.fillna(0)
df['model_ret'] = df['涨跌幅']*df['signal']
#绘制净值图
df['指数净值'] = (1+df['涨跌幅']).cumprod()
df['指数净值'] = df['指数净值']/df['指数净值'].iloc[0]
df['策略净值'] = (1+df['model_ret']).cumprod()
df[['指数净值','策略净值']].plot(figsize=(15,8))
plt.scatter(long_sig_dates,df['指数净值'].loc[long_sig_dates],c='red')
plt.scatter(short_sig_dates,df['指数净值'].loc[short_sig_dates],c='green')
plt.show()
#计算策略评价指标
pfm_mean = df[['涨跌幅','model_ret']].mean()*252
pfm_mean.index = ['指数','策略']
pfm_std = df[['涨跌幅','model_ret']].std()*np.sqrt(252)
pfm_std.index = ['指数','策略']
pfm_sharpe = df[['涨跌幅','model_ret']].mean()*252/(df[['涨跌幅','model_ret']].std()*np.sqrt(252))
pfm_sharpe.index = ['指数','策略']
pfm_mdd = ((df[['指数净值','策略净值']].cummax()-df[['指数净值','策略净值']])/df[['指数净值','策略净值']].cummax()).max()
pfm_mdd.index = ['指数','策略']
pfm = pd.concat([pfm_mean,pfm_std,pfm_sharpe,pfm_mdd],axis=1)
pfm.columns = ['年化收益','年化波动','夏普比率','最大回撤']
return pfm
04
输出结果
我这里选择 N=120, Z_1=2, Z_2=1.5 来运行回测函数,输出结果为两张图和一个表格。
boll_trade_model(north_money,'2018-01-01','2021-03-12',120,2,1.5)
第一张图为布林带的轨线位置图:
第二张图为回测结果,收益计算方式为:T日收盘时获得信号,以T+1日收盘价买入或卖出,T+2日开始计算收益。
05
手续费测试
06
总结
代码都是可以直接拿来用的,考虑到有朋友对python不太熟悉,后续我也会在公众号实时更新模型信号(具体形式还没想好)。
最后提示一下风险。从2018年开始,我们的布林带宽度就在一直稳定扩大,这说明北上资金的每日净流入的滚动标准差越来越高,反映出北上资金自身也存在很大的分歧。这种扩张的布林带会使我们模型的信号变得迟钝。举个例子,2018年只要出现当日净流入50亿就可以触发买入信号,但到了今年这一数字变成了150亿,卖出信号也是同理,迟钝的信号可能会加剧策略波动。另外,毕竟我们只有3年的回测数据,如果未来北上资金净流入的分布发生变化,策略也面临失效的风险。
最后,我想用著名统计学家George Box的话来收尾:
All models are wrong,
but some are useful.
联系客服