• 实锤,无脑定投指数基金真的可以赚钱么?


    投资有风险,入市需谨慎,本文不构成任何投资理财建议,仅做交流学习使用。

    不知道大家有没有在网上见过各种理财课程,说什么小白入门最简单的就是定投指数基金,还说人家巴菲特也这么投,这种课程介绍是越写越夸张,后面最夸张说还能打成一个小目标。

    过分了啊,如果打成小目标这么简单,那不是中国打成小目标的怎么地也有个百分之十左右了吧,但是我身边好像没见过达成小目标的人啊,难道是因为我太 qiong 的缘故?

    还有那种用巴菲特举例子的课程介绍,拜托下次介绍的时候把故事讲完好不好,人家老巴是在美股定投基金,美股是一个熊短牛长的市场,已经连续十几年的牛市了,当然哈,最近因为疫情的影响我们和老巴一起见证了五六次的熔断。

    想想都觉得自己牛逼,老巴这么大一把岁数,也不过比我们多见证了一次美股的熔断,那么,是不是说我约等于老巴?

    所以,本着对任何事情都抱有怀疑的态度,我今天就要实锤一下,看看无脑定投我们国内的指数基金到底能不能赚钱。

    首先如果我们在低点开始定投,在高点卖出,这个是肯定能赚钱的,这个大家都没什么疑问吧?如果这个再有问题就真的出门左转吧。

    但是如果我们在高点开始定投,在低点卖出,这个真的也能赚钱么?

    接下来,我会通过科学的工具以及方法,看看能不能锤爆那些卖理财课的。

    开锤

    第一步,我们要选定一个指数基金,这里我就已自己买过的沪深 300 指数基金,开始锤起来。

    国内有很多网站都可以查到基金的历史净值,我这里选择天天基金网,选择的是「易方达沪深300ETF联接A(110020)」。名字和代码都有了,这个就不多说。

    首先,我们打开这款基金的查询页面,链接: http://fundf10.eastmoney.com/jjjz_110020.html

    可以看到哈,这个基金成立日期是 2009 年,足够我们做 10 年的数据回归分析了。

    下一步就是要把这只基金的 10 年的数据抓取下来,前面看过我的爬虫系列的同学可以先停一下,自己动手试一下,没看过的可以接着往下看。

    祭出神器 F12 ,选择 network 标签栏,顺便在页面上选择我们要的时间段,然后点击查询。

    然后我们在 network 标签栏中,可以看到这么一条请求:

    http://api.fund.eastmoney.com/f10/lsjz?callback=jQuery18303890660068294629_1586089381722&fundCode=110020&pageIndex=1&pageSize=20&startDate=2010-01-01&endDate=2020-04-05&_=1586089722912
    

    可以看到有两个分页有关的参数,一个是 pageSize (每页数据量)另一个是 pageIndex (第几页),这里我们偷个懒,尝试一次把所有的数据都拉回来,把 pageSize 设置为 4000 , 10 年, 4000 个工作日,应该足够了。

    接下来开始写第一小段代码:

    import requests
    
    startDate = '2010-01-01'
    endDate = '2020-04-05'
    foundCode = '110020'
    pageSize = 4000
    
    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
        'Referer': f'http://fundf10.eastmoney.com/jjjz_110020.html'
    }
    
    url = f'http://api.fund.eastmoney.com/f10/lsjz?&fundCode=110020&pageIndex=1&pageSize={pageSize}&startDate={startDate}&endDate={endDate}&_=1586089722912'
    response = requests.get(url, headers=header)
    
    def write_file(content):
        filename = '110020.txt'
        with open(filename, 'a') as f:
            f.write(content + '
    ')
    
    write_file(response.text)
    

    这段代码很简单,就是把数据抓回来,并且创建了一个 txt 文件,然后把数据写进去,给你们看下结果:

    数据量有点大,贴不下,就简单截个图,大致看了下数据, 10 年间,共计有 2502 条数据(这个意思就是 10 年间,只有 2502 个工作日咯)。

    计算收益

    接下来要做的事情就很明显了,我们要解析数据,开始按照定投的方式来计算收益了,对过程不敢兴趣的同学可以直接跳过这一小节,直接看后面的数据分析。

    我这里按照两种定投方式做收益计算,一个是按周定投,每周投资 500 大洋;另一个是按月定投,每月投资 2000 大洋,看下最后的收益能差多少。

    首先我们开始解析上面我们生成的那个 txt 文件,把我们需要的日期和金额取出来,使用 key 和 value 的形式组成一个 dict ,如下:

    def get_data():
        '''
        获取数据
        :return: dict
        '''
        with open('110020.txt') as f:
            line = f.readline()
            result = json.loads(line)
            date_price = {}
            for found in result['Data']['LSJZList'][::-1]:
                date_price[found['FSRQ']] = found['DWJZ']
            return date_price
    
    
    date_price = get_data()
    
    print(date_price)
    

    结果我还是截图吧,数据毕竟也是有 2502 个key value 对的。

    然后我们就开始制定我们的投资方式,第一个是按周定投,我们假定每周一定投,那么可以做一个函数 calculate_by_week(start_date, end_date) 来计算我们每周定投的收益情况,如下:

    def calculate_by_week(start_date, end_date):
        '''
        每周一定投,每次定投 500
        :param start_date: 开始时间
        :param end_date: 结束时间
        :return:
        '''
        total_stock = 0
        total_amount = 0
        nums = 0
        day = start_date + datetime.timedelta(days=-1)
        while day < end_date:
            day = day + datetime.timedelta(days=1)
            if day.weekday() != 1:
                continue
            while date_price.get(day.strftime('%Y-%m-%d'), None) is None and day < end_date:
                day += datetime.timedelta(days=1)
            if day == end_date:
                break
            nums += 1
            total_stock += round(500 / float(date_price[day.strftime('%Y-%m-%d')]), 2)
            total_amount += 500
    
        # 计算盈利
        while date_price.get(end_date.strftime('%Y-%m-%d'), None) is None:
            end_date += datetime.timedelta(days=-1)
    
        total_profit = round(total_stock, 2) * float(date_price[end_date.strftime('%Y-%m-%d')]) - total_amount
    
        return nums, round(total_stock, 2), total_amount, round(total_profit)
    

    上面这个函数会返回 4 个结果,分别是:定投次数,最终持有份额,买入总金额,实际收益。

    接下来我们在写一个按月定投的,规则简单粗暴,每月 1 号定投,如果 1 号不是交易日,那么顺延至第一个交易日,如下:

    def get_first_day_of_next_month(date):
        first_day = datetime.datetime(date.year, date.month, 1)
        days_num = calendar.monthrange(first_day.year, first_day.month)[1]  # 获取一个月有多少天
        return first_day + datetime.timedelta(days=days_num)
    
    def calculate_by_month(start_date, end_date):
        '''
        按月定投,每月 1 号买入,如果 1 号不是交易日,则顺延至下一交易日
        :param start_date:
        :param end_date:
        :return:
        '''
        total_stock = 0
        total_amount = 0
        nums = 0
        first_day = datetime.datetime(start_date.year, start_date.month, 1)
        day = first_day + datetime.timedelta(days=-1)  # 将日期设置为 start_date 上个月最后一天
        while day < end_date:
            day = get_first_day_of_next_month(day)
            while date_price.get(day.strftime('%Y-%m-%d'), None) is None and day < end_date:
                day = day + datetime.timedelta(days=1)
            if day == end_date:
                break
            nums += 1
            if day.strftime('%Y-%m-%d') in date_price:
                total_stock += round(2000 / float(date_price[day.strftime('%Y-%m-%d')]), 2)
            total_amount += 2000
    
        # 计算盈利
        while date_price.get(end_date.strftime('%Y-%m-%d'), None) is None:
            end_date += datetime.timedelta(days=-1)
    
        total_profit = round(total_stock, 2) * float(date_price[end_date.strftime('%Y-%m-%d')]) - total_amount
    
        return nums, round(total_stock, 2), total_amount, round(total_profit)
    

    数据分析

    我们先看下 110020 这只基金本身自己近 10 年的走势:

    line = (
        Line()
        .add_xaxis(list(date_price.keys()))
        .add_yaxis(
            '',
            y_axis=list(date_price.values())
        )
        .set_global_opts(
            title_opts=opts.TitleOpts(title='110020 基金走势图'),
        )
    )
    line.render()
    

    结果如下:

    从图中我们可以看到,在 2015 年前后达到了一个峰值,接着在 2018 年前后又有一个小高峰,随后在 2019 年一直阴跌,之后开始震荡。

    原因嘛大家也都知道, 15 年有一个大牛市, 18 年也涨了一波,在 110020 这支基金的折线图上体现的是淋漓尽致。

    而且 2010 年年初就净值和现在的净值相差并不大,我查了下数据, 2010 年初的单位净值为 1.06 元,而现在的单位净值为 1.3018 元,涨幅也就个 22.81% ,也就是说当年如果买了 1 万元的这个基金,到 10 年后点击今天,也就赚了个 2281 元,一年赚了 200 多,好像看起来还不如余额宝赚的多。

    接下来就是激动人心的时刻,我们来验证我们定投的收益,我预计肯定会比一次买入赚的多,我们试试看。

    首先第一组,我们从 10 年前开始买入,两组数据代入,如下:

    # 按周定投
    print(calculate_by_week(datetime.datetime.strptime(startDate,'%Y-%m-%d').date(), datetime.datetime.strptime(endDate,'%Y-%m-%d').date()))
    # 按月定投
    print(calculate_by_month(datetime.datetime.strptime(startDate,'%Y-%m-%d'), datetime.datetime.strptime(endDate,'%Y-%m-%d')))
    

    结果如下:

    (533, 273836.56, 266500, 89980)
    (125, 254918.67, 250000, 81853)
    

    我先来解释下上面这组数据,第一组是按周定投的结果,我们总共定投了 533 次,买入的总金额是 266500 元,实际收益是 89980 元。第二组是按月定投的结果,我们总共定投了 125 次,买入的总金额是 250000 元,实际收益是 81853 元。

    我用计算器算了一下,基本上收益 / 买入金额 * 100% 结果都在 33% 左右。

    10 年 33% ,好像收益比余额宝差不多了,不过这里还有一个概念,就是我们这笔钱是逐年投入的,而不是一次性投入,实际上收益应该是个 2 倍左右的关系。这个和分期还款的利息算法差不多,不多说。

    那么,还剩一个问题,我们如果是在 2015 年,也就是大牛市的最高点开始定投,直到今天那么还能赚钱么?

    来,我们接着代入数据:

    # 2015年开始,按周定投
    print(calculate_by_week(datetime.datetime.strptime('2015-01-01','%Y-%m-%d').date(), datetime.datetime.strptime(endDate,'%Y-%m-%d').date()))
    # 2015年开始,按月定投
    print(calculate_by_month(datetime.datetime.strptime('2015-01-01','%Y-%m-%d'), datetime.datetime.strptime(endDate,'%Y-%m-%d')))
    

    结果如下:

    (273, 113522.13, 136500, 11283)
    (65, 106851.87, 130000, 9100)
    

    当我看到这组数据的时候,说实话我是有点懵的,竟然真的还是能盈利的虽然赚的比例已经很低了,但是确实是还在赚钱的。

    解释一下,从 2015 年 1 月 1 日起开始周定投,共计定投了 273 次,买入的总金额是 136500 元,实际收益是 11283 元。按照月定投,共计定投了 65 次,买入总金额是 130000 ,实际收益是 9100 。

    小结

    小结一下吧,我们简单的回测了 110020 这支沪深 300 的指数基金,当然我是使用 python 来完成的,如果您觉得不方便,通过 Excel 同样可以完成,方法是次要的,目标完成了就好。

    简单分析一下,我们回测了 10 年定投和 5 年定投的结果,结果是 5 年定投的收益比 10 年定投的收益少了 7w 左右,当然,从牛市开始定投至今能达成正收益我自己也有点吃惊,不过还是可以简单的证明,即使选择在股市最高点入市,把时间拉长,指数定投也不会亏钱。

    今天的实锤打的脸有点疼,我想静静。

    最后还是那句话,投资有风险,入市需谨慎,本文不构成任何投资理财建议,仅做交流学习使用。

    代码上传至代码仓库,有需要的同学可以自取。

    示例代码

    老规矩,所有的示例代码都会上传至代码管理仓库 Github 和 Gitee 上,方便大家取用。

    示例代码-Github

    示例代码-Gitee

  • 相关阅读:
    死锁篇
    java线程池
    sql server 多行数据指定字符串拼接
    动态提交 表格
    ABP
    DDD学习
    sql 语句插入数据返回id
    Post方式提交,通过上下文HttpContext,Request[""]获取
    JQ的过滤隐藏
    sql 查询有多少行
  • 原文地址:https://www.cnblogs.com/babycomeon/p/12641883.html
Copyright © 2020-2023  润新知