作者:易执

来源:Python读财

提示:本文根据数据研究所得,不构成任何投资意见!

基金定投如何选择买卖点?

关于定投的择时研究

基金定投是近几年各种理财平台比较推崇的一种理财方式,大多数关于定投的宣传语中,会有这”六字箴言“:不择时,长期投。所谓择时,便是选择合适的买点和卖点,那基金定投真的用不着择时么?本文就用数据来探究一下是否需要择时以及该如何进行择时。

是否需要择时?

下面上的这张图是2004年到2020年16年间,上证指数和道琼斯指数的走势对比图,大致代表了两个不同资本市场的行情走势。

从图中可以看出两大指数的走势都颇具特点,道指基本是一种长牛的走势,对于这种走势,如果进行指数基金定投是可以不择时的。

而对上证指数而言,走势呈现一种比较明显的周期性,牛短熊长,一个牛熊周期大概为7.5年,整体的重心是向上移动的。如果进行长期投资但不进行择时,虽然长周期来看也可以获得正向的收益,但却享受不了牛市带来的情绪溢价,就像坐了趟过山车,图个刺激。

所以,对基金定投而言,还是有必要进行择时操作的,在指数低估时买入,等牛市来时卖出,那么该如何进行择时呢?

择时的基本策略

定投中可以根据股指的点位或估值高低等指标进行择时操作,本文主要研究基于估值的择时操作,在估值较低时开始定投,待估值达到历史较高水平时卖出,因此如何定义估值是高还是低便是择时操作的关键。

由于沪深300指数能够较好地反映沪深两市的走势,常被设为基金业绩的比较基准。所以本文其为例进行研究,并以市盈率(PE)为估值指标,探究不同估值区间的择时效果

具体策略:择时的指标为该指数当前的PE估值在过去7年(兼顾一轮牛熊周期)的分位数水平,若分位数水平低于定义的区间下限,开启定投周期,直到估值的分位数水平高于区间上限,将持有的份额全部卖出。卖出后,要等到估值水平再次低于区间下限,开始下一轮定投,按照这个规则持续运行。

回测的具体参数如下:

投资标的:易方达沪深 300ETF联接A(110020.OF)

回测时间范围:2013年1月1日-2020年1月1日

估值指标:市盈率(PE)

申购费率:0.15%

赎回费率:0.5%

估值区间:20%-80,30%-80%,40%-80%,20%-70%,30%-70%,40%-70%

每次定投金额:5000

频率:每周一

回测结果

具体的回测框架已经被我封装成可以直接调用的函数run_strategy,代码部分较长,已贴到文末,大家可以直接复制使用。下面直接以图形化的方式展示回测结果。

注: 结果图中,用基金净值走势近似指数走势,黄色阴影代表定投轮此区间,蓝色阴影代表当前估值水平在过去7年的百分位数(右轴表示)。

不择时,一直定投

如不进行择时,则在这七年间每周一均进行买入,一共定投了334期,总收益率为37.71%,年化的收益率大概为4.7%,总体结果算不上理想。

20%-80%区间

设定低估区间为20%以下,高估区间为80%以上则完美的吃到了2014-2015年的这轮牛市,在指数达到顶点前于左侧卖出。期间共进行了113次定投,该轮定投的收益率达到了92.8%,但由于后续估值百分位再也没有达到20%以下,之后没有产生任何买卖行为。

30%-80%区间

将低估区间的范围扩大到30%后,定投轮次加多了一轮,几乎在2018年末最低点抄底,赶上了2019年初的小阳春行情,第二阶段的定投了52期,整体收益率为10.2%。

40%-80%区间

将低估区间进一步放宽到40%后,2013-2020年这七年间共进行了3轮定投,三次定投的期数分别为113次,88次,以及68次,三个轮次定投所产生的收益也较为可观,虽然在16年和18年四季度定投产生了较大回撤,但也把握住了后期的行情。

下面将高估区间降低到70%,看进一步的回测情况。

20%、30%、40%-70%区间

下面一起显示定投区间分别为20%-70%,30%-70%以及40%-70%区间的回测结果图

