第8章 ml4tワークフロー 第2節: ベクトル化バックテスト
はじめに
第8章ではml4tのワークフローについて学びます。(今後第一節にもっと詳しく書きたいと思います。)
フロー解説
・データを取得(Market: OHLCVやdepth等の市場データ, Fundamental: ファンダメンタルデータ, Alternative:オルタナティブ(その他)データ)
・時間を調整する。(データによって頻度が違うので、同じ時刻でどういう値をしているのかについて見ていく。)
・ファクター&特徴量エンジニアリング
・機械学習モデル(モデルの定義、パラメーターチューニング、交差検定)
・予測(リスクファクター、価格とリターン、共分散)
以下実務
・資産選定(ルールベース、モデルベース、ベットサイズ)
・ポートフォリオ最適化(アセットアロケーション、セクター比重、リスクリターンプロファイル)
・ターゲットポートフォリオ
・注文
・執行
・ライブ
ベクトル化バックテスト(Vectorized Backtesting)とは?
ベクトル化バックテストは最も基本的な戦略の評価方法です。シグナルベクトルを目標在庫量として、投資期間のリターンから損益を計算します。
インポートと設定
import warnings
warnings.filterwarnings('ignore')
from pathlib import Path
from time import time
import datetime
import numpy as np
import pandas as pd
import pandas_datareader.data as web
from scipy.stats import spearmanr
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
import seaborn as sns
sns.set_style('whitegrid')
np.random.seed(42)
データの読み込み
DATA_DIR = Path('..', 'data')
data = pd.read_hdf('00_data/backtest.h5', 'data')
data.info()
'''
<class 'pandas.core.frame.DataFrame'>
MultiIndex: 190451 entries, ('AAL', Timestamp('2014-12-09 00:00:00')) to ('YUM', Timestamp('2017-11-30 00:00:00'))
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 predicted 74054 non-null float64
1 open 190451 non-null float64
2 high 190451 non-null float64
3 low 190451 non-null float64
4 close 190451 non-null float64
5 volume 190451 non-null float64
dtypes: float64(6)
memory usage: 10.4+ MB
'''
ここのデータセットは00_data/data_prep.pyを実行する必要があります。
S&P500 ベンチマーク
sp500 = web.DataReader('SP500', 'fred', '2014', '2018').pct_change()
sp500.info()
'''
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1044 entries, 2014-01-01 to 2018-01-01
Data columns (total 1 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 SP500 1042 non-null float64
dtypes: float64(1)
memory usage: 16.3 KB
'''
フォワードリターンの計算
daily_returns = data.open.unstack('ticker').sort_index().pct_change()
daily_returns.info()
'''
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 751 entries, 2014-12-09 to 2017-11-30
Columns: 257 entries, AAL to YUM
dtypes: float64(257)
memory usage: 1.5 MB
'''
fwd_returns = daily_returns.shift(-1)
シグナルの生成
predictions = data.predicted.unstack('ticker')
predictions.info()
'''
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 751 entries, 2014-12-09 to 2017-04-14
Columns: 257 entries, AAL to YUM
dtypes: float64(257)
memory usage: 1.5 MB
'''
N_LONG = N_SHORT = 15
long_signals = ((predictions
.where(predictions > 0)
.rank(axis=1, ascending=False) > N_LONG)
.astype(int))
short_signals = ((predictions
.where(predictions < 0)
.rank(axis=1) > N_SHORT)
.astype(int))
ポートフォリオリターンの計算
long_returns = long_signals.mul(fwd_returns).mean(axis=1)
short_returns = short_signals.mul(-fwd_returns).mean(axis=1)
strategy = long_returns.add(short_returns).to_frame('Strategy')
結果のプロット
fig, axes = plt.subplots(ncols=2, figsize=(14,5))
strategy.join(sp500).add(1).cumprod().sub(1).plot(ax=axes[0], title='Cumulative Return')
sns.distplot(strategy.dropna(), ax=axes[1], hist=False, label='Strategy')
sns.distplot(sp500, ax=axes[1], hist=False, label='SP500')
axes[1].set_title('Daily Standard Deviation')
axes[0].yaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y)))
axes[1].xaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y)))
sns.despine()
fig.tight_layout();
res = strategy.join(sp500).dropna()
この記事が気に入ったらサポートをしてみませんか?