文 | 太阳雪

来源:Python 技术「ID: pythonall」

最近股市火热,有很多人跃跃欲试,面对琢磨不透的市场,除了随波逐流,勇当韭菜的命运外,还有没有其他选择呢?今天就用 Python,从定投的视角上重新认识下股市。

投资有风险!决策需谨慎!

定投

很多人都听说过定投的概念,定投就是定期定额买入一个标的(股票、基金或者其他有增长空间的投资物),并且长期持有,最终会获得很好的收益,是很好的投资策略

关于长期,股神巴菲特说:

如果你没打算将一只股票持有十年,甚至都不用考虑持有十分钟

言下之意长期就是十年或者永远。

为什么要定投?一般认为,定投可以拉低购买的成本价,使收益扩大

不过也可能抬高成本价

真正原因是,市场中的 熊市比牛市长很多很多,定投相当于在长期的熊市里积聚力量,最终在牛市中展现出投资的价值

真是这样的吗?

下面我们就研究下国内市场上的一些股票基金,看看结果如何

获取数据

我们以网易财经 http://quotes.money.163.com/old/#HS 作为数据来源

通过指定股票或者基金代码获取数据

由于网易财经股票和基金 url 和 展示页不同,所以需要分别处理,以获取基金数据为例:

def fund(code):
    url = 'http://quotes.money.163.com/fund/jzzs_%s_%d.html?start=2001-01-01&end=2020-12-31&sort=TDATE&order=asc'
    data = pd.DataFrame()
    for i in range(0, 100):
        html = getHtml(url % (code, i))
        page = dataFund(html)
        if page is not None:
            data = data.append(page, ignore_index=True)
        else:
            break
        print("page ", i)
        time.sleep(1)
    filename = 'fund_%s.xlsx' % code
    data.to_excel(filename, index=False)
    print("数据文件:", filename)

def getHtml(url)):
    while(True):
        rp = rq.get(url)
        rp.encoding = 'utf-8'
        if rp.text.find("对不起!您所访问的页面出现错误") > -1:
            print("获取过于频繁,等待 5 秒再试")
            time.sleep(5)
            continue
        else:
            break
    return rp.text

def dataFund(html):
    table = Bs(html, 'html.parser').table
    if table is None:
        print(html)
        return None
    rows = table.find_all('tr', recursive=True)
    data = []
    columns = [th.text for th in rows[0].find_all('th')]
    for i in range(1, len(rows)):
        data.append(rows[i].text.split('\n')[1:-1])
    if len(data) > 0:
        pdata = pd.DataFrame(np.array(data), columns=columns)
        return pdata
    else:
        return None
  • 方法 fund 为基金数据获取总方法,接受基金代码作为参数
    
  • 通过特定的 url,可查询到 2001 年到 2020 年间的数据,数据开始时间晚于 2001 年的,会以实际开始时间来获取
    
  • 数据是分页展示的,预设最大为 100 页,循环每一页获取数据
    
  • 将获取的数据追加到 data 中,data 为 pandas 的 DataFrame
    
  • 最后将数据存入以类型和代码命名的 Excel 文件中
    
  • 方法 getHtml 接受 url 作为参数,返回 html 字符串
    
  • rq 是 requests 的别名,通过 get 方法,获取页面的 html 字符串
    
  • 判断是否被拒绝访问,如果被拒绝,等待 5 秒再试
    
  • 方式 dataFund 接受 html 字符串作为参数,从中抓取基金数据
    
  • 因为页面只有一个 table,所以先拿到 table,然后提取所有行
    
  • 从第一行中获取列名
    
  • 然后获取其他行的数据,存入 data 列表
    
  • 如果获取到了数据,将数据转换为 DataFrame 对象返回,否则返回 None
    

例如获取代码为 150124 的基金:

fund('150124')

最后在当前目录中,生成 fund_150124.xlsx 文件

相应的股票对应的方法是 stock,例如获取代码为 601600 的股票数据:


fund('601600')

生成的数据文件则为: stock_601600.xlsx

整理数据

股票数据和基金数据列有所不同,需要在分析前将数据处理为统一的形式

另外,定投而言,只需保留日期和价格,这样也有助于减少数据量

代码如下:

