打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
一个简单好用的北上资金择时模型


01


前言


最近市场很是动荡,我一直在想能不能为大家提供一点投资建议。但奈何个人修为不够,根本看不透股市未来的走势。于是我干脆直接做了一个数据驱动的model,信号由model给出,避免掺杂任何主观判断,至于怎么用这些信号就靠大家自由发挥了。

之前的推文或多或少有一丝学术气息,今天这篇推送和以往的风格都不一样,完全以应用为导向,给出全部代码,模型原理简单,可操作性也比较高。既适合入门级量化爱好者拿来练手,同时又具备一定的实用价值。

02


北上资金简介


本文提出的择时模型是基于北上资金的流向数据构建的。北上资金即通过沪港通和深港通流入A股的资金,也是我们平时所讲的“外资”。通常而言,北上资金都被当做典型的“聪明钱”,也就是在满地韭菜中的一把镰刀,其一举一动也备受国内投资者关注。

另外,正因为北上资金的大幅流入和流出经常被分析师解读为重大的利好或利空,其异常动向也会迅速带动国内大量的跟风盘,进而正反馈作用于市场,推动市场出现拐点。因此我们可以合理猜测,北上资金流向对A股具有一定的择时效果。

03


模型构建



本文选择常见的一种交易策略:布林带。布林带包含3条轨线,中轨由北上资金净流入的N日移动平均值计算得到,上、下轨则分别由N日移动平均±Z倍标准差形成。交易品种定为沪深300指数,当北上资金当日净流入上穿上轨线时买入沪深300指数,下穿下轨线时卖出沪深300指数。回测时不进行仓位管理,即一旦出现买入信号时就全仓买入,并保持满仓至出现卖出信号为止。

数据来源是python的第三方库akshare,里面提供了免费的金融数据api,调用起来十分方便。直接上代码:
#导入常用数据分析包
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 分别是沪股通和深股通的历史资金流数据,其中“当日资金流入”和“当日成交净买额”这两列容易混淆,在这里稍作解释:当日资金流入=当日成交净买额+已申报未成交买单金额。本文使用当日资金流入数据,大家也可以自行尝试使用当日成交净买额数据的效果怎么样。


接着将沪股通和深股通的资金流入数据按日加总,就可以得到北上资金的每日资金流入数据了。为了后续计算方便,我们将北上资金数据和沪深300每日涨跌幅合并成一个dataframe。

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()
我分别计算了一下2017、2018、2019、2020年的偏度,分别是0.11、0.695、0.832、0.223,基本可以确定北上资金净流入是一个右偏分布,因此我们需要对布林带策略做少许调整:计算上下轨线时使用不同的Z值,且上轨的Z值应该大于下轨

准备工作全部到位,开整。

#北上资金布林带策略
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
为了在调参回测时节省时间,我把全部的分析代码都打包到了这个主函数boll_trade_model()。该函数有6个变量,flow_df 输入我们已经处理好的dataframe,即前面得到的north_money;start 和 end 分别输入回测开始日期和结束日期;N、Z_1、Z_2 是上文中提到计算轨线时所需的可变参数。

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日开始计算收益。

可以看到,我们的择时策略显著优于基准指数,可以较为精准地把握市场上涨的机会,并且在几次巨大的市场波动之前就空仓锁定收益,回撤控制很优秀。

在指数净值曲线上标注出了模型生成的买卖点信号,红色点为买入信号,绿色点为卖出信号。明显看出自2018年底尤其是2019年开始,信号的准确度相当高,抓到了很多重要的拐点位置。而近期模型也报了两次卖出信号,成功减少了这段时间大跌造成的损失。

最后一个表格展示的是一些重要的策略指标:

而这仅仅只是择时交易沪深300指数,如果把交易标的换成沪深300指数增强基金,收益还会更高。大家也可以自己调整不同参数来进行回测尝试。

05


手续费测试


量化交易一项很重要的损耗就是手续费,高换手+高手续费会吃掉很大的利润空间。我对比测试了双边手续费分别为0.3%、0.5%、1%、1.5%和2%时的策略净值表现:

可见双边手续费在0.5%以下时,策略表现仍然可圈可点;而超过1%的费率水平就已经严重限制策略发挥了。不过还好,市场上有很多双边手续费低于0.5%的交易方式。

06


总结


代码都是可以直接拿来用的,考虑到有朋友对python不太熟悉,后续我也会在公众号实时更新模型信号(具体形式还没想好)。

最后提示一下风险。从2018年开始,我们的布林带宽度就在一直稳定扩大,这说明北上资金的每日净流入的滚动标准差越来越高,反映出北上资金自身也存在很大的分歧。这种扩张的布林带会使我们模型的信号变得迟钝。举个例子,2018年只要出现当日净流入50亿就可以触发买入信号,但到了今年这一数字变成了150亿,卖出信号也是同理,迟钝的信号可能会加剧策略波动。另外,毕竟我们只有3年的回测数据,如果未来北上资金净流入的分布发生变化,策略也面临失效的风险。


其实,任何一个模型都可能(甚至一定)失效。一个优秀的quant trader最重要的品质就是知道什么时候应该信任模型,什么时候应该进行人为干预。就算是西蒙斯这样的量化交易教父在遇到巨额回撤的时候还是把计算机的电源拔了。正因如此人家才能在30年的时间里创造年化39.1%收益的奇迹,完全信任数学模型的LTCM不到四年就爆仓了。

最后,我想用著名统计学家George Box的话来收尾:

 

All models are wrong, 

but some are useful.



本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
写给初学者的LASSO回归
高斯变换简介及Python示例
Python随机波动模型Stochastic volatility,SV随机变分推断SVI分析标普500指数时间数据波动性可视化
股市中的摇钱“树”
Python机器学习库sklearn数据预处理,数据集构建,特征选择
实战|Python数据分析可视化并打包
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服