基于Python的金融数据分析以及经济数据应用

几个基本概念:

截面:表示某个时间点的数据
面板:多个数据项在多个时间点的截面数据构成一个面板
面板数据既可以被表示为层次化索引的DataFrame,也可以被表示为三维的Panel pandas对象

import pandas as pd
import numpy as np
from pandas import DataFrame,Series
from datetime import datetime

一、数据对齐问题

data = [[379.74,64.64,1165.24,71.15],[379.74,64.64,1165.24,71.15],[379.74,64.64,1165.24,71.15],
                    [379.74,64.64,1165.24,71.15],[379.74,64.64,1165.24,71.15],[379.74,64.64,1165.24,71.15],
                       [379.74,64.64,1165.24,71.15]]
prices = DataFrame(data,index=pd.date_range('2011-09-06',periods=7),
                   columns=['AAPL','JNJ','SPX','XOM'])

频率不同的时间序列的运算

有些经济学时间序列有时就是不规则的,比如盈利预测调整随时都可能发生。频率转换和重对齐的主要工具是resamplereindex方法。
resample用于将数据转换到固定频率,而reindex用于使数据符合一个新索引

ts1 = Series(np.random.rand(3),index=pd.date_range('2012-6-13',periods=3,freq='W-WED'))

如果将其重采样到工作日频率,则那些没有数据的就会出现一个“空洞”,所以加上一个ffill表示向前填充

ts1.resample('B').ffill()

如果要将ts1中最当前的值填充到ts2中。将两者重采样为规整频率后再相加,并维持ts2的日期索引,则reindex是更好的解决方案

dates = pd.DatetimeIndex(['2012-6-12','2012-6-17','2012-6-18',
                          '2012-6-21','2012-6-22','2012-6-29'])
ts2 = Series(np.random.rand(6),index=dates)
ts1.reindex(ts2.index,method='ffill')
ts2 + ts1.reindex(ts2.index,method='ffill')

二、Period的使用

Period提供了一种处理不同频率时间序列的方法,比如说发布以6月结尾的财年的每季盈利报告,表示为Q-JUN

gdp = Series([1.78,1.94,2.08,2.01,2.15,2.31,2.46],index=pd.period_range('1984Q2',periods=7,freq='Q-SEP'))
infl = Series([0.025,0.045,0.037,0.04],index=pd.period_range('1982',periods=4,freq='A-DEC'))

gdp
Out:
1984Q2    1.78
1984Q3    1.94
1984Q4    2.08
1985Q1    2.01
1985Q2    2.15
1985Q3    2.31
1985Q4    2.46
Freq: Q-SEP, dtype: float64

infl
Out:
1982    0.025
1983    0.045
1984    0.037
1985    0.040
Freq: A-DEC, dtype: float64

Period索引的两个不同频率的时间序列之间的运算必须进行显式转换。假设已知infl值是在每年年末观测的,于是我们就可以将其转换到Q-SEP以得到该频率下的正确时期:

infl_q = infl.asfreq('Q-SEP',how='end')

然后就可以被重复索引了

infl_q.reindex(gdp.index,method='ffill')

时间和“最当前”数据选取

但数据不规整的处理方法(观测值没有精确落在期望的时间点上),生成一个交易日内的日期范围和时间序列

rng = pd.date_range('2012-6-1 9:30','2012-6-1 15:59',freq='T')

生成5天的时间点

rng = rng.append([rng+pd.offsets.BDay(i) for i in range(1,4)])
ts = Series(np.arange(len(rng),dtype=float),index=rng)

1、利用Python的datetime.time对象进行索引可抽取这些时间点上的值

from datetime import time
ts[time(10,0)]

2、该方法相当于

ts.at_time(time(10,0))

3、还有一个between方法

ts.between_time(time(10,0),time(10,1))

可能刚好没有任何数据落在某个具体的时间

indexer = np.sort(np.random.permutation(len(ts))[700:])
irr_ts = ts.copy()
irr_ts[indexer] = np.nan
irr_ts['2012-6-1 9:50':'2012-6-1 10:00']
# 构造一个日期范围(每天上午十点),传入asof
selection = pd.date_range('2012-6-1 10:00',periods=4,freq='B')

