アセットマネージャーのためのファイナンス機械学習:金融データのラベリング トレンドスキャン法 実験
トレンドスキャン法は、トリプルバリア法に必要な保有期間や利食いと損切りのバリアを定義する必要はなく、価格変動のトレンドを特定し、それが持続する可能性がある限り持続させるラベリングである。
予測したい証券価格時系列の観測値を$${\{{X_t\},t=1,\dots T}$$とする。
これに、トレンドに従いラベルをつける。
$${\displaystyle{y_t =\begin{cases}-1 & (上昇トレンド)\\0 & (トレンド無し)\\+1 & (下降トレンド)\end{cases} }}$$
トレンドは、線形モデルの推定回帰係数($${\hat{\beta_l}}$$)のt値($${\hat{t}_{\hat{\beta_l}}}$$)を使って決定する。
$${x_{t+l}=\beta_0 + \beta_l l + \varepsilon_{t+l}}$$
$${\displaystyle{\hat{t}_{\hat{\beta_l}}=\frac{\hat{\beta_l}}{\hat{\sigma}_{\hat{\beta_l}}}}}$$
$${\hat{\sigma}_{\hat{\beta_l}}}$$は$${\hat{\beta_l}}$$の標準誤差である。推定回帰係数を取るコードはスニペット5.1で実装されている。
import statsmodels.api as sm1
def tValLinR(close):
#tValue from a linear trend
x = np.ones((close.shape[0],2))
x[:,1] = np.arange(close.shape[0])
ols = sm1.OLS(close, x).fit()
return ols.tvalues[1]
しかし、ここで、$${l=0,1,\dots,L-1,L}$$のLはルックフォーワード期間であり、t値はLが変われば、変わる。よって、与えられた領域内の異なるLで試行し、将来観測されるトレンドが最も統計的に有利になる最大の$${| \hat{t}_{\hat{\beta_l}}|}$$を出すLの値を取るようにしたのがスニペット5.2である。
def getBinsFromTrend(index,close, span):
'''
Derive labels from the sign of t-value of trend line
output includes:
- t1: End time for the identified trend
- tVal: t-value associated with the estimated trend coefficient
- bin: Sign of the trend
'''
out = pd.DataFrame(index=index, columns=['t1', 'tVal', 'bin'])
hrzns = range(*span)
for idx in index:
df0 = pd.Series(dtype='float64')
iloc0 = close.index.get_loc(idx)
if iloc0+max(hrzns) > close.shape[0]:continue
for hrzn in hrzns:
dt1 = close.index[iloc0+hrzn-1]
df1 = close.loc[idx:dt1]
df0.loc[dt1]=tValLinR(df1.values)
dt1 = df0.replace([-np.inf, np.inf, np.nan], 0).abs().idxmax() #get largest t-statistics calculated over span period
out.loc[idx, ['t1', 'tVal', 'bin']] = df0.index[-1], df0[dt1], np.sign(df0[dt1])
out['t1'] = pd.to_datetime(out['t1'])
out['bin'] = pd.to_numeric(out['bin'], downcast='signed')
return out.dropna(subset=['bin'])
ガウスランダムウォークに正弦トレンドを加えることで変曲点を強制的に与えたダミーの価格系列に、トレンドスキャン法を適用し、トレンドの範囲と、t値、トレンドラベルをgetBinsFromTrendから取得する。
この実験のコードはスニペット5.3で与えらている。
df0 = pd.Series(np.random.normal(0, .1, 100)).cumsum()
df0 += np.sin(np.linspace(0, 10, df0.shape[0]))
df1 = getBinsFromTrend(df0.index, df0, [3,10,1])
トレンドをグラフ上に色分けしてプロットする。
import matplotlib.pyplot as plt
%matplotlib inline
plt.scatter(df1.index, df0.loc[df1.index].values,c=df1['bin'].values, cmap='viridis')

上昇トレンド、下降トレンドのラベルは$${-1,0,1}$$でしかないので、そのトレンドの強さは不明である。これを表しているt値を合わせてプロットしたのが次の図である。
