写在前面
我博士毕业之后去了公司工作,但等安稳下来经常会想,既然接受8年的科学研究培养,如果就此搁下科研未免太对不起自己过去的付出。自那开始,我有空就开始琢磨继续独立做点什么研究。首先,我排除了CV和NLP,其一个是没显卡;其二,这些领域实在是太卷了,神仙的战场凡人还是不要参与了。
经过一段时间的摸索,我决定选定时间序列预测作为业余的研究方向。相比之下,时序的研究算是不冷不热,对显卡需求没那么高。更重要的是,我对量化投资一直非常有兴趣,数字货币、股票、外汇这些都有参与。但我从没赚过钱,事实上读书期间我陆续赔掉了2万多块钱,不过好在我的热情一直没有消减。我想有兴趣做驱动,应该能在时序的路上能走的更远一些。过去我一直是手动交易赔钱,我未来期望至少也要让模型替我亏钱,而我则能在赔钱过程中收获技能的成长和乐趣。
最后,如你所见,在时序领域我也是新人。我期望通过记录自己的学习过程,包括:时序论文阅读分享、时序数据分析技巧,能让所看、所做的工作有所积淀。当然,如果我的笔记能帮助到后来者则是更好的。
So,让我们从Kaggle竞赛的数字货币可视化和基本预处理分析开始吧!
数据集导入
今天记录的是时序数据可视化和基本分析的入门篇,数据来源Kaggle竞赛(G-Research Crypto forecasting competition),包含train,supplemental_train,asset_details这三个核心数据文件。各位可到官网去了解学习,本文的代码大量来自上面。赛题要求参赛者运用机器学习专业知识来预测比特币、以太坊等14种热门加密货币的短期回报。数据集包含数百万行自2018年以来的真实市场数据,不过今天我们不做建模,只进行数据基本分析和可视化。
首先,导入train.csv数据结构,发现数据包含时间戳、货币ID、收盘、开盘价、最低、最高价、预期收益率(Target)等特征。其中预期收益率Target的计算,我们在之后的博客中详细讲。
代码语言:javascript复制import pandas as pd
df_train = pd.read_csv('量化数据/train.csv')
df_train.head()
我们继续导入asset_details.csv数据集,查看都有哪些数字货币对。实际总共包含14种货币,这里只显示出了前五种,可以看到asset_details中中间有一列Weight特征,这个特征表示每种货币的在市场中的权重,之后我们计算Target时,还会用到该特征。
代码语言:javascript复制asset_details = pd.read_csv('量化数据/asset_details.csv')
asset_details
蜡烛图
蜡烛图表是一种以开盘价、最高价、最低价和收盘价为基础的市场数据聚合形式。我们可以通过常用的蜡烛图表来可视化这些数据,并对盘中数值进行技术分析。我们使用 plotly 库可视化比特币价格数据的一部分,绘图的底部显示一个范围滑块,我们可以使用它来放大绘图。
以比特币(Asset_ID=1)的数据为例,我们选取最近的200条数据
代码语言:javascript复制btc = crypto_df[crypto_df["Asset_ID"]==1].set_index("timestamp") # Asset_ID = 1 for Bitcoin
btc_mini = btc.iloc[-200:] # Select recent data rows
使用plotly 库函数,绘制比特币的蜡烛图
代码语言:javascript复制import plotly.graph_objects as go
import cufflinks as cf
# 不加下两行,图表可能显示空白
cf.go_offline()
cf.set_config_file(offline=True, world_readable=True)
fig = go.Figure(data=[go.Candlestick(x=btc_mini.index, open=btc_mini['Open'], high=btc_mini['High'], low=btc_mini['Low'], close=btc_mini['Close'])])
fig.show()
结果如下所示,注意该图表是可交互的,通过拖动下方滑块可以放大缩小。
数据预处理
像这类时序数据,有缺失值是常见的,接下来我们以以太坊ETH为例,查看缺失值并进行填补处理。可以看到以太坊货币对的Target特征行数少于其他特征,表明有缺失值。
代码语言:javascript复制eth = crypto_df[crypto_df["Asset_ID"]==6].set_index("timestamp") # Asset_ID = 6 for Ethereum
eth.info(show_counts =True)
使用.isna()和.sum()函数可以具体统计缺失值的数量,可以看到在这个时间范围内,以太坊Target缺少340条数据。
代码语言:javascript复制eth.isna().sum()
继续进行分析,我们把时间戳转换为正常时间格式,并查看数据的时间横跨范围。
代码语言:javascript复制beg_btc = btc.index[0].astype('datetime64[s]')
end_btc = btc.index[-1].astype('datetime64[s]')
beg_eth = eth.index[0].astype('datetime64[s]')
end_eth = eth.index[-1].astype('datetime64[s]')
print('BTC data goes from ', beg_btc, 'to ', end_btc)
print('Ethereum data goes from ', beg_eth, 'to ', end_eth)
由于缺失的资产数据是直接成行的缺失,因此我们可以检查连续行之间的时间戳差异,看看是否有丢失的数据。正常情况下,数据是连续的,两行数据相减应为60,如果两行相见大于60,则说明缺行。
代码语言:javascript复制(eth.index[1:]-eth.index[:-1]).value_counts().head()
上面的结果显示数据中存在许多空白。为了使用时间序列连续,我们应该将数据预处理为没有时间间隙的格式。我们固定时间戳的开始和结尾,使用 .reindex() 方法进行前向填充,填充代码如下,填充完毕再次进行检查,现在我们发现数据变成连续的固定时间间隔了。
代码语言:javascript复制eth = eth.reindex(range(eth.index[0],eth.index[-1] 60,60),method='pad')
(eth.index[1:]-eth.index[:-1]).value_counts().head(
数据可视化分析
上面使用了蜡烛图进行可视化分析,由于收盘价非常关键,我们这里首先可视化两种资产的收盘价。
代码语言:javascript复制import matplotlib.pyplot as plt
# plot vwap time series for both chosen assets
f = plt.figure(figsize=(15,4))
# fill missing values for BTC
btc = btc.reindex(range(btc.index[0],btc.index[-1] 60,60),method='pad')
ax = f.add_subplot(121)
plt.plot(btc['Close'], label='BTC')
plt.legend()
plt.xlabel('Time')
plt.ylabel('Bitcoin')
ax2 = f.add_subplot(122)
ax2.plot(eth['Close'], color='red', label='ETH')
plt.legend()
plt.xlabel('Time')
plt.ylabel('Ethereum')
plt.tight_layout()
plt.show()
从折线图可以看出来,两种资产价格差异很大,并且历史数据的变化幅度也有差异,我们进一步检查最近两种资产的变化幅度。选取2021年6月-2021年7月的数据,并绘制图表。
代码语言:javascript复制import time
# auxiliary function, from datetime to timestamp
totimestamp = lambda s: np.int32(time.mktime(datetime.strptime(s, "%d/%m/%Y").timetuple()))
# create intervals
btc_mini_2021 = btc.loc[totimestamp('01/06/2021'):totimestamp('01/07/2021')]
eth_mini_2021 = eth.loc[totimestamp('01/06/2021'):totimestamp('01/07/2021')]
在选定的短时间间隔内,我们可以直观地看到两种资产之间的似乎有一些潜在相关性(同涨同跌)。接下来,我们要做的是定量刻画这种相关性的强弱。然而,不同的资产的价格规模不同,因此价格实际不具有可比性。我们可以通过对数函数log对收盘价进行处理,然后计算相邻时间价格差,即每个时刻的return。
这里使用了.diff()函数,该函数是在给定周期periods后,求该周期的差值,如periods=1,就是相邻数值求差。
Log return
代码语言:javascript复制# define function to compute log returns
def log_return(series, periods=1):
return np.log(series).diff(periods=periods)
接下来可视化两种资产的对数回报(相当于在这一分钟买入,下一分钟卖出),由于取了对数,数值变化幅度在基本在较小区间内,重合度很高。
代码语言:javascript复制import scipy.stats as stats
lret_btc = log_return(btc_mini_2021.Close)[1:]
lret_eth = log_return(eth_mini_2021.Close)[1:]
lret_btc.rename('lret_btc', inplace=True)
lret_eth.rename('lret_eth', inplace=True)
plt.figure(figsize=(8,4))
plt.plot(lret_btc);
plt.plot(lret_eth);
plt.show()
两种资产的相关性
那么,两种资产变化的相关性到底如何呢,现在让我们更详细地检查一下。先使用之前定义的log_return获取每个时间点的return,然后使用concat拼接,注意axis=1是左右拼,默认为0是上下拼。
代码语言:javascript复制# join two asset in single DataFrame
lret_btc_long = log_return(btc.Close)[1:]
lret_eth_long = log_return(eth.Close)[1:]
lret_btc_long.rename('lret_btc', inplace=True)
lret_eth_long.rename('lret_eth', inplace=True)
two_assets = pd.concat([lret_btc_long, lret_eth_long], axis=1)
# group consecutive rows and use .corr() for correlation between columns
corr_time = two_assets.groupby(two_assets.index//(10000*60)).corr().loc[:,"lret_btc"].loc[:,"lret_eth"]
corr_time.plot();
plt.xticks([])
plt.ylabel("Correlation")
plt.title("Correlation between BTC and ETH over time");
如果你不理解上面关于corr_time的计算,我特意在下方写了一个分步骤的小例子,看完应该差不多能懂。
这样,我们就算出了比特币和以太坊return的相关性,可以看到越到后期,两者的相关性越强,一般来说相关性系数超过0.8,已经是非常相关了。接下来通过可视化相关矩阵来检查所有资产之间的相关性。我们可以看到某些资产的成对相关性比其他资产高得多,这也许是潜在的投资机会。
代码语言:javascript复制# create dataframe with returns for all assets
all_assets_2021 = pd.DataFrame([])
for asset_id, asset_name in zip(asset_details.Asset_ID, asset_details.Asset_Name):
asset = crypto_df[crypto_df["Asset_ID"]==asset_id].set_index("timestamp")
asset = asset.loc[totimestamp('01/01/2021'):totimestamp('01/05/2021')]
asset = asset.reindex(range(asset.index[0],asset.index[-1] 60,60),method='pad')
lret = log_return(asset.Close.fillna(0))[1:]
all_assets_2021 = all_assets_2021.join(lret, rsuffix=asset_name, how="outer")
算出结果,就可以可视化的显示出来了
代码语言:javascript复制plt.imshow(all_assets_2021.corr());
plt.yticks(range(14), asset_details.Asset_Name.values);
plt.xticks(range(14), asset_details.Asset_Name.values, rotation='vertical');
plt.colorbar();
至此,我们基本走通了数据读取、缺失值处理、可视化和相关性的基本分析。限于篇幅,在下一篇我们将开始尝试建立基础模型,来进行Target的预测任务。
以上数据集和代码可在公众号,回复“代码01” 获取。除了本文的数字币数据集外,时间序列常用的electricity、exchange_rate、illness、traffic、weather数据集一并打包在里面。
参考链接: [1]https://www.kaggle.com/competitions/g-research-crypto-forecasting/overview