見出し画像

第10章:ベイジアンML-ダイナミックSR 第4節: ローリング回帰

はじめに

第9章では、共和分に基づくペアトレーディングを紹介しました。
重要な実装ステップは、相殺する位置の相対的なサイズを決定するためのヘッジ比率の推定を含みました。
ここでは、ローリングベイズ線形回帰を使用してこの比率を計算する方法について説明します。

インポートと設定

import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
from pathlib import Path

import numpy as np
import theano
import pymc3 as pm

from sklearn.preprocessing import scale

import yfinance as yf

import matplotlib.pyplot as plt
import seaborn as sns
# model_path = Path('models')
sns.set_style('whitegrid')

単純線形回帰デモ

人口データ

size = 200
true_intercept = 1
true_slope = 2

x = np.linspace(0, 1, size)
true_regression_line = true_intercept + true_slope * x
y = true_regression_line + np.random.normal(scale=.5, size=size)

x_shared = theano.shared(x)

モデル定義

with pm.Model() as linear_regression: # model specification

   # Define priors
   sd = pm.HalfCauchy('sigma', beta=10, testval=1) # unique name for each variable
   intercept = pm.Normal('intercept', 0, sd=20)
   slope = pm.Normal('slope', 0, sd=20)

   # Define likelihood
   likelihood = pm.Normal('y', mu=intercept + slope * x_shared, sd=sd, observed=y)
pm.model_to_graphviz(linear_regression)

画像1

HMC推定

with linear_regression:
   # Inference
   trace = pm.sample(draws=2500, # draw 2500 samples from posterior using NUTS sampling
                     tune=1000, 
                     cores=1) 
pm.plot_posterior(trace);

画像2

ペアトレーディングで線形回帰

prices = yf.download('GFI GLD', period='max').dropna().loc[:, 'Close']
'''
[*********************100%***********************]  2 of 2 completed
'''
returns = prices.pct_change().dropna()
prices.info()
'''
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 3962 entries, 2004-11-18 to 2020-08-14
Data columns (total 2 columns):
#   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
0   GFI     3962 non-null   float64
1   GLD     3962 non-null   float64
dtypes: float64(2)
memory usage: 92.9 KB
'''
returns.info()
'''
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 3961 entries, 2004-11-19 to 2020-08-14
Data columns (total 2 columns):
#   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
0   GFI     3961 non-null   float64
1   GLD     3961 non-null   float64
dtypes: float64(2)
memory usage: 92.8 KB
'''
prices_normed = prices.apply(scale)

時間の経過とともに価格をプロットすると、強い相関関係が示唆されます。ただし、相関関係は時間とともに変化するようです。

fig, axes= plt.subplots(figsize=(14,4), ncols=2)

prices.plot(secondary_y='GFI', ax=axes[0])
axes[0].set_title('Asset Price Series')
points = axes[1].scatter(prices.GLD,
                        prices.GFI,
                        c=np.linspace(0.1, 1, len(prices)), 
                        s=15, 
                        cmap='winter')
