Python | 怎么在基金定投上实现收益最大化

来自:SAMshare,作者:Samshare

Index

  • 分析思路阐述

  • 是否存在最合适的定投周期?

  • 设置多少止盈点较为合适?

  • 其他策略

  • 本文总结


分析思路阐述

继上一篇文章《Python在基金定投上的验证》,我们今天讨论一下更加深入的Ideas,总结来说主要有两点:

1)是否存在最合适的定投周期?

2)我们到底要设置多少止盈点较为合适?

什么意思呢?我们展开来说,

对于第一点:是否存在最合适的定投周期?

基金定投作为"懒人理财"的头号玩家,当然得贯彻其"懒人"思维,我们想知道到底定投多久,才能说大概率地获得较为满意的收益。

这里有几个点需要理一下:

  • 入场的随机性:因为涉及到实际操作,每个人的入场时间不尽相同,所以这里探讨最合适的定投周期,要考虑入场时机的随机性。

  • 指数的异质性:我们讨论的是指数基金,但不同的指数之间,也是可能会存在不一样的性质,这里也要考虑。

  • 人性的弱点:因为是定投,实际操作的还是人,所以当定投期结束时,是否会因为当前是所谓的"上涨"or"下跌"行情而纠结?

  • 何为"满意的收益”:如何判断“满意”呢,不同人对满意的态度不同,如何定义一个基准。

针对以上的几点,我在自己思考后给出了下面的验证方式:

在指定的定投周期内(定投周期为0.5年的整数倍),遍历所有可能的入场点,卖出点取离场点当月的指数均值,如果收益率大于余额宝定存相同周期时间内的收益的,即为之"满意",同时验证多几个指数。

简单解释一下,我们验证的定投周期,均是0.5年的整数倍,比如定投1年、1.5年、2年等等,买入日期,选择月初,而卖出点,不会是最后的那个月的月初,而是取其当月的指数均值,比如2019年1月1日开始定投,定投1年,那么卖出点就是2020年2月的指数均值,就是为了防止有的人因为当时股价变化而出现的纠结情绪。

当指数基金定投收益率大于余额宝定存收益率的,记为1,为我们的正样本,反之则记为0,为我们的负样本。我们就来统计一下,不同的定投周期,正负样本的比例关系,看下怎么样的定投周期,具备较大概率的"满意"结果!

对于第二点:我们到底要设置多少止盈点较为合适?

关于这点,我们就是为了找出多少的止盈点较为合适。这个结合我们上篇文章的内容,我们知道如果我们定投的周期越长,收益率当然就是越大的,所以,不同定投周期之间的收益率没有可比性。所以我们需要指定一个定投周期(姑且我们用第一点得到的"最优"定投周期N来作为我们的观测周期)。

我们的验证方式:

在指定的定投周期内,遍历所有可能的入场点,卖出点取离场点当月的指数均值,记录此时的收益率情况,统计收益率的大概率集中区间,即为我们的参考止盈点。

接下来,我们就针对以上的2个内容,来用python代码验证一波。(:show time~)


是否存在最合适的定投周期?

1. 导入相关库

 1# -*- coding: UTF-8 -*-