若将估值70%分位数以上标定为高估区,则相较80%更早的逃顶卖出,在牛市行情中更早落袋为安,也损失了部分潜在收益。与此同时,以70%分位数为卖出点会使得卖出频率相应地也增加了。尤其是40%-70%区间,此时的高估和低估区距离较为接近,买入和卖出更加频繁。

汇总结果

将以上所有回测结果进行整理,得到下面的统计表

表中

绝对收益比 = 各区间回测总收益/不择时总收益

年化收益率 = 期间绝对收益/(首轮定投总期数*每期定投额)

由于定投是采用增量资金进行投资,如果期间定投了多轮,其实总收益率比较难去定义,所以这里选用绝对收益和我自己定义的年化收益综合去衡量(不一定科学)

如果从绝对收益的结果来看,不择时的绝对收益是最高的,但其总共定投了334期。40%-80%估值区间的回测结果用269期定投取得了和334期定投差不多的绝对收益值,且年化收益上更是远远跑赢。如果单从本次回测结果来看,综合考虑资金利用效率等因素,采用40%-80%区间进行择时的效果最好。

但实际进行选择时应该结合自己的风险偏好,低估区间设置得越高,一方面能收集更多筹码,但同时也意味着可能会承担更大的回撤风险。高估区间设置得越低,虽然会错过潜在的收益,但也能提早落地为安。

代码