axes[1].set_title('Price Correlation over Time')
cbar = plt.colorbar(points, ax=axes[1])
cbar.ax.set_yticklabels([str(p.year) for p in returns[::len(returns)//10].index]);

sns.regplot(x='GLD', y='GFI', 
           data=prices, 
           scatter=False, 
           color='k',
           line_kws={'lw':1, 
                     'ls':'--'},
           ax=axes[1])
sns.despine()
fig.tight_layout();

画像3

with pm.Model() as model_reg:
   pm.glm.GLM.from_formula('GFI ~ GLD', prices)
   trace_reg = pm.sample(draws=5000,
                         tune=1000,
                         cores=1)
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(111, 
                    xlabel='GLD', 
                    ylabel='GFI',
                    title='Posterior predictive regression lines')
points = ax.scatter(prices.GLD,
                        prices.GFI,
                        c=np.linspace(0.1, 1, len(prices)), 
                        s=15, 
                        cmap='winter')

pm.plot_posterior_predictive_glm(trace_reg[100:], 
                                samples=250, 
                                label='posterior predictive regression lines',
                                lm=lambda x, 
                                sample: sample['Intercept'] + sample['GLD'] * x,
                                eval=np.linspace(prices.GLD.min(), prices.GLD.max(), 100))
cb = plt.colorbar(points)
cb.ax.set_yticklabels([str(p.year) for p in prices[::len(prices)//10].index]);
ax.legend(loc=0);

画像4

ローリング回帰

次に、時間の経過に伴う回帰係数の変化を可能にする改善されたモデルを構築します。具体的には、切片と傾きが時間のランダムウォークに従うと仮定します。

αt ~ N(α_{t-1}, σ_{α}^2)
βt ~ N(β_{t-1}, σ_{β}^2)

まず、σ_{α}とσ_{β}のハイパー優先度を定義しましょう。
このパラメーターは、回帰係数のボラティリティとして解釈できます。

model_randomwalk = pm.Model()
with model_randomwalk:
   sigma_alpha = pm.Exponential('sigma_alpha', 50.)    
   alpha = pm.GaussianRandomWalk('alpha', 
                                 sd=sigma_alpha, 
                                 shape=len(prices))
   
   sigma_beta = pm.Exponential('sigma_beta', 50.) 
   beta = pm.GaussianRandomWalk('beta', 
                                sd=sigma_beta, 
                                shape=len(prices))
with model_randomwalk:
   # Define regression
   regression = alpha + beta * prices_normed.GLD
   
   # Assume prices are normally distributed
   # Get mean from regression.
   sd = pm.HalfNormal('sd', sd=.1)
   likelihood = pm.Normal('y', 
                          mu=regression, 
                          sd=sd, 
                          observed=prices_normed.GFI)
with model_randomwalk:
   trace_rw = pm.sample(tune=2000, 
                        draws=200,
                        cores=1,
                        target_accept=.9)

結果の分析

fig, axes = plt.subplots(figsize=(15, 5), ncols=2, sharex=True)

axes[0].plot(trace_rw['alpha'].T, 'r', alpha=.05)
axes[0].set_xlabel('Time') 
axes[0].set_ylabel('Alpha')
axes[0].set_title('Intercept')
axes[0].set_xticklabels([str(p.date()) for p in prices[::len(prices)//9].index])

axes[1].plot(trace_rw['beta'].T, 'b', alpha=.05)
axes[1].set_xlabel('Time') 
axes[1].set_ylabel('Beta')
axes[1].set_title('Slope')

fig.suptitle('Changes of coefficients over time', fontsize=14)
sns.despine()
fig.tight_layout()
fig.subplots_adjust(top=.9);

画像5

事後予測プロットは、時間の経過に伴う回帰の変化をより適切に捉えていることを示しています。価格の代わりに返品を使用する必要があったことに注意してください。モデルは同じように機能しますが、視覚化はそれほど明確ではありません。

x = np.linspace(prices_normed.GLD.min(), 
                prices_normed.GLD.max())

dates = [str(p.year) for p in prices[::len(prices)//9].index]

colors = np.linspace(0.1, 1, len(prices))
colors_sc = np.linspace(0.1, 1, len(trace_rw[::10]['alpha'].T))

cmap = plt.get_cmap('winter')
fig, ax = plt.subplots(figsize=(14, 8))

for i, (alpha, beta) in enumerate(zip(trace_rw[::25]['alpha'].T, 
                                     trace_rw[::25]['beta'].T)):
   for a, b in zip(alpha[::25], beta[::25]):
       ax.plot(x,
               a + b*x,
               alpha=.01,
               lw=.5,
               c=cmap(colors_sc[i]))
       
points = ax.scatter(prices_normed.GLD,
                   prices_normed.GFI,
                   c=colors,
                   s=5, 
                   cmap=cmap)

cbar = plt.colorbar(points)
cbar.ax.set_yticklabels(dates);

ax.set_xlabel('GLD')
ax.set_ylabel('GFI')
ax.set_title('Posterior predictive regression lines')
sns.despine()
fig.tight_layout();

画像6



































この記事が気に入ったらサポートをしてみませんか?