三、拼接多个数据源

  • 在一个特定的时间点上,从一个数据源切换到另一个数据源
  • 利用pandascontact将两个TimeSeriesDateFrame对象合并在一起
  • 用另一个时间序列对当前时间序列中的缺失值“打补丁”
  • 将数据中的符号(国家、资产代码)替换为实际数据
案例1
data1 = DataFrame(np.ones((6,3),dtype=float),columns=['a','b','c'],index=pd.date_range('6/12/2012',periods=6))
data2 = DataFrame(np.ones((6,3),dtype=float)*2,columns=['a','b','c'],index=pd.date_range('6/13/2012',periods=6))
spliced = pd.concat([data1.loc[:'2012-6-14'],data2.loc['2012-6-15':]])
spliced
Out:

			a	b	c
2012-06-12	1.0	1.0	1.0
2012-06-13	1.0	1.0	1.0
2012-06-14	1.0	1.0	1.0
2012-06-15	2.0	2.0	2.0
2012-06-16	2.0	2.0	2.0
2012-06-17	2.0	2.0	2.0
2012-06-18	2.0	2.0	2.0
案例2,假设data1缺失data2中存在的某个时间序列
data2 = DataFrame(np.ones((6,4),dtype=float)*2,columns=['a','b','c','d'],index=pd.date_range('6/13/2012',periods=6))
spliced = pd.concat([data1.loc[:'2012-6-14'],data2.loc['2012-6-15':]])
spliced
Out:

			a	b	c	d
2012-06-12	1.0	1.0	1.0	NaN
2012-06-13	1.0	1.0	1.0	NaN
2012-06-14	1.0	1.0	1.0	NaN
2012-06-15	2.0	2.0	2.0	2.0
2012-06-16	2.0	2.0	2.0	2.0
2012-06-17	2.0	2.0	2.0	2.0
2012-06-18	2.0	2.0	2.0	2.0

combine_first可以引入合并点之前的数据,这样就也扩展了d项的历史,且data2没有‘2012-6-12’的数据,则没有被填充

spliced_filled = spliced.combine_first(data2)
spliced_filled
Out:

			a	b	c	d
2012-06-12	1.0	1.0	1.0	NaN
2012-06-13	1.0	1.0	1.0	2.0
2012-06-14	1.0	1.0	1.0	2.0
2012-06-15	2.0	2.0	2.0	2.0
2012-06-16	2.0	2.0	2.0	2.0
2012-06-17	2.0	2.0	2.0	2.0
2012-06-18	2.0	2.0	2.0	2.0

dataframe也有类似的方法upgrade,如果只想填充空洞,则必须传入参数overwrite=False

spliced.update(data2,overwrite=False)
以上的方法都能实现将数据终端符号替换为实际数据

利用dataframe的索引机制直接对列进行设置更简单

cp_spliced = spliced.copy()
cp_spliced[['a','c']] = data1[['a','c']]

四、收益指数和累计收益

import pandas_datareader.data as web
price = web.get_data_yahoo('AAPL','2019-1-1')['Adj Close']
price[-5:]

计算两个时间点之间的累计百分比回报只需计算价格的百分比变化即可:

price['2019-10-3']/price['2019-3-1']-1

利用cumprod计算出一个简单的收益指数

returns = price.pct_change()
ret_index = (1+returns).cumprod()
ret_index[0]=1 #将第一个值设置为1
ret_index
Out:
Date
2018-12-31    1.000000
2019-01-02    1.001141
2019-01-03    0.901420
2019-01-04    0.939901
2019-01-07    0.937809
                ...   
2020-04-20    1.786217
2020-04-21    1.731005
2020-04-22    1.780864
2020-04-23    1.773962
2020-04-24    1.825176
Name: Adj Close, Length: 332, dtype: float64

得到收益指数后,计算指定时期内的累计收益就很简单了:

m_returns = ret_index.resample('BM').last().pct_change()
m_returns['2020']

