Python数据分析案例69——中美股市20年定投收益对比

背景

做量化交易或者是经常炒股的人其实都知道最近这一年股市变化有多奇妙,A股在2024年9月27日'927'新政的拉扯下,2024年还是涨了不少。美股在2024年川普当选的同时也涨了不少。但是最近2025年初无论是中国股市还是美国股市都经历了不同程度的下跌,这会让人很怀疑到底该怎么投资,怎么买是最好的。

那很自然的,还是要用数据说话,那些什么交易指标基本是都是没有用的,只要用大数据,用历史经验来证明才是有效的。

交易策略其实有很多种,但是无论你用哪种,长期来看,其实本质上来说,你还是得吃股市的整体盘面的上涨,这样才是能拿到平均收益。很简单,我们选一个时间点作为初始状态,买入并持有计算到期后的收益率,就可以对比两国股市的这个收益率的差距。但是这样很显然也是不公平的,因为跟你选不同的时间段有很大的关系,如果是买在了2008年的高峰的人可能现在都没有解套。所以我们还是使用定投收益,每天投1块钱,相当于每天都进行了投资的成本,这样我们可以计算两国股市到期之后你整体的资产价格和对应的收益率。

这样就没择时的问题,其实定投是最好的投资策略,大多数的主动型管理基金都还是超越不了市场的平均回报率。

数据介绍

本次使用中国的上证指数,深证成指和沪深300,还有美股的道琼斯,标普500以及纳斯达克的指数作为对比。用这些指数的价格进行定投,并且每天定投1块钱来计算每年的年化回报率以及到期之后的总资产价值和资产收益率。

A股的三个指数来讲比较好获取,美股的指数,其实在大多数的国内的量化库的接口都不是那么好用。Tushare要充钱,akshare获取不全面。而自己去找很慢。

我这里使用的是yfinance包,这个库是雅虎财经的,用于获取美股的价格数据是再合适不过了,并且是免费的,只是这个包在国内没有办法进行访问,需要开启代理,在代码环境里面设置代理配置。后面我会写一篇怎么开代理使用yfinance获取数据。(自备梯子)

当然,所有的数据我都下载到本地了,不同指数的数据都放在一个表的不同sheet里面。

 

需要本次案例的数据和全部代码文件的同学可以参考:定投收益案例


代码实现

做数据分析还是先导入包

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 
import seaborn as sns

plt.rcParams ['font.sans-serif'] ='SimHei'               #显示中文
plt.rcParams ['axes.unicode_minus']=False               #显示负号

读取数据:

#读取数据
sheets_dict = pd.read_excel('股票数据.xlsx',parse_dates=['Date'], sheet_name=None)
print(sheets_dict.keys())

#处理数据只要收盘价,都装入字典
df_dict={}
for k, df in sheets_dict.items():
    df_dict[k]=df.set_index('Date')['Close'].to_frame()

可以看到所有的sheet表名都读出来了,所有的数据都存在了一个字典里面,字典的键就是这个指数的中文名称,字典的值就是数据框装着里面所有的数据。

查看其中一个的前三行

df_dict['上证指数'].head(3)

我们自定义一个函数,计算一个股指的每一年的定投的收益率:

## 计算一个指数的每年的定投收益率
def calculate_dca_return(df):
    if not isinstance(df.index, pd.DatetimeIndex):
        df.index = pd.to_datetime(df.index)
    
    def yearly_return(group):
        total_shares = (1 / group['Close']).sum()
        total_investment = len(group)
        year_end_value = total_shares * group['Close'].iloc[-1]
        return (year_end_value - total_investment) / total_investment
    
    df_return_year = df.groupby(df.index.year).apply(yearly_return).reset_index()
    df_return_year.columns = ['Year', 'Return Rate']
    df_return_year['Year']=df_return_year['Year'].astype('str')
    return df_return_year.set_index('Year')

# 示例调用
df_return_year = calculate_dca_return(df_dict['标普500']).rename(columns={'Return Rate':'标普500'})
df_return_year

