尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
由於大多數人骨子里傾向於投機或賭博,受欲望、恐懼和貪婪所左右,因此大多數時間里股票市場都是非理性的,容易有過激的股價波動。 By 本傑明·格雷厄姆
引言
時間序列是金融量化分析中最常見的數據類型,記錄某一變量或特徵沿著時間軸而取值,比如某只股票2008-2018年日收盤價。量化分析的一個重要環節之一是基於歷史數據進行分析和挖掘,試圖 從歷史的維度探究某一事物的變化規律或發展趨勢(做預測)。目前,時間序列分析理論已經相對成熟,包括一般統計分析(如平穩性、自相關、譜分析等)、統計建模和推斷、時間序列預測(包括流行的機器學習、深度學習,如LSTM模型)和濾波控制等。在使用Python分析時間序列時,經常會碰到時間日期格式處理和轉換問題,尤其在可視化分析和分時期統計方面。本文作為時間序列分析的入門指引之一,將著重介紹如何利用Python處理日期和分時期統計分析,希望能起到拋磚引玉的作用。
先引入數據分析和畫圖常用庫,pandas、numpy、matplotlib以及中文亂碼處理,畫圖也可以使用pyecharts、seaborn、bokeh等,以下代碼均使用Jupyter notebook(python3.7)編譯。
import pandas as pd import numpy as np import matplotlib.pyplot as plt %matplotlib inline #正常顯示畫圖時出現的中文和負號 from pylab import mpl mpl.rcParams['font.sans-serif']=['SimHei'] mpl.rcParams['axes.unicode_minus']=False
1 datetime處理日期
python常用的處理時間的庫有:datetime,time,calendar。datetime庫包括了date(儲存日期:(年、月、日),time(儲存時間:(小時、分、秒和微秒),datetime同時包含了data和time,timedelta代表兩個datetime之間的差(天、秒、微秒)。
from datetime import datetime now=datetime.now() print(f'當前時間:{now}') print(f'{now.year}年{now.month}月{now.day}日') 輸出結果: 當前時間:2019-01-11 10:25:21.445490 2019年1月11日 now.strftime('%Y-%m-%d') 輸出結果: '2019-01-10' delta=datetime(2019,1,10)-datetime(2019,1,1,12,30) delta 輸出結果: datetime.timedelta(days=8, seconds=41400) from datetime import timedelta start=datetime(2018,1,1) #計算50天後是哪一天 start+timedelta(50) datetime.datetime(2018, 2, 20, 0, 0) #字符串和時間的轉化 #比如想要知道列表里兩個時間字符串之間相差多少天 datestr=['12/20/2018','12/11/2018'] new_date=[datetime.strptime(d,'%m/%d/%Y') for d in datestr] new_date[0]-new_date[1] 輸出結果: datetime.timedelta(days=9) #將datetime格式轉換為常見的年(Y)月(m)日(d)格式表示 [date.strftime('%Y-%m-%d') for date in new_date] 輸出結果: ['2018-12-20', '2018-12-11']
datetime.strptime只能根據設定的時間格式來處理指定的字符串,如果列表里(list)包含不止一種格式的字符串,如datestr=[’12/20/2018′,’12/11/2018′,’2018-10-18′],使用datetime.strptime就很難處理了。遇到這種情況可以引入第三方時間處理包dateutil,可以處理任意格式字符串。
from dateutil.parser import parse datestr=['12/20/2018','20180210','2019-01-10'] #轉換成datetime格式 new_d=[parse(d) for d in datestr] #統一為12/20/2018格式 d1=[d.strftime('%m/%d/%Y') for d in new_d] d2=[d.strftime('%Y%m%d') for d in new_d] d3=[d.strftime('%Y-%m-%d') for d in new_d] d4=[d.strftime('%y-%m-%d') for d in new_d] print(f'datetime格式:\n{new_d}') print(f'"月/日/年"格式:\n {d1}') print(f'"年月日"格式:\n{d2}') print(f'"年-月-日格式":\n{d3}') print(f'"年(後兩位)-月-日"格式:\n{d4}') 輸出結果: datetime格式: [datetime.datetime(2018, 12, 20, 0, 0), datetime.datetime(2018, 2, 10, 0, 0), datetime.datetime(2019, 1, 10, 0, 0)] "月/日/年"格式: ['12/20/2018', '02/10/2018', '01/10/2019'] "年月日"格式: ['20181220', '20180210', '20190110'] "年-月-日格式": ['2018-12-20', '2018-02-10', '2019-01-10'] "年(後兩位)-月-日"格式: ['18-12-20', '18-02-10', '19-01-10']
2 使用NumPy庫處理日期
numpy庫主要用於數組操作(線性代數分析),但在處理日期和時間數據上功能也很強大,其時間格式是datetime64
#將字符串轉換成numpy格式時間 #注意個位前補0,如1月寫成01 nd=np.datetime64('2019-01-10') nd 輸出結果: numpy.datetime64('2019-01-10') #轉化為字符串 np.datetime_as_string(nd) 輸出結果: '2019-01-10' np.datetime64('1901') 輸出結果: numpy.datetime64('1901') #轉化為datetime格式 nd.astype(datetime) 輸出結果 datetime.date(2019, 1, 10) #生成時間序列 #默認以日為間隔,算頭不算尾 np.arange('2019-01-05','2019-01-10',dtype='datetime64') 輸出結果 array(['2019-01-05', '2019-01-06', '2019-01-07', '2019-01-08', '2019-01-09'], dtype='datetime64[D]') #以月為間隔,生成2018年12個月 np.arange('2018-01-01','2019-01-01',dtype='datetime64[M]') 輸出結果: array(['2018-01', '2018-02', '2018-03', '2018-04', '2018-05', '2018-06', '2018-07', '2018-08', '2018-09', '2018-10', '2018-11', '2018-12'], dtype='datetime64[M]') #以年為間隔 np.arange('2015-01-01','2019-01-20',dtype='datetime64[Y]') 輸出結果: array(['2015', '2016', '2017', '2018'], dtype='datetime64[Y]') #以周為間隔 np.arange('2018-12-01','2018-12-20',dtype='datetime64[W]') 輸出結果: array(['2018-11-29', '2018-12-06', '2018-12-13'], dtype='datetime64[W]') #設定隨機種子(括號里的數字只是起標記作用) np.random.seed(1) #h:小時,m:分,s:秒,ms微秒 #生成分時 x=np.arange('2019-01-10T00:00:00', '2019-01-10T23:00:00',dtype='datetime64[m]') #生成標準正態分布時間序列 y=np.random.standard_normal(len(x)) #設置圖片大小 fig=plt.figure(figsize=(12,6)) #將x的np.datetime轉換為datetime.datetime plt.plot(x.astype(datetime),y) fig.autofmt_xdate() plt.title('模擬23小時內每分鐘正態分布的隨機數分布') # 將右邊 上邊的兩條邊顏色設置為空 其實就相當於抹掉這兩條邊 ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') plt.show()
3Pandas庫處理日期
Pandas庫是處理時間序列的利器,pandas有著強大的日期數據處理功能,可以按日期篩選數據、按日期顯示數據、按日期統計數據。pandas的實際類型主要分為timestamp(時間戳)、period(時期)和時間間隔(timedelta),常用的日期處理函數有:pd.to_datetime(),pd.to_period(),pd.date_range(),pd.period_range;pandas的resample函數還提供了對日期樣本的轉換,如高低頻數據轉化等。
01 時間格式處理及轉換
定義時間格式和不同格式之間相互轉換,常用函數:pd.Timestamp(),pd.Period(),pd.to_timestamp(),pd.to_datetime(),pd.to_period()。
#定義timestamp t1=pd.Timestamp('2019-01-10') t2=pd.Timestamp('2018-12-10') print(f't1= {t1}') print(f't2= {t2}') print(f't1與t2時間間隔:{(t1-t2).days}天') 輸出結果: t1= 2019-01-10 00:00:00 t2= 2018-12-10 00:00:00 t1與t2時間間隔:31天 #獲取當前時間 now=pd.datetime.now() print(now) print(now.strftime('%Y-%m-%d')) 輸出結果: 2019-01-11 11:56:49.014612 2019-01-11 #時間間隔 pd.Timedelta(days=5, minutes=50, seconds=20, milliseconds=10, microseconds=10, nanoseconds=10) 輸出結果: Timedelta('5 days 00:50:20.010010') #計算當前時間往後100天的日期 dt=now+pd.Timedelta(days=100) #只顯示年月日 dt.strftime('%Y-%m-%d') 輸出結果: '2019-04-21' #定義時期period,默認是A-DEC,代表年份,以12月作為最後一個月 p1=pd.Period('2019') p2=pd.Period('2018') print(f'p1={p1}年') print(f'p2={p2}年') print(f'p1和p2間隔{p1-p2}年') #可以直接+、-整數(代表年) print(f'十年前是{p-10}年') 輸出結果: p1=2019年 p2=2018年 p1和p2間隔1年 十年前是2009年 #通過asfreq轉換時期頻率 #以第一個月算,p1前面已賦值為2019年 p1.asfreq('M','start') 輸出結果: Period('2019-01', 'M') #以最後一個月算 p1.asfreq('M','end') 輸出結果: Period('2019-12', 'M') #財報季度 p=pd.Period('2019Q3',freq='Q-DEC') #起始月日 print(p.asfreq('D','start')) #結束月日 print(p.asfreq('D','end')) 結果輸出: 2019-07-01 2019-09-30 #時間戳和時期相互轉換 print(p1.to_timestamp(how='end')) print(p1.to_timestamp(how='start')) 輸出結果: 2019-12-31 00:00:00 2019-01-01 00:00:00 #t1前面賦值為'2019-1-10' #轉換為月時期 print(t1.to_period('M')) #轉換為日時期 print(t1.to_period('D')) print(t1.to_period('W')) 輸出結果: 2019-01 2019-01-10 2019-01-07/2019-01-13
02 生成日期序列
常用函數:pd.date_range(),生成的是DatetimeIndex格式的日期序列;pd.period_range(),生成PeriodIndex的時期日期序列。
#使用date_range生成日期序列 #如要詳細了解該函數,可以使用help(pd.date_range) #參數四選三:起始時間,結束時間,freq,periods #freq='M'月,'D'天,'W',周,'Y'年 #生成月時間序列 dm = pd.date_range('2018/01/01', freq='M', periods=12) print(f'生成月時間序列:\n{dm}') #算頭不算尾 #生成年時間序列,默認是以12月結尾,freq='Y-DEC' dy=pd.date_range('2008-01-01','2019-01-10',freq='Y') print(f'生成年時間序列:\n{dy}') #生成日時間序列 dd=pd.date_range('2018-01-01',freq='D',periods=10) print(f'生成日時間序列:\n{dd}') #生成周時間序列,默認以sunday周日作為一周最後一日 #如要改成周一作為第一天,freq='W-SAT' dw=pd.date_range('2018-01-01',freq='W',periods=10) print(f'生成周時間序列:\n{dw}') 輸出結果: 生成月時間序列: DatetimeIndex(['2018-01-31', '2018-02-28', '2018-03-31', '2018-04-30','2018-05-31', '2018-06-30', '2018-07-31', '2018-08-31', '2018-09-30', '2018-10-31', '2018-11-30', '2018-12-31'], dtype='datetime64[ns]', freq='M') 生成年時間序列: DatetimeIndex(['2008-12-31', '2009-12-31', '2010-12-31', '2011-12-31','2012-12-31', '2013-12-31', '2014-12-31', '2015-12-31', '2016-12-31', '2017-12-31', '2018-12-31'], dtype='datetime64[ns]', freq='A-DEC') 生成日時間序列: DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04','2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08', '2018-01-09', '2018-01-10'], dtype='datetime64[ns]', freq='D') 生成周時間序列: DatetimeIndex(['2018-01-07', '2018-01-14', '2018-01-21', '2018-01-28','2018-02-04', '2018-02-11', '2018-02-18', '2018-02-25', '2018-03-04', '2018-03-11'], dtype='datetime64[ns]', freq='W-SUN') #使用period_range生成日期序列 #參數四選三:起始時間,結束時間,freq,periods #freq='M'月,'D'天,'W',周,'Y'年 #生成月時期序列 dpm = pd.period_range('2019/01/01', freq='M', periods=12) print(f'生成月時間序列:\n{dpm}') #生成年時期序列,默認是以12月結尾,freq='Y-DEC' dpy=pd.period_range('2008-01-01','2019-01-10',freq='Y') print(f'生成年時間序列:\n{dpy}') #生成日時期序列 dpd=pd.period_range('2018-01-01',freq='D',periods=10) print(f'生成日時間序列:\n{dpd}') #生成周時期序列,默認以sunday周日作為一周最後一日 #如要改成周一作為第一天,freq='W-SAT' dpw=pd.period_range('2018-01-01',freq='W-SUN',periods=10) print(f'生成周時間序列:\n{dpw}') 輸出結果: 生成月時間序列: PeriodIndex(['2019-01', '2019-02', '2019-03', '2019-04', '2019-05', '2019-06', '2019-07', '2019-08', '2019-09', '2019-10', '2019-11', '2019-12'], dtype='period[M]', freq='M') 生成年時間序列: PeriodIndex(['2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018', '2019'], dtype='period[A-DEC]', freq='A-DEC') 生成日時間序列: PeriodIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04','2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08', '2018-01-09', '2018-01-10'], dtype='period[D]', freq='D') 生成周時間序列: PeriodIndex(['2018-01-01/2018-01-07', '2018-01-08/2018-01-14','2018-01-15/2018-01-21', '2018-01-22/2018-01-28','2018-01-29/2018-02-04', '2018-02-05/2018-02-11','2018-02-12/2018-02-18', '2018-02-19/2018-02-25','2018-02-26/2018-03-04', '2018-03-05/2018-03-11'], dtype='period[W-SUN]', freq='W-SUN') #畫以時間為x軸的圖,pandas的DataFrame自動將index列作為x軸 np.random.seed(2) #生成日期序列 x=pd.date_range('2018/01/01','2019/12/31', freq='d') #x=pd.period_range('2018/01/01','2019/12/31', freq='d') #標準正態分布時間序列 y=np.random.standard_normal(len(x)) #將二者轉換為pandas的數據格式 df=pd.DataFrame(y,columns=['標準正態分布'],index=x) df.plot(figsize=(12,6)) plt.title('模擬標準正態分布隨機數') ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') plt.show()
03 時間樣本頻率轉換
時間序列樣本轉換主要分兩種:即高頻數據向低頻數據轉換;低頻數據向高頻數據轉換。用場景:行情交易數據一般是高頻,基本面一般是月度、季度、年度等低頻數據,量化分析的時候,常常要將基本面數據和行情交易數據結合起來進行統計回歸分析,這時候就要用到樣本數據頻率的轉換了。主要函數:df.resample(),df代表pandas的DataFrame格式數據,resample方法的參數參數中,freq表示重采樣頻率,例如‘M’、‘5min’,Second(15);用於產生聚合值的函數名或數組函數,例如‘mean’、‘ohlc’、np.max等,默認是‘mean’,其他常用的有:‘first’、‘last’、‘median’、‘max’、‘min’,xis=0默認是縱軸,橫軸設置axis=1。
高頻數據向低頻數據轉化
#導入2019年1月10日上證指數的分時數據 #數據來源:同花順 df=pd.read_excel('Table.xlsx') df.head() #設置時間作為索引 df=df.set_index(df['時間']) #畫圖,pandas數據表自動將索引作為x軸 df['成交'].plot(figsize=(16,6),label='成交價格') plt.title('上證綜指2019年1月10日分時圖',fontsize=15) ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') plt.show()
#由於時間索引列只有時分秒,是object格式,加入年月日再進行樣本變換 d=datetime(2019,1,10) dt=pd.to_datetime([datetime.combine(d,t) for t in df['時間'].values]) #構建新的數據框 ts=pd.DataFrame(df['成交'].values,columns=['成交'],index=dt) ts.head() #5分鐘樣本,取最後一個數,標誌默認左側, #所以第一個區間[9:20-9:25],顯示9:20 ts.resample('5min',closed='right').last().head() #5分鐘採用,取最後一個數, ts.resample('5min',closed='right',label='right').last().head() #將其轉換為每小時樣本,默認closed='left',label='left' #可以使用均值mean(),或取第一個數first(),或最後一個last() ts.resample('H').mean()
低頻數據向高頻數據轉換
#frq='W'代表周 df=pd.DataFrame(np.random.randn(5,4), index=pd.date_range('1/4/2019',periods=5,freq='W'), columns=['GZ','BJ','SH','SZ']) df #將上述樣本轉換為日序列,缺失值使用前值補上 #如使用後值則用bfill() df_daily=df.resample('D').ffill() df_daily.head() #根據period來重采樣 df1=pd.DataFrame(np.random.randn(2,4), index=pd.period_range('1-2017','12-2018',freq='A'), columns=['GZ','BJ','SH','SZ']) df1.head() #Q-DEC: Quarterly, decenber df1.resample('Q-DEC').ffill()
04 日期數據分組統計
#注意pd是pandas的簡稱,np是numpy的簡稱,使用之前先import date=pd.date_range('1/1/2018', periods=500, freq='D') ts=pd.Series(np.random.standard_normal(500),index=date) ts.head() 輸出結果: 2018-01-01 0.681604 2018-01-02 1.006493 2018-01-03 -0.942035 2018-01-04 -0.733425 2018-01-05 -1.035250 Freq: D, dtype: float64 #按月顯示,不統計 #按年是A,季度是Q tsp=ts.to_period('D') tsp.head() 輸出結果 2018-01-01 0.681604 2018-01-02 1.006493 2018-01-03 -0.942035 2018-01-04 -0.733425 2018-01-05 -1.035250 Freq: D, dtype: float64 #根據不同時期顯示索引值 #按季度頻率Q,月度M,年度A tsp.index.asfreq('Q') 輸出結果: PeriodIndex(['2018Q1', '2018Q1', '2018Q1', '2018Q1', '2018Q1', '2018Q1','2018Q1', '2018Q1', '2018Q1', '2018Q1',...'2019Q2', '2019Q2', '2019Q2', '2019Q2', '2019Q2', '2019Q2', '2019Q2', '2019Q2', '2019Q2', '2019Q2'],dtype='period[Q-DEC]', length=500, freq='Q-DEC') #按工作日統計 tsp.index.asfreq('B') 輸出結果: PeriodIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04','2018-01-05', '2018-01-08', '2018-01-08', '2018-01-08','2018-01-09', '2018-01-10', ... '2019-05-06', '2019-05-07', '2019-05-08', '2019-05-09', '2019-05-10', '2019-05-13', '2019-05-13', '2019-05-13', '2019-05-14', '2019-05-15'], dtype='period[B]', length=500, freq='B') #按周進行顯示,求和匯總 #月:M,年:A,季度:Q #sum()、mean(),first(),last() print(ts.resample('W').sum().head()) 輸出結果: 2018-01-07 -0.532703 2018-01-14 -3.905250 2018-01-21 -0.037820 2018-01-28 -4.010447 2018-02-04 -2.165019 Freq: W-SUN, dtype: float64 print(ts.resample('AS').sum()) # "AS"是每年第一天為開始日期, "A是每年最後一天 輸出結果: 2018-01-01 0.434155 2019-01-01 0.171082 Freq: AS-JAN, dtype: float64 # 按年統計並顯示 print(ts.resample('AS').sum().to_period('A')) 輸出結果: 2018 0.434155 2019 0.171082 Freq: A-DEC, dtype: float64 # 按季度統計並顯示 print(ts.resample('Q').sum().to_period('Q')) 輸出結果: 2018Q1 -23.716613 2018Q2 -3.304391 2018Q3 15.039522 2018Q4 12.415637 2019Q1 1.153160 2019Q2 -0.982078 Freq: Q-DEC, dtype: float64 根據groupby進行resampling #按月進行匯總求平均值 ts.groupby(lambda x:x.year).mean() 輸出結果: 2018 0.001189 2019 0.001267 dtype: float64 #按周進行匯總求平均值 ts.groupby(lambda x:x.weekday).mean() 輸出結果: 0 -0.021922 1 -0.005215 2 -0.002857 3 -0.037211 4 -0.133802 5 0.011450 6 0.198504 dtype: float64
關於Python金融量化
專注於分享Python在金融量化領域的應用,包括金融數據分析與挖掘、金融建模與量化交易等。公眾號本身具有AI自動回復功能,關注並回復python入門、python進階、python高階,免費領取python金融量化學習資料,回復「指南」可查看詳情。