其中‘how’参数已经不适用了,可以将resample(how='last')改成resample.last()的形式,详情请看:
resample函数‘how’参数报错解决方案

五、分组变换和分析

1、首先随机生成1000个股票代码

import random; random.seed(0)
import string

N=1000
def rands(n):
    choices = string.ascii_uppercase
    return ''.join([random.choice(choices) for _ in range(n)])
tickers = np.array([rands(5) for _ in range(N)])

2、创建一个含有3列的dataframe承载假想数据

M=500
df = DataFrame({'Momentum':np.random.randn(M)/200+0.03,
                'Value':np.random.randn(M)/200+0.08,
                'ShortInterest':np.random.randn(M)/200-0.02},
               index=tickers[:M])

3、为这些股票随机创建一个行业分类

ind_names = np.array(['FINANCIAL','TECH'])
sampler = np.random.randint(0,len(ind_names),N)
industries = Series(ind_names[sampler],index=tickers,name='industry')

4、根据行业分类进行分组并执行分组聚合和变换

by_industry = df.groupby(industries)
by_industry.mean()
Out:
			Momentum	Value	ShortInterest
industry			
FINANCIAL	0.030001	0.080211	-0.019961
TECH		0.029953	0.079883	-0.019949

对这些按行业分组的投资组合进行各种变换,编写自定义的变换函数
例如:行业内标准化处理,广泛用于股票资产投资组合的构建过程
行业内标准化处理

def zscore(group):
    return (group-group.mean())/group.std()
df_stand = by_industry.apply(zscore)

这样处理之后,各行业的平均值为0,标准差为1:

df_stand.groupby(industries).agg(['mean','std'])
Out:
			Momentum			Value			ShortInterest
			mean			std	mean			std	mean			std
industry						
FINANCIAL	2.946117e-16	1.0	-9.475033e-16	1.0	3.793403e-15	1.0
TECH		3.747702e-15	1.0	-9.488209e-16	1.0	-1.912103e-15	1.0
使用内置函数(如rank)会更简洁

1、行业内降序排名

ind_rank = by_industry.rank(ascending=False)
ind_rank.groupby(industries).agg(['min','max'])

“排名和标准化”是一种常见的变换组合,通过将rankzscore连接在一起即可完成整个变换过程
2、行业内排名和标准化

by_industry.apply(lambda x:zscore(x.rank()))

六、信号前沿分析

将金融和技术领域的几只股票做成一个投资组合,并加载他们的历史价格数据

names = ['AAPL','GOOG','MSFT','DELL','GS','MS','BAC','C']
def get_px(stock,start,end):
    return web.get_data_yahoo(stock,start,end)['Adj Close']
px = DataFrame({n:get_px(n,'1/1/2019','4/1/2020') for n in names})

绘制每只股票的累计收益

import matplotlib.pyplot as plt
px = px.asfreq('B').fillna(method='pad')
rets = px.pct_change()
((1+rets).cumprod()-1).plot()
plt.show()

在这里插入图片描述
对于投资组合的构建,我们要计算特定回顾期的动量,然后按降序排列并标准化

def calc_mon(price,lookback,lag):
    mom_ret = price.shift(lag).pct_change(lookback)
    ranks = mom_ret.rank(axis=1,ascending=False)
    demeaned = ranks-ranks.mean(axis=1)
    return demeaned/demeaned.std(axis=1)

利用这个变换函数,我们再编写一个对策进行事后检验的函数:通过指定回顾期和持有期(买卖之间的日期)计算投资组合整体的夏普比率

compound = lambda x : (1+x).prod()-1
daily_sr = lambda x : x.mean()/x.std()

def strat_sr(prices,lb,hold):
    # 计算投资组合权重
    freq = '%dB' % hold
    port = calc_mon(prices,lb,lag=1)
    daily_rets = prices.pct_change()
    
    # 计算投资组合收益
    port = port.shift(1).resample(freq).first()
    returns = daily_rets.resample(freq).compound()
    ## 报错了不知怎么解决
    port_rets = (port * returns).sum(axis=1)
    return daily_sr(port_rets)*np.sqrt(252/hold)
  • 2
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值