可以看到,其实用很简单,我们只要丢一个数据框进去,就能够返回这个数据框对应的指数的每一年的定投收益率。

遍历所有指数,合并到一个数据框。

df_return_year=pd.DataFrame()
for k, df in df_dict.items():
    df_return_year_one = calculate_dca_return(df).rename(columns={'Return Rate':f'{k}'})
    df_return_year=pd.concat([df_return_year,df_return_year_one],axis=1)

查看:

df_return_year.T.style.bar(color='pink')

可能直接看这种柱状条形图不是很明显,反正大体上的趋势就是每年有正有负。也不知道谁好谁坏,我们可以将两国股市的6个指数每一年的收益画成折线图,在同一张图上。

可视化

# 定义线型
line_styles = {
    '上证指数': '-',  # 实线
    '沪深300': '-',  # 实线
    '深证成指': '-',  # 实线
    '道琼斯': '--',  # 虚线
    '标普500': '--',  # 虚线
    '纳斯达克': '--'  # 虚线
}

marker_styles = {
    '上证指数': 'o',  # 实线
    '沪深300': 'o',  # 实线
    '深证成指': 'o',  # 实线
    '道琼斯': '^',  # 虚线
    '标普500': '^',  # 虚线
    '纳斯达克': '^'  # 虚线
}
plt.figure(figsize=(12, 4),dpi=128)
# 将数据框从宽格式转换为长格式
df_melted = df_return_year.reset_index().melt(id_vars='Year', var_name='Metric', value_name='Value')

# 画折线图
for metric, group in df_melted.groupby('Metric'):
    sns.lineplot(data=group, x='Year', y='Value', label=metric, linestyle=line_styles[metric], marker=marker_styles[metric])

# 设置标题和标签
plt.title('每年的定投收益', fontsize=14)
plt.xlabel('Year', fontsize=12)
plt.ylabel('Return Rate', fontsize=12)
plt.legend(title='Metric', bbox_to_anchor=(1.05, 1), loc='upper left')  # 将图例放在图外
# 显示图形
plt.tight_layout()
plt.show()

这样就可以清晰的看到每个指数的定投收益的情况,其实a股的三个指数走势还是很一致的,美股的三个指数走势也比较一致。只是在不同的年份a股或者是美股之间的差异有一些大,就比如在2006,2014年a股的收益好像挺高的,比美股高。然后在2008年的时候,两者的收益其实都是负数,因为发生了金融危机。

但大体趋势上来看的话,两者好像拉不开差距。

也就是说无论从哪一年的1月份开始定投,好像a股跟美股是差不了太远。

确实,定投这个事情短期来看的话是很难拉开差距的,但是如果我们定投个20年来试一试呢?


每天定投一块(20年)

我们自定义一个函数计算每天定投一块,买入指数能获取的份额,然后再将这些份额累加乘以每日的收盘价,就可以得到你的定投的收益率的每天的价值的变化

def calculate_daily_value(df,start_date=None):
    """
    计算从开始每天买入 1 元,每天价格变化的序列
    :param df: 输入数据框,包含日期和收盘价  ,start_date开始日期    
    :return: 返回一个数据框,包含日期、累计份额和每日资产价值
    """
    if start_date:
        df=df.loc[start_date:,].copy()
    # 确保日期是索引
    if not isinstance(df.index, pd.DatetimeIndex):
        df.index = pd.to_datetime(df.index)
    
    # 计算每日的份额
    df['Shares'] = 1 / df['Close']
    # 计算累计份额
    df['Cumulative Shares'] = df['Shares'].cumsum()
    # 计算每日的资产价值
    df['Daily Value'] = df['Cumulative Shares'] * df['Close']
    
    # 返回结果
    return df[['Close', 'Shares', 'Cumulative Shares', 'Daily Value']]
calculate_daily_value(df_dict['标普500'])

## 遍历所有指数,合并到一个数据框

 