def dataFormat(code, type_='fund', cycleDays=5, begin='2001-01-01'):
    rawdf = pd.read_excel('%s_%s.xlsx' % (type_, code))
    # 选择对应的列
    if type_ == 'fund':
        buydf = buydf[['公布日期','单位净值']]
    else:
        buydf = buydf[['日期','收盘价']]
    buydf.columns = ["日期","单价"]
    buydf = buydf[buydf['日期']>=begin]
    buydf = buydf[buydf.index % cycleDays==0] # 选出定投时机
    return buydf
  • 方法 dataFormat 为数据整理方法
    
  • 参数 code 为股票或者基金代码,type_ 用来指定分析的是股票还是基金
    
  • 参数 cycleDays 表示定投时间间隔,默认为 5 天,即一个星期(扣除周末)
    
  • 参数 begin 为开始定投日期,默认为 2001-01-01,这也是获取数据最早的日期
    
  • 根据 code 和 type_ 用 pandas 读取数据文件,存入原始数据 rawdf 中
    
  • 然后根据 type_ 参数抽取需要的列,即 日期 和 价格,由于股票数据和基金数据的列名不同,需要更新为统一的列,日期 和 单价
    
  • 然后再筛选出开始日期及以后的数据,作为定投数据
    
  • 最后过滤出购买定投的数据,即从开始日期起,把每隔定投时间间隔的数据筛选出来,并返回
    

呈现

开始之前,需要先搞清楚怎么计算定投的价值

首先在每个定投点上,需要定投固定的金额,这个金额除以当前价格,会得到购买份数

持续下去,到将来某个点上,价值是多少呢?应该是 此前所有购买份数之和乘以这个点上的价格

对比每个点上定投的金额合计与这个点上的价值,就能知道此点上是盈是亏,用图表来展示更加直观

代码如下:

def show(buydf, amount=1000):
    buydf.insert(2,'定投金额', np.array(len(buydf)*[amount]))  ## 增加定投列
    buydf.insert(3,'数量', buydf['单价'].apply(lambda x: amount/x))  # 计算出价值
    buydf.insert(4,'累计本金', buydf['定投金额'].cumsum())  ## 计算定投累计
    buydf.insert(5,'累计数量', buydf['数量'].cumsum())  ## 计算价值累计
    buydf.insert(6,'当前价值', buydf['累计数量']*buydf['单价']) ## 计算实际单价

    ## 净值趋势
    tend = pd.DataFrame(columns=['单价'],index=buydf['日期'].to_list(),data={'单价':buydf['单价'].to_list()})

    tend.plot.line(title="净值走势", linewidth=1.5, yticks=[])
    plt.show()

    ## 选取投资比较
    data = pd.DataFrame(columns=['累计本金','当前价值'],
        index=buydf['日期'].to_list(),
        data={'累计本金': buydf['累计本金'].to_list(),
              '当前价值': buydf['当前价值'].to_list()})

    data.plot.line(title="定投效果", linewidth=1.5, yticks=[])
    plt.show()
  • 方法 show 数据展示方法,接受两个参数,buydf 为需要定投的数据,amount 为定投金额
    
  • 向 buydf 中插入辅助计算的列,并利用 pandas 的集合运算计算出 数量、累计本金、累计数量 和 当前价值,其中 DataFrame 的 cumsum 方法会计算每行的累计值
    
  • 再提取 单价 用于展示价格走势
    
  • 最后提取 累计本金 和 当前价值 作为投资效果展示
    
价格走势和投资效果最好展示在一个图表上,试试有什么办法可以做到,欢迎在学习群里交流

以基金建信央视50B (150124) 为例,从2015年5月26日开始到现在的定投:

show(dataFormat('150124', begin='2015-05-26'))

图表如下: 价格走势

定投效果

注意:基金为随意挑选,并不具备投资参考价值

结论

对多个不同的基金、股票做定投分析,发现以下结论:

  • 更早的投资,获取收益的可能性较大
    
  • 熊市越久,最后获得的收益越好
    
  • 无论是熊市还是牛市买入,长期来看对最终结果影响不大
    
  • 最重要的是:即使在最高点买入,在价格收复之前,价值便会超过成本,太神奇了!
    

最终可以得到一个与众不同的观点,定投策略几乎可以稳赚,只要坚持的时间够久

与大多数人认知不同的是,熊市是最终收益的基础,进一步证实了:

市场中的熊市比牛市长很多很多,定投相当于在长期的熊市里积聚力量,最终在牛市中展现出投资的价值

看来只要选对具有上升空间的标的,坚持长期定投,就能获得丰厚回报

不过还得提醒:

投资有风险!决策需谨慎!

你怎么看的,跑下代码试试看

总结

习得一个技能,不仅仅可以提高效率,更重要的是它可能改变我们的思考方式,将之前视而不见的东西,看的更清楚,理解的更透彻了,就像带上了眼镜,面对的是相同的世界,却看的更清晰了。

参考

https://www.liaoxuefeng.com/wiki/1016959663602400/1017785454949568

  • https://blog.csdn.net/ajian6/article/details/93615594
    
  • https://www.pythonf.cn/read/71
    
  • https://zhuanlan.zhihu.com/p/33450843
    
  • https://ri.firesbox.com/#/cn/