具体的代码我已封装好,有兴趣的可以自己进行其他指数的研究。根据目前Tushare支持的数据,下面回测框架可以研究沪深300指数、创业板指数、中证500指数、上证50指数等主流的宽基指数的定投策略。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tushare as ts
import warnings
from IPython.display import display
from tqdm import tqdm_notebook
from datetime import *
warnings.filterwarnings("ignore")
%matplotlib inlinepro = ts.pro_api()
plt.rcParams["font.sans-serif"] = ["FangSong"]def run_strategy(index_code="",fund_code="",money_per_time=5000,start_date=None,end_date=None,v_low = None,v_high = None,plot=True):fund_df = pro.fund_nav(ts_code = fund_code)fund_df = fund_df.loc[fund_df.update_flag=='0',['ts_code','end_date','unit_nav']].rename(columns = {'end_date':'trade_date'})fund_df = fund_df[(fund_df.trade_date>=start_date) & (fund_df.trade_date<=end_date)]fund_df["trade_date"] = pd.to_datetime(fund_df.trade_date)fund_df.sort_values(by="trade_date",inplace=True)fund_df.reset_index(drop=True,inplace=True)# tushare提取数据有限制# 分开提取然后合并数据index_start_date = str(int(start_date[:4])-7) + start_date[4:]index_end_date = end_dateindex_valuation_1 = pro.index_dailybasic(ts_code = "000300.SH",start_date = index_start_date,end_date = start_date,fields="trade_date,pe_ttm")index_valuation_2 = pro.index_dailybasic(ts_code = "000300.SH",start_date = start_date,end_date = index_end_date,fields="trade_date,pe_ttm")index_valuation = pd.concat([index_valuation_1,index_valuation_2],axis=0)index_valuation["trade_date"] = pd.to_datetime(index_valuation.trade_date)index_valuation.sort_values(by="trade_date",inplace=True)index_valuation.set_index("trade_date",inplace=True)# 回测时需要计算的指标fund_df["units"] = np.nanfund_df["total_units"] = np.nanfund_df["market_value"] = np.nanfund_df["signal"] = np.nanfund_df["avg_unit_cost"] = np.nanfund_df["return_rate"] = np.nanfund_df["round"] = np.nanfund_df["percentile"] = np.nanround_num = 0round_on = False#  money_per_time = 5000# 循环进行回测# 申购费率设置为0.15%# 赎回费率设置为0.5%for i in tqdm_notebook(range(len(fund_df))):valuation_start_date = (fund_df.loc[i,"trade_date"]-pd.Timedelta(7,unit="y")).date()valuation_end_date = fund_df.loc[i,"trade_date"]valuation_data = index_valuation.loc[valuation_start_date:valuation_end_date].iloc[:,0].values.tolist()valuation_high = np.percentile(valuation_data,v_high)valuation_low = np.percentile(valuation_data,v_low)now = valuation_data[-1]fund_df.loc[i,"percentile"] = np.round((sorted(valuation_data).index(now)+1)/len(valuation_data),4)weekday = valuation_end_date.dayofweekif now<=valuation_low and round_on == False:round_on = Trueround_num += 1if round_on:if now < valuation_high:if weekday==0:fund_df.loc[i,"round"] = round_numfund_df.loc[i,"signal"] = 1fund_df.loc[i,"units"] =np.round((money_per_time - money_per_time * 0.0015)/fund_df.loc[i,"unit_nav"],2)fund_df.loc[i,"total_units"] = fund_df.loc[(fund_df.signal==1).values & (fund_df["round"]==round_num).values,"units"].sum()fund_df.loc[i,"market_value"] = fund_df.loc[i,"total_units"] * fund_df.loc[i,"unit_nav"]fund_df.loc[i,"avg_unit_cost"] = (len(fund_df[(fund_df.signal==1).values & (fund_df["round"]==round_num).values])*money_per_time)/fund_df.loc[i,"total_units"]fund_df.loc[i,"return_rate"] = np.round(fund_df.loc[i,"unit_nav"]/fund_df.loc[i,"avg_unit_cost"] -1,4)else:fund_df.loc[i,"signal"] = 0fund_df.loc[i,"units"] = -1 * fund_df.loc[fund_df.signal==1,"total_units"].iloc[-1]fund_df.loc[i,"total_units"] = 0fund_df.loc[i,"market_value"] = 0fund_df.loc[i,"avg_unit_cost"] = 0fund_df.loc[i,"return_rate"] = np.round((fund_df.loc[i,"unit_nav"]*0.995)/fund_df.loc[fund_df.signal==1,"avg_unit_cost"].iloc[-1] -1,4)fund_df.loc[i,"round"] = round_numround_on = False# 结果指标的计算action_rounds = fund_df['round'].value_counts().indexresult_list = []for action_round in action_rounds:round_df = fund_df.loc[fund_df["round"]==action_round].reset_index(drop=True)is_sell = True if round_df.loc[len(round_df)-1,'signal']== 0 else False#判断该定投轮次是否已卖出if is_sell:start_date = round_df.loc[0,'trade_date']n_period = len(round_df) - 1end_date = round_df.loc[n_period,'trade_date']avg_unit_cost = round_df.loc[n_period-1,"avg_unit_cost"]final_unit_nav = round_df.loc[n_period,"unit_nav"]return_rate = round_df.loc[n_period,"return_rate"]else:start_date = round_df.loc[0,'trade_date']n_period = len(round_df)end_date = round_df.loc[n_period-1,'trade_date']avg_unit_cost = round_df.loc[n_period-1,"avg_unit_cost"]final_unit_nav = round_df.loc[n_period-1,"unit_nav"]return_rate = round_df.loc[n_period-1,"return_rate"]result_list.append([start_date,end_date,n_period,avg_unit_cost,final_unit_nav,return_rate])result_df = pd.DataFrame(result_list,columns=['起始日','截止日','投资期数','单位平均成本','期末单位净值','总收益率'])display(result_df)y_rate = ((result_df["投资期数"]*result_df["总收益率"]).sum()/result_df["投资期数"].sum()+1)**(1/7)-1income = (np.sum(result_df["投资期数"]*result_df["总收益率"])*5000)print("年化收益率:{:.2f}%".format(y_rate*100))print("绝对收益:{}".format(income))if plot:fig,ax1 = plt.subplots(figsize=(12,8))fig.text(x=0.1, y=0.92, s='              {low}%-{high}%区间定投结果           '.format(low=v_low,high=v_high), fontsize=32,weight='bold', color='white', backgroundcolor='#3c7f99')hq_plot = fund_df[["trade_date","unit_nav"]]x1 = hq_plot["trade_date"].valuesy1 = hq_plot["unit_nav"].valuesbuy_signal = fund_df.loc[fund_df.signal==1,["trade_date","unit_nav"]]x2 = buy_signal["trade_date"].valuesy2 = buy_signal["unit_nav"].valuessell_signal = fund_df.loc[fund_df.signal==0,["trade_date","unit_nav"]]x3 = sell_signal["trade_date"].valuesy3 = sell_signal["unit_nav"].values# 绘制基金净值走势# 绘制买点和卖点ax1.plot(x1,y1,linewidth=1.5,label='指数走势')ax1.scatter(x2,y2,marker="^",c = "r",label = "买点")ax1.scatter(x3,y3,marker="v",c="g",label = "卖点")ax1.spines['top'].set_visible(False)ax1.spines['bottom'].set_visible(False)ax1.set_ylabel("基金净值",fontdict={"size":16})ax1.tick_params(axis='x',length=0,labelsize=16)ax1.tick_params(axis='y',labelsize=16)ax1.margins(0.01,0.02)ax1.legend(fontsize=14)# 绘制每日的估值百分位数,并用蓝色阴影部分表示ax2 = ax1.twinx()ax2.plot(x1,fund_df["percentile"].values,color="#87cefa",alpha=0.1,label="近七年PE")ax2.fill_between(x1,0,fund_df["percentile"].values,color = "#87cefa",alpha=0.2)ax2.spines['top'].set_visible(False)ax2.spines['bottom'].set_visible(False)ax2.set_ylabel("估值百分位数",fontdict={"size":16,'rotation':270},labelpad=20)ax2.tick_params(axis='x',length=0,labelsize=16)ax2.tick_params(axis='y',labelsize=16)ax2.set_yticklabels(['' ,'0%','20%','40%','60%','80%'])ax2.margins(0.01,0.02)# 用黄色阴影表示定投轮次for i in range(len(result_df)):round_start = result_df.loc[i,'起始日']round_end = result_df.loc[i,'截止日']date_span = pd.date_range(round_start, round_end)ax1.fill_between(date_span,np.min(y1),np.max(y1),facecolor="#ffff4d",alpha=0.2)plt.show()