def cal_allindex_daily_value(start_date=None):
    df_value_year=pd.DataFrame()
    for k, df in df_dict.items():
        df_value_year_one = calculate_daily_value(df,start_date=start_date)['Daily Value'].to_frame().rename(columns={'Daily Value':f'{k}'})
        df_value_year=pd.concat([df_value_year,df_value_year_one],axis=1)
    return df_value_year

查看后五行

df_value_year=cal_allindex_daily_value()
df_value_year.tail()

有的是nan很正常,因为中国的股市跟美国的股市交易日不完全相同,美股没有交易的话就是缺失。

#计算无风险收益的机会成本

怎么计算我们的投入成本?其实很简单,因为每天定投一块钱,所以我们有多少个交易日就投入了多少钱,就相当于交易日的日数就是我们的成本。但是因为金钱的价值在于其还有无风险的收益。我们就用最基础的两个点的收益来算好了

df_value_year.shape[0]*(1+0.02)**(df_value_year.index.year.nunique())

相当于我这二十年来定投的成本是7716块钱,其实交易日只有5000多个交易日,相当于只投了五千多块钱,还有多的2000块钱是这20年来付给你的每年年化2%的利息。有的同学说现在银行利率才一点几,哪来的2%?。。。。无风险利率并不等于银行的年化利率,也并不完全等于国债收益率,并且定投也不是从一开始所有的钱都会算利息的,而是每天投入1块钱,每天新投入的钱才会算利息。这里只是一个很简单的近似计算方法,就不用那么纠结了。

那么,最终我们来看20年来,这六大股指每天定投1块钱,他们的资产价格走势是什么样的呢?

定义函数,进行可视化:

def plot_daily_value(df_value_year):
    plt.figure(figsize=(12, 5),dpi=128)
    # 将数据框从宽格式转换为长格式
    df_melted = df_value_year.reset_index().melt(id_vars='Date', var_name='Metric', value_name='Value')
    # 画折线图
    for metric, group in df_melted.groupby('Metric'):
        sns.lineplot(data=group, x='Date', y='Value', label=metric, linestyle=line_styles[metric])
        
        

    # 设置标题和标签
    plt.title('每天定投1元的资产变化', fontsize=14)
    plt.xlabel('Year', fontsize=12)
    plt.ylabel('资产价值', fontsize=12)
    plt.axhline(y=df_value_year.shape[0]*(1+0.02)**(df_value_year.index.year.nunique()), color='gray', linestyle=':', linewidth=2, alpha=0.5)
    plt.legend(title='index', loc='upper left')  
    # 显示图形
    plt.tight_layout()
    plt.show()
plot_daily_value(df_value_year)

灰色的线是我们的定投成本,也就是上面所说的加上无风险的收益最后的7000多块钱,纳斯达克的。定投收益是最高的,其次是标普500,然后再就是道琼斯。然后再是沪深300上证指数以及深圳成指。

可以看到中国的股市没有超过灰色的线,这意味着什么我就不用说了吧,这意味着你定投20年的a股还不如把钱存在银行..........而纳斯达克在2016年之后明显与其他股指拉开了极大的差距,最终其价值有25000多块钱,也就是说你5000块钱的成本最后二十年下来能够翻五倍。标普500大概是16000多,20年来翻了三倍。

这种不利于和谐的结论我们就不要再多讲了。反正大概能看懂这个图就知道买谁好了。

20年来定投能够翻三倍,也就是说假设我们从二十五岁开始存钱,左手定投纳斯达克,右手定投标普500,每天每个指数定投100块钱,每天定投200块钱,一年大概250个交易日,一年你会定投5万块钱,如果你坚持这样定投20年,那么你相当于有了100万的成本,按照这过去20年的经验平均下来能够翻3~5倍,假设我们只翻三倍,也就是说你100万到了45岁坚持定投的话会有300万,300万基本上足够一个家庭开销15年。完全可以活到60岁退休了。

当然,想象都是这么美好的过去的经验,也不代表着说未来二十年能够再翻这么多倍,并且中国70%的人不缴纳个税,也就是说70%的人月收入都是5000块及以下,他们不可能每个月都能够拿得出将近5000块钱去定投基金的。