2import matplotlib as mpl
3import matplotlib.pyplot as plt
4import pandas as pd
5import numpy as np
6import tushare as ts
7from datetime import datetime
8
9#解决中文显示问题,Mac
10%matplotlib inline
11from matplotlib.font_manager import FontProperties
12
13plt.rcParams['figure.figsize'] = (10.04.0# 设置figure_size尺寸
14plt.rcParams['image.interpolation'] = 'nearest' # 设置 interpolation style
15plt.rcParams['image.cmap'] = 'gray' # 设置 颜色 style
16plt.rcParams['savefig.dpi'] = 100 #图片像素
17plt.rcParams['figure.dpi'] = 100 #分辨率
18
19# 利用plotly绘图
20import plotly.offline as of
21import plotly.graph_objs as go

2. 数据获取

这里还是适用Tushare的接口来获取数据,具体代码如下:

 1# 设置token
2ts.set_token('your token')
3
4# 初始化接口
5pro = ts.pro_api()
6
7# 获取基本信息
8sh_basic = pro.index_basic(market='SSE') # 上交所
9sz_basic = pro.index_basic(market='SZSE') # 深交所
10
11
12# 000001.SH 上证综指
13# 399001.SZ 深证成指
14data_sh = pro.index_daily(ts_code='000001.SH', start_date='19901219', end_date='20190401')
15data_sz = pro.index_daily(ts_code='399001.SZ', start_date='19901219', end_date='20190401')
16data_sh.head()

3. 代码封装

对上面提及的验证方式进行逻辑封装:

 1def Log_of_AIP(data, n, money):
2    '''
3    data:测试数据集,即指数历史走势数据
4    n:定投周期,6个月的整数倍,如n=1代表定投6个月
5    money:定投金额
6    '''
7
8    data = data.loc[:,['open','trade_date']]
9    data['trade_month'] = data['trade_date'].apply(lambda x:str(x)[0:6])
10    data['date'] = data['trade_date'].apply(lambda x:datetime.strptime(x,'%Y%m%d'))
11    data = data.set_index('date').sort_index()
12
13    # 假设余额宝的年化收益率为 4%
14    data['余额宝利率'] = (4.0/100+1)**(1.0/250)-1  
15    data['理财收益_净值'] = (data['余额宝利率']+1).cumprod()
16
17    # 选择每个月的第一个交易日进行定投
18    trading_day = data.resample('M', kind='date').first()
19
20    # 确定循环次数,因为得保证满足定投周期
21    try:
22        All_Sales = pd.DataFrame()
23        for i in range(len(trading_day) - (6*n)):
24            # 在定投周期结束后一个月卖出
25            trading_cycle = trading_day.iloc[i:i+6*n+1] 
26
27            # 计算卖出点 下个月的指数均值
28            in_month = data[data['trade_month']==list(trading_cycle['trade_month'][-1:])[0]]
29            sales_point = in_month.pivot_table(values='open', index='trade_month').mean().values[0]
30
31
32            # 定投指数基金
33            AIP = pd.DataFrame(index=trading_cycle.index)
34            AIP['定投金额'] = int(money)
35
36            # 以基金当天的开盘价作为当天买入的价格
37            AIP['基金价格'] = trading_cycle['open']
38            AIP['购买基金份额'] = AIP['定投金额']/AIP['基金价格']
39            AIP['累计基金份额'] = AIP['购买基金份额'].cumsum()
40
41            # 定期购买理财产品
42            AIP['购买理财产品份额'] = AIP['定投金额']/trading_cycle['理财收益_净值']
43            AIP['累计理财产品份额'] = AIP['购买理财产品份额'].cumsum()
44
45            # 累计投入本金
46            AIP['累计定投本金'] = AIP['定投金额'].cumsum()
47
48            # 计算每个交易日的本息(即本金+利息,公式=当天的份额 X 当天的基金价格)
49            result = pd.concat([trading_cycle, AIP], axis=1)
50            result['基金本息'] = (result['open'] * result['累计基金份额']).astype('int')
51            result['理财本息'] = (result['理财收益_净值'] * result['累计理财产品份额']).astype('int')
52
53            # 买入点 result['trade_date'][0]
54            # 定投周期(月) 6*n
55            # 定投投入本金 result['累计定投本金'][-2:-1][0]
56            # 基金卖出后本息 result['累计基金份额'][-2:-1][0] * sales_point
57            # 余额宝卖出后本息 result['理财本息'][-2:-1][0]
58
59
60            Each_Sales = pd.DataFrame([[result['trade_date'][0], 
61                                       6*n, 
62                                       result['累计定投本金'][-2:-1][0],
63                                       result['累计基金份额'][-2:-1][0] * sales_point, 
64                                       result['理财本息'][-2:-1][0]]], 
65                                     columns=['买入点','定投周期(月)', '累计定投本金', '基金卖出后本息', '余额宝卖出后本息'])
66            Each_Sales['基金收益率%'] = 100*(Each_Sales['基金卖出后本息'][0]/Each_Sales['累计定投本金'][0] - 1)
67            Each_Sales['余额宝收益率%'] = 100*(Each_Sales['余额宝卖出后本息'][0]/Each_Sales['累计定投本金'][0] - 1)
68            Each_Sales['LikeOrNot'] = Each_Sales['基金卖出后本息'] > Each_Sales['余额宝卖出后本息'] 
69            All_Sales = All_Sales.append(Each_Sales)
70
71        return All_Sales
72
73    except:
74        print("定投周期大于历史股价走势!请重新设置定投周期。")
75
76
77def Rate_of_Like(data, money):
78    data= data
79    money= money
80
81    Rate_of_like = pd.DataFrame()
82    for i in range(int(len(trading_day)/6)):
83        tt = Log_of_AIP(data_sh, i+1, 2000)
84        rate = pd.DataFrame([[(i+1)*6, (tt['LikeOrNot'].value_counts()/len(tt))[True]]], 
85                            columns=['定投周期(月)','定投基金满意占比'])
86        Rate_of_like = Rate_of_like.append(rate)
87    return Rate_of_like

4. 函数调用&猜想验证

1rate_of_sh = Rate_of_Like(data_sh, 2000)
2rate_of_sh.head()

 1# 绘制定投满意概率图
2of.offline.init_notebook_mode(connected=True)
3
4trace1 = go.Scatter(
5    x=rate_of_sh['定投周期(月)'],
6    y=rate_of_sh['定投基金满意占比'],
7    mode = 'lines+markers',
8    name = '定投基金满意的次数占比'
9)
10data = go.Data([trace1])
11
12layout = dict(title = '是否存在最合适的定投周期?',
13              yaxis = dict(showgrid=True,  #网格
14                           zeroline=False,  #是否显示基线,即沿着(0,0)画出x轴和y轴
15                           nticks=20,
16                           showline=True,
17                           title='定投基金满意的次数占比'),
18
19              xaxis = dict(showgrid=True,  #网格
20                           zeroline=False,  #是否显示基线,即沿着(0,0)画出x轴和y轴
21                           nticks=20,
22                           showline=True,
23                           title='定投周期(月)')
24             )
25fig = dict(data=data, layout=layout)
26of.plot(fig, filename='rate_of_like')

上面,我只是验证了上证综指,从数据中可以看出,当定投周期超出240个月,即20年,我们获得满意(即定投收益跑赢余额宝定存收益)的基金定投的概率超出80%!

此外,我们看到,好像定投周期在300个月以上,获得满意的概率竟然得到了100%,但是我们知道这不太符合实际,原因是因为这基本是从上证开盘就开始定投,也不太满足我们的实际操作。

综上所述:(针对上证综指)

  • 定投周期在50个月之内的,获得满意的概率是一半一半,定投的威力还没体现出来;

  • 当定投周期在60个月左右的满意概率与定投60~120个月的满意概率差不多;

  • 定投240个月左右的满意概率可以达到80%,定投的威力终于来了!


设置多少止盈点较为合适?

1. 函数调用

 1log = Log_of_AIP(data_sh, 40, 2000)
2log = log.sort_values(['基金收益率%'])
3
4# 转化为密度函数,以10%为一个单位
5log['density'] = log['基金收益率%'].apply(lambda x:int(x/10))
6log2 = log.pivot_table(values='LikeOrNot', index='density', aggfunc=len).reset_index()
7log2['density2'] = log2['density'].apply(lambda x:str(10*x) +'%~'+str(10*(x+1)) + '%')
8
9# 帕累托图
10log2['density3'] = log2['density'].cumsum()
11log2['density4'] = log2['density3']/(log2.tail(1).reset_index()['density3'][0])
12log2

2. 猜想绘制

这里只是贴了其中一个绘制代码:

 1# 绘制收益分布图
2of.offline.init_notebook_mode(connected=True)
3
4trace1 = go.Scatter(
5    x = pd.Series(range(len(log))),
6    y = log['基金收益率%'],
7    mode = 'markers',
8    name = '基金 收益率分布图'
9)
10
11trace2 = go.Scatter(
12    x = pd.Series(range(len(log))),
13    y = log['余额宝收益率%'],
14    mode = 'markers',
15    name = '余额宝 收益率分布图'
16)
17
18data = go.Data([trace1,trace2])
19
20layout = dict(title = '设置多少止盈点较为合适?',
21              yaxis = dict(showgrid=True,  #网格
22                           zeroline=False,  #是否显示基线,即沿着(0,0)画出x轴和y轴
23                           nticks=20,
24                           showline=True,
25                           title='投资收益率%'),
26
27              xaxis = dict(showgrid=True,  #网格
28                           zeroline=False,  #是否显示基线,即沿着(0,0)画出x轴和y轴
29                           nticks=20,
30                           showline=True,
31                           title='迭代次数'
32                           )
33             )
34fig = dict(data=data, layout=layout)
35of.plot(fig,filename='log')

我们按照定投最优的周期(240个月)来进行止盈点的计算,从上图可以看出,余额宝在定投240个月后,收益率大概维持在50%左右,而基金大多数的收益率都是大于余额宝的。

根据基金的收益率,我进行了密度转换,可以看出,我们的收益率,大致都是集中在60~70%之间。

综上所述:(针对上证综指)

  • 定投20年基金,大概率(80%)水平上会获得比余额宝要好得多的收益;

  • 定投20年基金,止盈点定在80~90%,发生的可能性为80%;

  • 定投20年基金,止盈点定在130~140%,发生的可能性为50%;

  • 定投20年基金,止盈点定在200~210%,发生的可能性为20%;


其他策略

此外,我也从网上看到了其他更佳复杂的投资策略,这里就先不验证,先放出来给大家参考,下次一起验证:

方法一:高抛低吸降成本

熊市中,不要相信某一次上涨行情可能是新一轮牛市的开端,牛市的到来远远比你想象的要遥远。对于部分套牢的基金,建议在反弹10%以上时,逐步卖出,保留一部分底仓。然后遇到下跌,再买回来。这样做,大概率上会降低成本。

要牢牢记住,反弹多了就减仓,就算你很不幸,卖在了牛市的起步阶段,你也可以在牛市行情确立的时候再买回来。

这样高抛低吸的来回交易,对主动管理型基金来说,成本费用太高。建议买入创业板ETF或者中小板ETF,手续费更便宜。

方法二:10%补仓法

不断进行补仓,使亏损比例控制在10%以内,然后一个10%的反弹就解套了。是不是很简单?缺点就是要有足够的资金。


本文总结

我们通过上述的验证,大致可以得到下面的结论,当然这个结论是针对上证综指的,按道理我应该看过几个指数,但是授之以鱼不如授之以渔,方法都在代码里,大家不妨自己动手试试?自己得到一些结论不是来得更有feel不

  • 定投周期在50个月之内的,定投跑赢余额宝的概率是50%;

  • 当定投周期在60个月(5年)左右的满意概率与定投60~120个月的满意概率差不多,大概是65%;

  • 定投240个月(20年)左右的满意概率可以达到80%,大概率水平上会获得比余额宝要好得多的收益!

  • 定投20年基金,止盈点定在80~90%,发生的可能性为80%;

  • 定投20年基金,止盈点定在130~140%,发生的可能性为50%;

  • 定投20年基金,止盈点定在200~210%,发生的可能性为20%;


Reference

推荐↓↓↓
Python编程
上一篇:Python实现微信防撤回 下一篇:爆红GitHub!有人打算用这个项目100天拿下Python