注:本文根据数据研究所得,不构成任何投资意见!

-------------------End-------------------

公众号后台回复「微信群」,将邀请加入读者交流群。

为防意外,Lemon 还有一个小号叫  「柠檬数据」(ID:LemonDataLab),会不定期分享关于数据的故事,也墙裂建议大家一并关注,以防突然某天就和 Lemon 失联了

扫描回复 “600

获取《Python知识手册

基金定投如何选择买卖点?——关于定投的择时研究相关推荐

  1. 通达信缠论买卖点公式_缠论买卖点阐述及选股思路

    缠论一部关于如何精确买卖点的理论.它采用顶分型,底分型,笔,线段,中枢等基本构件,利用类背,盘背,背驰等看盘手段,依据区间套原则,在操作级别中找出盘背,背驰段,再在次级别中精确找出盘背,背驰买卖点,是 ...

  2. python不同时间周期k线_请问期货不同时间级别的k线呈现相反形态怎么判断买卖点?...

    题主的意思我用三角形态说说看. 日线级别上三角形态收敛,5分钟级别上K线是三角形态突破.这时候你就该怎么判断买卖点? 其实这个问题,就是个大小周期共振的问题. 我们一般以大周期的形态作为买卖方向的确定 ...

  3. 缠论123买卖点主图公式_缠论主图指标的正确应用方式

    [本文最先发表于公众号「缠论技术」上,公众号缠论技术内容包涵完整版缠论视频学习教程.缠论中枢指标.缠论复盘视频以及文档和实战群等等,是缠论学习者的福音].曾经写过一篇文章关于讲到缠论中枢指标的导入和安 ...

  4. php实现分时线图,史上最全分时图买卖点图解(转发收藏)!

    一.短线操作的真正目的:规避风险 短线操作不是不想赚大钱,而是为了不参与走势中不确定因素太多的调整."走势中的不确定因素"就是一种无法把握的巨大风险,用短线操作的方法,就可以尽量避 ...

  5. 《缠中说禅108课》21:缠中说禅买卖点分析的完备性

    前面已经说过三类的买卖点,一个很现实的问题,就是除了这三类买卖点之外,还有什么其他类型的买卖点?答案是否定的.这里必须强调的是,这三类买卖点,都是被理论所保证的,100%安全的买卖点,如果对这三类买卖 ...

  6. 一位交易巨匠的十年心得:如何使用MACD判断后期趋势,把握买卖点

    高手与散户的区别在哪里? 高手与散户的区别在哪里,从宏观上讲:一流高手用境界,二流高手用趋势,三流高手用技术,普通散户用迷糊.最高境界的人在讲心境如何,什么也不看,只用感觉就能炒好股赚钱.我说一流高手 ...

  7. Python示例代码之按指定算法判断买卖点计算股票收益

    炒股票的小伙伴们一般都有一个神奇的愿景,总认为按照自己的方法选择股票的买卖点,就一定能赚钱,今天我用程序模拟了一把买卖点和收益的实测,结果显示,无论我使用什么买卖点判断方法,总是有些股票赚钱,有些股票 ...

  8. 学计算机买笔记本是i5 i7,i7不一定比i5好!懂电脑的人选择买i5,而不是i7,究竟怎么回事?...

    摘要:我们在电脑装机时,经常会被五花八门的型号参数看的神魂颠倒.很多机友在选择购买英特尔酷睿I系列CPU时,通常都仅限于i7一定比i5好,i5一定比i3好的阶段.然而,对于懂电脑的人来说,选购电脑CP ...

  9. kdj买卖指标公式源码_翔博精选指标KDJ买卖点提示(通达信公式 副图 测试图)...

    做价值的传播者,一路同行,一起成长 公式源码: 公式没未来函数,买卖点提示信号不漂移. SJ:=1; N:=15; M1:=5; M2:=3; RSV:=(CLOSE-LLV(LOW,N))/(HHV ...

  10. 抄底指标:秘密买卖点

    应朋友们的请求,我将自己常用的抄底指标奉献出來,该指标捕捉底部买点还较准,但世上的指标毕竟没有百分之百准确的,大家还要在使用中认真摸索经验,根据大盘趋势來使用,在大盘趋势向上时,用它來捕捉探底结束的个 ...

最新文章

  1. android中文首字母排序,Android上汉字按拼音排序如何实现?
  2. 怎样查看was的服务器信息,WAS 查看服务状态
  3. 2018-2019-1 20165324_20165302_20165321 实验一 开发环境的熟悉
  4. python strip() 函数探究
  5. 2019年湘潭大学程序设计竞赛(重现赛)补题:F.Black White(尺取法)
  6. 【数据结构】之基本概念和术语
  7. 【Java】不需要正则表达式,提取字符串中的所有汉字、数字、字母
  8. Linux程序在预处理、编译、汇编、链接、运行步骤的作用
  9. 4.openSession() 、 getCurrentSession()与session上下文
  10. 利用mergeAttributes设置name属性
  11. R语言各个包里面的数据集
  12. goland gorm分组查询统计_MySQL·查询(一)
  13. Fedora Core下声卡驱动全功略(转)
  14. VirtualBox虚拟机安装和环境搭建
  15. 关于如何将网页做的完整且美观的解决办法
  16. 微信另外一种分享(图片 + 文字) 到朋友圈
  17. SortArrayDistanceLessK
  18. MIPS 、DMIPS、MFLOPS 是 什么?
  19. python ——随机选取n个元素
  20. php aes解密中文,PHP AES加密解密算法

热门文章

  1. IP定位如何揪出SEM、百度竞价的“头号天敌——恶意点击”
  2. PM_09 十大管理之项目人力资源管理(六)
  3. 1143 Lowest Common Ancestor
  4. C++案例——联系人管理系统
  5. 微信公众平台与微信公众平台的区别与联系
  6. 大型网站架构系列:电商网站架构案例
  7. 河北省计算机专业对口大学分数线,计算机对口升学河北分数线2017
  8. Win300英雄服务器不显示,win7系统玩不了300英雄的解决方法
  9. oracle box怎么全屏,Oracle VM VirtualBox 虚拟机设置全屏与共享
  10. 互动课件制作 html,如何实现多图版互动式图片课件的制作