有的同学肯定定要说了,二十年太久了,你怎么不拿美股2020年熔断的那么多次来试试?

那我们的定投时间就不从二十年前的2005年开始,我们就从2020年疫情爆发的那一年开始。

从2020年开始定投

上面写那么多函数,就是为了下面这里可以进行方便的对比,我们只需要一行代码计算收益率,一行代码进行可视化就可以了。

df_value_year=cal_allindex_daily_value(start_date='2020-01-01')
plot_daily_value(df_value_year)

可以看到,如果我们从2020年开始定投的话,其A股三大股指也是完全不如无风险收益率的。而美股的收益是还是较高,只是说纳斯达克和标普500差距没有那么明显了。

我们计算一下成本和对应的六大股指的定投的最终收益:

print(f'投资机会成本:{df_value_year.shape[0]*(1+0.02)**(df_value_year.index.year.nunique())}')
df_value_year.tail(3)

可以看到,过去5年,1299个交易日,年化2%的情况下,我们的投资机会成本是1434,而a股的三个指数都是小于这个数值的,美股的三个指数都是大于这个数值的,其中纳斯达克涨的最多。其收益率是(1905-1299)/1299=46.6%,看起来比较多,其实每年的年化也不到10%,可能也就八九个点左右。

有的同学说,你专挑疫情经济不好的这几年,你从今年开始看看?

那我们就从2024年开始定投看看:

从2024年开始定投

df_value_year=cal_allindex_daily_value(start_date='2024-01-01')
plot_daily_value(df_value_year)

print(f'投资机会成本:{df_value_year.shape[0]*(1+0.02)**(df_value_year.index.year.nunique())}')
df_value_year.tail(3)

可以看到,A股2024年过去的这一年走势也是偏弱的,在9月27号927新政之后才收益率大幅拉上来了一点,并且波动也是较大的。其最后定投的收益率还是差于美股的三个指数。但是差距并不大,这跟我们上面计算每一年的年化定投收益率是一致的,因为从短期来看,只从一年来看,确实两大国家的股指定投的收益是差不多的。

结论这里就不用多写了,写多了可能不利于和谐。大家能够看得懂就好,怎么进行定投也不用我多说了。

(所有的结论只是学术研究,代码演示,描述现象的作用,不构成任何投资建议,无任何立场观点)


创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~

### 解决方案 对于国内用户而言,由于网络访问限制可能导致直接调用 `yfinance` 获取雅虎财经数据遇到困难。为了确保能够稳定使用该库并下载市场数据,建议采取以下措施: #### 1. 使用代理服务器 通过设置 HTTP 或 SOCKS 代理绕过可能存在的网络封锁是一个常见方法。可以在 Python 中利用环境变量或代码内部指定代理参数。 ```python import os os.environ['http_proxy'] = 'http://your.proxy.server:port' os.environ['https_proxy'] = 'https://your.proxy.server:port' # 或者在请求时显式传递代理参数 proxies = { "http": "http://your.proxy.server:port", "https": "https://your.proxy.server:port" } ``` #### 2. 更改镜像源加速依赖安装 如果是因为 PyPI 官方仓库速度慢而导致的安装失败问题,则可以通过更换为国内镜像站点来加快 pip 的操作效率[^3]。 ```bash pip install -i https://pypi.tuna.tsinghua.edu.cn/simple yfinance ``` #### 3. 尝试其他金融数据API服务提供商 考虑到长期稳定性以及更丰富的功能需求,也可以考虑转而采用一些专门为国内市场设计的数据接口服务商所提供的 SDK 或 RESTful API 接口来进行行情查询等工作[^1]。 #### 4. 修改 DNS 设置优化解析过程 有时域名解析也会成为瓶颈之一,适当调整本地计算机上的 hosts 文件或将路由器中的公共DNS替换为中国电信、阿里云等提供的免费公共DNS可能会有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阡之尘埃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值