為替レートは予測できるのか
為替レートを動かすもの、為替レートが動かすもの
「米国金利が上がればドルが買われる」「円が安くなれば、外国人が日本に旅行に来る」「輸出が増えれば円が買われる」。このような説明をよく耳にします。
それでは、物価、金利、株価、貿易、景気などの動きは、実際の為替レートの動きとどの程度整合的なのでしょうか。
変動相場制への移行以降、購買力平価、金利平価、マネタリーモデルなど、さまざまな為替モデルが提案されてきました。金利や株価と為替レートの関係を明らかにする実証モデル、物価から適正レートを算出する理論モデル、為替レートに含まれる情報を将来の予測に利用するモデルなどです。 本稿では、これらのモデルを実際のデータを使って一つずつ簡単に確かめます。
まず、ドル円レートと、金利、株価、物価、輸出入、GDPなどの動きを比較します。つぎに、回帰モデルや時系列モデルを使い、過去に観察された関係から将来を予測できるかを調べます。ただし、過去のデータによく当てはまることと、将来を予測できることは同じではありません。予測誤差が小さいことと、その予測が実際の取引や判断に役立つことも同じではありません。為替レートには、予測がうまくいくように見える時期もあれば、まったく機能しない時期もあります。その違いを、どのような視点から整理すればよいのでしょうか。
本稿の目的は、為替レートと物価、金利、株価、貿易、景気などの変数がどのように関係しているかを整理し、状況に応じてモデルの有効性を判断するための視点を身につけることです。
1 まずドル円レートを見てみよう
最初に、長期のドル円レートを図示し、その動きの特徴を確認します。
1.1 使用データ
ドル円レートには、FREDから取得した次の2系列を使用します。
- DEXJPUS`:日次ドル円レート
- EXJPUS:月次平均ドル円レート
どちらも、1米ドルに対する日本円の金額で表示されています。
したがって、ドル円レートの上昇は円安・ドル高、低下は円高・ドル安を意味します。
月次平均データのEXJPUSは、長期的な推移を見やすくするために使用します。日次データのDEXJPUSは、日々の為替レートの変化と変動性を計算するために使用します。
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from pandas_datareader import data as web
from statsmodels.tsa.stattools import adfuller
start = "1971-01-01"
series_ids = [
"DEXJPUS", "EXJPUS", # 為替レート
"DGS10", "IRLTLT01JPM156N",# 金利
"NASDAQ100", "NIKKEI225",# 株価
"INDPRO", "JPNPRMNTO01GYSAM",# 景気
"CPIAUCNS", "JPNCPIALLMINMEI",# 物価
"XTEXVA01JPM664S", "XTIMVA01JPM664S",# 輸出入
"GDPC1", "JPNRGDPEXP"# GDP
]
raw_data = web.DataReader(
series_ids, "fred", start
)
# 既存コードとの互換性を保つ
fred = {
series_id: raw_data[[series_id]].dropna()
for series_id in series_ids
}
fx = fred["DEXJPUS"].copy()
fx.columns = ["USDJPY"]
fx_monthly = fred["EXJPUS"].copy()
fx_monthly.columns = ["USDJPY"]
1.2 ドル円レートの長期推移
最初に、日次ドル円レートをそのまま表示します。
ax = fx["USDJPY"].plot(figsize=(4, 2.5),title="USD/JPY Exchange Rate"
)
ax.set_xlabel("Date")
ax.set_ylabel("Japanese Yen per U.S. Dollar")
plt.tight_layout()
plt.show()
グラフが上昇している期間は円安・ドル高、下降している期間は円高・ドル安です。長期のグラフを見ると、円高または円安が何年も続く局面があり、為替レートには長期的な方向性が存在するように見えます。しかし、長期的な上昇や下降が観察できることと、その方向を事前に予測できることは別の問題です。
事後的に見れば明確なトレンドであっても、そのトレンドがいつ始まり、いつ終わるかを事前に判断することはできません。
1.3 ドル円レートの対数値
つぎに、ドル円レートの自然対数を表示します。
log_fx = np.log(fx["USDJPY"]
)
ax = log_fx.plot(figsize=(4, 2.5),title="Log USD/JPY Exchange Rate")
ax.set_xlabel("Date")
ax.set_ylabel("Log Exchange Rate")
plt.show()
対数を取る目的は、単に数値の桁を小さくすることではありません。為替レートの変化を、円単位の差ではなく、割合の変化として比較しやすくすることにあります。例えば、ドル円レートが100円から110円に上昇する場合と、150円から160円に上昇する場合を考えます。どちらも10円の上昇ですが、変化率は異なります。対数を使うと、同じ割合の変化をほぼ同じ大きさとして表すことができます。また、対数値の差を取ることにより、為替レートの変化率に近い値が得られます。
1.4 日次対数価格差
時点 $t$ のドル円レートを $S_t$ とします。日次対数価格差は、つぎのように定義されます。
$
r_t\log S_t-\log S_{t-1}
$
これは、前日から当日までのドル円レートの変化を表します。
変化が小さい場合、対数価格差は通常の変化率に近似します。 $r_t\approx (S_t-S_{t-1})/(S_{t-1})$。日次データから対数価格差を計算します。
log_fx = np.log(fx["USDJPY"])
log_change = log_fx.diff()
log_changeが正であれば、ドル円レートは円安・ドル高方向に変化しています。
反対に、log_changeが負であれば、円高・ドル安方向に変化しています。
ドル円レートの水準には長期間の上昇や下降が見られます.日次対数価格差の多くはゼロの周辺に分布しています。
つまり、為替レートの水準と、日々の為替変化は分けて考える必要があるのです。
1.5 日次対数価格差の2乗
日次対数価格差は、円安方向なら正、円高方向なら負になります。
変動の方向ではなく、変動の大きさを測るには、日次対数価格差を2乗します。
$
r_t^2
$
日次対数価格差の期待値をほぼゼロとみなせば、日次の分散はつぎのように表されます。
$
\sigma_d^2
=E[r_t^2]
$
このとき、観察された$r_t^2$は、その日の実現分散と考えることができます。
実際の計算では、日次対数価格差を2乗します。
squared_log_change = log_change ** 2
2乗することで、円高方向と円安方向の違いは取り除かれます。そして、大きな価格変化が発生した日は、方向にかかわらず大きな値になります。
1.6 分散を年率換算する
データを比較するためには、基準をそろえる必要があります。年間の取引日数を252日とすると、1年間の対数価格差は、252日分の日次対数価格差の和です。
$
R_{252}
=\sum_{i=1}^{252}r_i
$
日次対数価格差が独立で、同じ分散$\sigma_d^2$を持つと仮定します。
独立な確率変数については分散を足すことができるため、年間の分散はつぎのようになります。
$
\operatorname{Var}(R_{252})
=\sum_{i=1}^{252}\operatorname{Var}(r_i)
$
各日の日次分散が同じであれば、
$
\operatorname{Var}(R_{252})
=252\sigma_d^2
$
です。
したがって、年率分散は日次分散の252倍になります。
$
\sigma_{\mathrm{annual}}^2
=252\sigma_d^2
$
ボラティリティは分散の平方根なので、年率ボラティリティは、
$
\sigma_{\mathrm{annual}}
=\sqrt{252}\sigma_d
$
となります。
日次分散を252日分足し、その年率分散の平方根を取った結果として、$\sqrt{252}$が現れます。
1.7 1日の実現変動を年率換算する
1日の実現分散を$r_t^2$とすると、それを252日分足した年率分散は、
$
252r_t^2
$
です。
その平方根を取ると、
$
\sqrt{252r_t^2}
=\sqrt{252}|r_t|
$
となります。
これは、その日に観察された変動の大きさが1年間続くと仮定した場合の、年率換算された変動です。
annualized_daily_movement = np.sqrt(252 * log_change ** 2)
この値は、年間の実際の収益率ではありません。
また、将来1年間のボラティリティを予測した値でもありません。
その日に観察された実現分散を、年間と同じ尺度に換算した値です。
1.8 20日ローリング・ボラティリティ
1日だけの$r_t^2$は、偶然の価格変化に大きく左右されます。
そこで、直近20日間の日つぎ対数価格差の2乗を平均し、日つぎ分散を推定します。
\widehat{\sigma}_{d,t}^2=\frac{1}{20}\sum_{j=0}^{19}r_{t-j}^2
これは、20個の日次実現分散の平均です。
この日次分散が252日間続くと仮定すれば、年率分散は、
\widehat{\sigma}_{\mathrm{annual},t}^2
=252
\left(
\frac{1}{20}
\sum_{j=0}^{19}
r_{t-j}^2
\right)
となります。
最後に平方根を取ると、20日ローリング・ボラティリティが得られます。
\widehat{\sigma}_{\mathrm{annual},t}
=\sqrt{
252
\left(
\frac{1}{20}
\sum_{j=0}^{19}
r_{t-j}^2
\right)
}
Pythonではつぎのように計算します。
annualized_volatility_20 = np.sqrt(252* log_change.pow(2).rolling(20).mean())
ここでは、日次対数価格差の標本標準偏差を計算しているのではありません。
日次対数価格差の平方を実現分散とみなし、その20日平均を日次分散の推定値として使用しています。
1.9 1日の実現変動と20日ローリング・ボラティリティ
1日の実現変動と20日ローリング・ボラティリティを、同じグラフに表示します。
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(
annualized_daily_movement.index,
annualized_daily_movement * 100,
linewidth=0.5,
alpha=0.5,
label="Daily annualized movement"
)
ax.plot(
annualized_volatility_20.index,
annualized_volatility_20 * 100,
linewidth=1.2,
label="20-day annualized volatility"
)
ax.set_xlabel("Date")
ax.set_ylabel("Annualized volatility (%)")
ax.set_title(
"USD/JPY Daily Movement and "
"20-Day Rolling Volatility"
)
ax.legend()
plt.show()
2本の線は、どちらも日次対数価格差の2乗を出発点としています。
1日の実現変動は、
$
\sqrt{252r_t^2}
$
です。
20日ローリング・ボラティリティは、
$
\sqrt{
252
\left(
\frac{1}{20}
\sum_{j=0}^{19}
r_{t-j}^2
\right)
}
$
です。前者は、その日に発生した変動の大きさを示します。
後者は、直近20日間に観察された実現分散を平均し、最近の変動性の水準を示します。
両者は同じ計算単位で表されているため、同じ縦軸で比較できます。
グラフを見ると、大きな日次変動が一度だけ発生する場合だけでなく、大きな変動が連続して発生する時期があることが分かります。
大きな変動が続けば、20日ローリング・ボラティリティも上昇します。反対に、小さな変動が続けば、20日ローリング・ボラティリティは徐々に低下します。
このように、為替レートの変動性には、高い状態や低い状態が一定期間続く傾向が見られます。変動性が急に上昇する現象をボラティリティクラスタリングといいます。
2 金利・株価・景気との関係を調べる
為替レートは、金利、株価、景気など、さまざまな経済変数と関係していると考えられています。しかし、その関係は常に同じとは限りません。ここでは、ドル円レートと各経済変数を並べ、長期的な動きを確認します。
2.1 使用データ
つぎのデータをFREDから取得します。
-
DEXJPUS:日次ドル円レート -
DGS10:米国10年国債金利 -
IRLTLT01JPM156N:日本10年国債金利 -
NASDAQ100:NASDAQ100指数 -
NIKKEI225:日経平均株価 -
INDPRO:米国鉱工業生産指数 -
JPNPRMNTO01GYSAM:日本の製造業生産前年比
DGS10は日次、IRLTLT01JPM156Nは月次の金利データです。NASDAQ100と日経平均株価は日次の終値です。
このコードにより、各系列をDataReaderで取得できることと、実際に取得された期間を確認できます。
2.2 日米金利差とドル円レート
「米国金利が日本金利より高くなればドルが買われる」という説明を確かめるため、日米の10年国債金利差とドル円レートを比較します。
日本の10年国債金利が月次データであるため、米国金利とドル円レートも月次に変換します。金利はパーセント表示のまま使用し、ドル円レートには対数を取ります。
fx_monthly = (fred["DEXJPUS"].resample("ME").last())
us_rate = (fred["DGS10"].resample("ME").mean())
jp_rate = fred["IRLTLT01JPM156N"].copy()
jp_rate.index = (jp_rate.index.to_period("M").to_timestamp("M"))
interest_data = pd.concat(
[np.log(fx_monthly),us_rate,jp_rate],axis=1
).dropna()
interest_data.columns = ["log_fx","us_rate","jp_rate"]
interest_data["rate_diff"] = (interest_data["us_rate"] - interest_data["jp_rate"])
fig, ax1 = plt.subplots(figsize=(4, 2.5))
ax2 = ax1.twinx()
ax1.plot(interest_data.index,interest_data["rate_diff"],label="US-Japan rate differential")
ax2.plot(interest_data.index,interest_data["log_fx"],label="Log USD/JPY", color="red")
ax1.set_xlabel("Date")
ax1.set_ylabel("Interest-rate differential (%)")
ax2.set_ylabel("Log USD/JPY")
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2,labels1 + labels2,fontsize=6)
plt.show()
日米金利差の拡大と円安が同時に進む期間がある一方、両者が同じように動かない期間もあります。金利差だけでドル円レートの長期的な動きを説明することは難しそうです。
2.3 株価とドル円レート
つぎに、ドル円レート、NASDAQ100、日経平均株価を比較します。
為替レートと株価指数は水準が大きく異なるため、それぞれの開始時点を基準として対数変化を計算します。
stock_data = pd.concat([fred["DEXJPUS"],fred["NASDAQ100"],fred["NIKKEI225"]],axis=1).dropna()
stock_data.columns = ["fx", "nasdaq", "nikkei"]
stock_log = np.log( stock_data/ stock_data.iloc[0])
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(stock_log.index,stock_log["fx"],label="USD/JPY")
ax.plot(stock_log.index,stock_log["nasdaq"],label="NASDAQ100")
ax.plot(stock_log.index,stock_log["nikkei"],label="Nikkei 225")
ax.set_xlabel("Date")
ax.set_ylabel("Cumulative log change")
ax.legend(fontsize=6)
plt.show()
株価とドル円レートが同じ方向に動く時期もあれば、異なる方向に動く時期もあります。また、米国株と日本株でも長期的な上昇率は異なります。
グラフが似ていることだけから、一方が他方を動かしていると判断することはできません。
2.4 景気とドル円レート
景気の動きを表す指標として、米国の鉱工業生産指数と日本の製造業生産を使用します。米国の鉱工業生産指数は月次の指数であり、日本の系列は前年同月からの変化率です。
比較の尺度を合わせるため、米国鉱工業生産指数とドル円レートについて、12か月の対数価格差を計算します。
us_production = fred["INDPRO"].copy()
jp_production_growth = fred["JPNPRMNTO01GYSAM"].copy()
fx_monthly = (fred["DEXJPUS"].resample("ME").last())
# すべてのインデックスを年月に統一する
fx_monthly.index = fx_monthly.index.to_period("M")
us_production.index = us_production.index.to_period("M")
jp_production_growth.index = (jp_production_growth.index.to_period("M"))
business_data = pd.concat(
[fx_monthly,us_production,jp_production_growth], axis=1
)
business_data.columns = ["fx","us_production","jp_production_growth"]
# ドル円レートの前年同月比対数変化率
business_data["fx_growth"] = (
np.log(business_data["fx"]).diff(12)* 100
)
# 米国鉱工業生産の前年同月比対数変化率
business_data["us_production_growth"] = (
np.log(business_data["us_production"]).diff(12)* 100
)
business_data = business_data[
["fx_growth","us_production_growth","jp_production_growth"]].dropna()
# PeriodIndexを描画用の日時に戻す
business_data.index = (business_data.index.to_timestamp())
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(business_data.index,business_data["fx_growth"],label="USD/JPY")
ax.plot(business_data.index,business_data["us_production_growth"],label="US production")
ax.plot(business_data.index,business_data["jp_production_growth"],label="Japan production")
ax.axhline(0,linewidth=0.5)
ax.set_xlabel("Date")
ax.set_ylabel("Annual change (%)")
ax.legend(fontsize=6)
plt.tight_layout()
plt.show()
景気の相対的な強さとドル円レートが整合的に動く期間があります。しかし、その関係は安定していません。為替レートは、金利、株価、景気のいずれか一つだけで決まるものではないことが分かります。
また、同じ時期に動いていることは、将来の為替レートを予測できることを意味しません。つぎのセッションでは、物価、輸出入、GDPから為替レートを考えます。
3 物価・輸出入・GDPから為替レートを考える
ここまでは、為替レートと金利、株価、景気指標の動きを比較しました。つぎに、物価、輸出入、GDPから為替レートを考えます。
3.1 使用データ
セッション2で取得したドル円レートを再利用し、つぎの系列だけを追加します。
-
CPIAUCNS:米国消費者物価指数 -
JPNCPIALLMINMEI:日本消費者物価指数 -
XTEXVA01JPM664S:日本の輸出額 -
XTIMVA01JPM664S:日本の輸入額 -
GDPC1:米国実質GDP -
JPNRGDPEXP:日本実質GDP
輸出額と輸入額には、季節調整済みの円建て系列を使用します。
3.2 物価と購買力平価
購買力平価では、同じ商品は為替換算後には同じ価格になると考えます。ドル円レートを円/ドルで表すと、相対的購買力平価はつぎのようになります。
$
S_t^{PPP}
=S_0
\frac{P_{JP,t}/P_{JP,0}}
{P_{US,t}/P_{US,0}}
$
$S_0$は基準時点のドル円レート、$P_{JP,t}$と$P_{US,t}$は日本と米国の物価指数です。
fx_price = (
fred["DEXJPUS"].resample("ME").mean()
)
us_cpi = fred["CPIAUCNS"].copy()
jp_cpi = fred["JPNCPIALLMINMEI"].copy()
fx_price.index = fx_price.index.to_period("M")
us_cpi.index = us_cpi.index.to_period("M")
jp_cpi.index = jp_cpi.index.to_period("M")
price_data = pd.concat([fx_price, us_cpi, jp_cpi],axis=1).dropna()
price_data.columns = ["fx", "us_cpi","jp_cpi"]
base = price_data.iloc[0]
price_data["ppp"] = (
base["fx"]* (price_data["jp_cpi"] / base["jp_cpi"])/ (price_data["us_cpi"] / base["us_cpi"])
)
price_data["log_fx"] = np.log( price_data["fx"])
price_data["log_ppp"] = np.log(price_data["ppp"])
price_data.index = ( price_data.index.to_timestamp())
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(price_data.index,price_data["log_fx"],label="Log USD/JPY")
ax.plot(price_data.index,price_data["log_ppp"],label="Relative PPP")
ax.set_xlabel("Date")
ax.set_ylabel("Log exchange rate")
ax.legend(fontsize=6)
plt.tight_layout()
plt.show()
購買力平価は、基準時点からの日米物価の相対的な変化を為替レートに換算したものです。実際のドル円レートは、購買力平価から長期間離れることがあります。
また、基準時点を変えれば購買力平価の水準も変わります。したがって、購買力平価は一意に決まる適正レートではなく、長期的な比較の基準と考える必要があります。
3.3 輸出入とドル円レート
輸出が増えれば外国から円への需要が増え、輸入が増えれば円から外国通貨への需要が増えると考えられます。
ドル円レート、輸出額、輸入額について、前年同月からの対数変化率を計算します。
fx_trade = (fred["DEXJPUS"].resample("ME").mean())
exports = fred["XTEXVA01JPM664S"].copy()
imports = fred["XTIMVA01JPM664S"].copy()
fx_trade.index = fx_trade.index.to_period("M")
exports.index = exports.index.to_period("M")
imports.index = imports.index.to_period("M")
trade_data = pd.concat([fx_trade, exports, imports],axis=1)
trade_data.columns = ["fx","exports","imports"]
trade_data["fx_growth"] = (
np.log(trade_data["fx"]).diff(12)* 100
)
trade_data["export_growth"] = (
np.log(trade_data["exports"]).diff(12)* 100
)
trade_data["import_growth"] = (
np.log(trade_data["imports"]).diff(12)* 100
)
trade_data = trade_data[
["fx_growth","export_growth","import_growth"]
].dropna()
trade_data.index = (trade_data.index.to_timestamp())
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(trade_data.index,trade_data["fx_growth"],label="USD/JPY")
ax.plot(trade_data.index,trade_data["export_growth"],label="Exports")
ax.plot(trade_data.index,trade_data["import_growth"],label="Imports")
ax.axhline( 0,linewidth=0.5)
ax.set_xlabel("Date")
ax.set_ylabel("Annual change (%)")
ax.legend(fontsize=6)
plt.tight_layout()
plt.show()
輸出入とドル円レートが同じ時期に大きく変化することがあります。しかし、その関係は一定ではありません。
また、ここで使用しているのは輸出入の数量ではなく円建ての金額です。輸出入額の変化には、数量だけでなく価格や為替レートの変化も含まれます。
3.4 GDPとドル円レート
最後に、日米の実質GDP成長率の差とドル円レートを比較します。
GDPは四半期データであるため、ドル円レートも四半期平均に変換します。
fx_quarterly = (fred["DEXJPUS"].resample("QE").mean())
us_gdp = fred["GDPC1"].copy()
jp_gdp = fred["JPNRGDPEXP"].copy()
fx_quarterly.index = (fx_quarterly.index.to_period("Q"))
us_gdp.index = (us_gdp.index.to_period("Q"))
jp_gdp.index = (jp_gdp.index.to_period("Q"))
gdp_data = pd.concat([fx_quarterly, us_gdp, jp_gdp],axis=1)
gdp_data.columns = ["fx","us_gdp","jp_gdp"]
gdp_data["fx_growth"] = (
np.log(gdp_data["fx"]).diff(4)* 100
)
gdp_data["us_growth"] = (
np.log(gdp_data["us_gdp"]).diff(4)* 100
)
gdp_data["jp_growth"] = (
np.log(gdp_data["jp_gdp"]).diff(4)* 100
)
gdp_data["growth_diff"] = (gdp_data["us_growth"]- gdp_data["jp_growth"])
gdp_data = gdp_data[["fx_growth", "growth_diff"]].dropna()
gdp_data.index = ( gdp_data.index.to_timestamp())
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(gdp_data.index,gdp_data["fx_growth"],label="USD/JPY")
ax.plot(gdp_data.index,gdp_data["growth_diff"],label="US-Japan GDP growth")
ax.axhline( 0, linewidth=0.5)
ax.set_xlabel("Date")
ax.set_ylabel("Annual change (%)")
ax.legend(fontsize=6)
plt.tight_layout()
plt.show()
米国の成長率が日本を上回る時期に円安が進むことがあります。しかし、常に同じ関係が見られるわけではありません。
物価、輸出入、GDPは、為替レートを考えるための重要な材料です。しかし、いずれか一つの指標だけでドル円レートを説明することは困難です。
つぎのセッションでは、これらの経済変数をモデルに組み込み、過去の関係を将来の予測に利用できるかを調べます。
4 モデルを作って将来を予測してみる
ここまでは、ドル円レートと金利、株価、景気、物価、輸出入、GDPの動きを比較しました。つぎに、過去に観察された関係を使って、翌月のドル円レートを予測できるかを調べます。
ただし、モデルの評価には、モデルを作るために使用していない将来のデータを使います。
4.1 何を予測するのか
月末のドル円レートを$S_t$とし、翌月の対数価格差を予測します。
$
y_{t+1}
=\log S_{t+1}-\log S_t
$
$y_{t+1}$が正なら円安・ドル高、負なら円高・ドル安です。
比較の基準には、翌月のドル円レートが今月と同じであるとするランダムウォーク予測を使います。
$
\widehat{y}_{t+1}^{RW}=0
$
予測モデルが有効であるためには、少なくともこの単純な予測よりも、標本外の予測誤差が小さくなければなりません。
4.2 予測に使用する変数
つぎの変数を使用します。
- 当月のドル円対数価格差
- 日米10年国債金利差
- 米国株と日本株の収益率差
- 日米の生産成長率差
為替レートと株価指数には対数を取り、金利はそのまま使用します。
生産統計は公表までに時間がかかるため、1か月前の値を使います。
required_ids = [
"DEXJPUS",
"DGS10",
"IRLTLT01JPM156N",
"NASDAQ100",
"NIKKEI225",
"INDPRO",
"JPNPRMNTO01GYSAM"
]
if "fred" not in globals():
fred = {}
missing = [
series_id for series_id in required_ids
if series_id not in fred
]
if missing:
downloaded = web.DataReader(
missing,"fred","1979-01-01"
)
for series_id in missing:
fred[series_id] = (
downloaded[[series_id]]
.dropna()
)
日次データと月次データのインデックスを、年月に統一します。
def monthly(series, method=None):
series = series.squeeze()
if method is not None:
series = getattr(series.resample("ME"),method)()
series.index = series.index.to_period("M")
return series
data = pd.concat(
{
"fx": monthly(fred["DEXJPUS"], "last"),
"us_rate": monthly(fred["DGS10"], "mean"),
"jp_rate": monthly(fred["IRLTLT01JPM156N"]),
"nasdaq": monthly(fred["NASDAQ100"], "last"),
"nikkei": monthly(fred["NIKKEI225"], "last"),
"us_production": monthly(fred["INDPRO"]),
"jp_production": monthly(fred["JPNPRMNTO01GYSAM"])
},
axis=1
)
予測変数を作ります。
# ドル円の月次対数価格差
data["fx_return"] = (np.log(data["fx"]).diff() * 100)
# 翌月のドル円対数価格差
data["target"] = (data["fx_return"].shift(-1))
# 日米金利差
data["rate_diff"] = (data["us_rate"]- data["jp_rate"])
# 米国株と日本株の月次収益率差
data["stock_diff"] = (np.log(data["nasdaq"]).diff()- np.log(data["nikkei"]).diff())* 100
# 日米生産成長率差
data["production_diff"] = ( np.log(data["us_production"]).diff(12)* 100- data["jp_production"]).shift(1)
model_data = data[["target","fx_return","rate_diff","stock_diff","production_diff"]].dropna()
4.3 予測モデル
最初のモデルは、当月の為替変化だけを使うAR(1)モデルです。
$
y_{t+1}
=\alpha+\beta y_t+\varepsilon_{t+1}
$
つぎに、金利差、株価収益率差、生産成長率差を使う経済変数モデルを作ります。
y_{t+1}
=\alpha
+\beta_1\mathrm{RateDiff}_t
+\beta_2\mathrm{StockDiff}_t
+\beta_3\mathrm{ProductionDiff}_{t-1}
+\varepsilon_{t+1}
最初の120か月を学習期間とし、その後は毎月データを1つ追加してモデルを再推定します。
これは、各時点で利用できる過去のデータだけを使う、逐次的な1か月先予測です。
import statsmodels.api as sm
def expanding_forecast(data,variables,minimum=120):
prediction = pd.Series(index=data.index,dtype=float)
for i in range(minimum, len(data)):
train = data.iloc[:i]
test = data.iloc[[i]]
model = sm.OLS(
train["target"],
sm.add_constant(train[variables],has_constant="add")).fit()
prediction.iloc[i] = model.predict(
sm.add_constant(test[variables],has_constant="add")
).iloc[0]
return prediction
results = pd.DataFrame({"Actual": model_data["target"],"Random walk": 0.0})
results["AR(1)"] = expanding_forecast(model_data,["fx_return"])
results["Economic model"] = expanding_forecast(
model_data,
["rate_diff","stock_diff","production_diff"]
)
results = results.dropna()
# 予測対象となる翌月の日付に変更
results.index = (results.index + 1).to_timestamp()
4.4 標本外予測を評価する
予測誤差は、実現値と予測値の差です。
$
e_{t+1}
= y_{t+1}-\widehat{y}_{t+1}
$
モデルの比較には、平均2乗誤差の平方根であるRMSEを使います。
$
RMSE
=\sqrt{
\frac{1}{T}
\sum_{t=1}^{T}e_t^2
}
$
models = ["Random walk","AR(1)","Economic model"]
errors = results[models].sub(results["Actual"], axis=0)
summary = pd.DataFrame(
{"RMSE": np.sqrt(errors.pow(2).mean())})
summary["Direction accuracy (%)"] = [
np.nan,
(np.sign(results["AR(1)"]) == np.sign(results["Actual"])).mean() * 100,
(np.sign(results["Economic model"])== np.sign(results["Actual"])).mean() * 100
]
print(summary.round(3))
RMSEがランダムウォークより小さければ、そのモデルは平均的には予測誤差を改善しています。
ただし、全期間の平均だけでは、予測が機能した時期と機能しなかった時期を区別できません。そこで、ランダムウォークに対する予測誤差の改善を累積します。
squared_error = errors.pow(2)
gain = pd.DataFrame(
{
"AR(1)": (
squared_error["Random walk"]- squared_error["AR(1)"]
).cumsum(),
"Economic model": (
squared_error["Random walk"]- squared_error["Economic model"]
).cumsum()
}
)
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(gain.index,gain["AR(1)"],label="AR(1)")
ax.plot(gain.index,gain["Economic model"],label="Economic model")
ax.axhline( 0,linewidth=0.5)
ax.set_xlabel("Date")
ax.set_ylabel("Cumulative error gain")
ax.legend(fontsize=6)
plt.tight_layout()
plt.show()
グラフが上昇している期間は、モデルの2乗予測誤差がランダムウォークより小さかったことを示します。下降している期間は、ランダムウォークよりも予測誤差が大きかったことを示します。モデルがある期間には機能しても、別の期間には機能しない可能性があります。したがって、過去の全期間によく当てはまることだけでは、安定した予測力があるとはいえません。また、RMSEが小さくなること、為替変化の方向を正しく予測できること、その予測を取引に利用して収益を得られることは、それぞれ異なる問題です。
つぎのセッションでは、単位根、ランダムウォーク、予測可能性の違いを整理します。
5 予測可能であることとランダムであること
最後に、単位根、ランダムウォーク、予測可能性の違いを整理します。
5.1 単位根を調べる
ドル円レートの対数値と、その対数価格差にADF検定を行います。ADF検定の帰無仮説は、時系列が単位根を持つことです。帰無仮説が棄却されれば、設定した定数項や時間トレンドのもとで、時系列が定常である可能性が示されます。棄却できない場合も、単位根が存在すると断定するのではなく、単位根を持つという仮説を否定できなかったと解釈します。
from statsmodels.tsa.stattools import adfuller
log_fx = np.log(fred["DEXJPUS"].squeeze().resample("ME").last()).dropna()
dlog_fx = log_fx.diff().dropna()
def adf_result(x):
result = adfuller(
x, regression="c",autolag="AIC"
)
return pd.Series({
"ADF statistic": result[0],"p-value": result[1], "lags": result[2]
})
adf_table = pd.DataFrame({
"Log USD/JPY": adf_result(log_fx),"Log difference": adf_result(dlog_fx)
}).T
print(adf_table.round(4))
対数ドル円レートで$p$値が有意水準を上回る場合、単位根があるという帰無仮説を棄却できません。一方、対数価格差で帰無仮説が棄却されれば、価格差は定常過程であると考えられます。
ただし、単位根を棄却できないことは、ドル円レートが完全なランダムウォークであることを意味しません。
5.2 単位根とランダムウォーク
ドリフト付きランダムウォークは、つぎのように表されます。
$
\log S_t
=\mu+\log S_{t-1}+\varepsilon_t
$
したがって、対数価格差は、
$
r_t=\log S_t-\log S_{t-1}=\mu+\varepsilon_t
$
となります。
ランダムウォークであるためには、価格水準が単位根を持つだけでなく、$\varepsilon_t$が過去の情報から予測できないことが必要です。
月次の対数価格差の自己相関を確認します。自己相関が小さければ、過去のドル円レートだけから翌月の変化を予測することは難しいと考えられます。しかし、自己相関が観察されても、それが将来も安定して続くとは限りません。
autocorrelation = pd.Series(
{lag: dlog_fx.autocorr(lag)for lag in range(1, 13)}
)
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.bar(autocorrelation.index, autocorrelation.values)
ax.axhline(0, linewidth=0.5)
ax.set_xlabel("Lag")
ax.set_ylabel("Autocorrelation")
plt.tight_layout()
plt.show()
5.3 予測可能性をどう判断するか
セッション4では、AR(1)モデルと経済変数モデルをランダムウォーク予測と比較しました。ただし、予測水準の誤差と、為替変化の方向を当てる能力とでは、比較すべき基準モデルが異なります。
ランダムウォーク予測は、翌期のドル円レートを現在のドル円レートと同じと予測します。
$$
\hat S_{t+1|t}=S_t
$$
したがって、予測される為替変化はゼロです。
$$
\widehat{\Delta S}_{t+1|t}=0
$$
このため、ランダムウォークはRMSEを比較するための基準モデルにはなりますが、上昇または下落を予測するモデルではありません。方向正答率を比較する場合には、すべての期間でドル買いを予測する「常時ドル買い」を基準とします。
まず、ランダムウォークに対するRMSEの比率を計算します。
comparison = summary.copy()
comparison["RMSE ratio"] = ( comparison["RMSE"] / comparison.loc["Random walk", "RMSE"] )
print(comparison.round(3))
RMSE比率が1を下回れば、モデルはランダムウォークよりも予測誤差を小さくしています。この結果では、AR(1)モデルのRMSE比は1.003、経済変数モデルは1.019であり、いずれもランダムウォーク予測を下回っていません。少なくとも今回の評価期間では、両モデルに明確な標本外予測力は確認できません。経済変数モデルの方向正答率は約52%ですが、この差が統計的または経済的に意味のあるものかは、別途検証する必要があります。ただし、つぎの3つは異なる問題です。
- 統計的な関係が存在すること
- 標本外予測誤差が小さくなること
- 予測が取引や意思決定に役立つこと
モデルのRMSEがわずかに改善しても、その改善が特定の期間に限られている可能性があります。また、為替変化の方向を正しく予測できなければ、取引には利用しにくい場合があります。取引費用、金利、予測誤差、モデルの不安定性を考慮すれば、統計的な予測力がそのまま利益につながるとは限りません。
5.4 為替レートは予測できるのか
ドル円レートの水準系列について単位根の帰無仮説を棄却できないことと、ドル円レートが単純なランダムウォークに従うことは同じではありません。単位根を含む系列であっても、為替変化に自己相関がある場合や、経済変数に将来の為替変化を説明する情報が含まれている場合には、一定の予測可能性が残されています。
しかし、今回の標本外予測では、AR(1)モデルと経済変数モデルのいずれも、ランダムウォーク予測よりRMSEを小さくすることはできませんでした。したがって、少なくとも今回使用したモデル、データおよび評価期間からは、安定した予測力があるとは確認できません。
為替レートはまったく予測できないとも、常に予測できるとも一概には結論できません。特定の期間や市場環境では予測可能性が現れることがありますが、その関係が別の期間にも持続するとは限らないからです。重要なのは、モデルが過去のデータによく当てはまるかではなく、未知のデータに対して単純な基準モデルを安定して上回るかを確認することです。さらに、予測誤差の改善が取引費用や金利を考慮した後にも経済的価値を持つかを、別の問題として評価する必要があります
1254229072026
参考文献
「為替オーバーレイ―CFA Institute(CFA協会)コンフェレンス議事録」(パンローリング)

Python3ではじめるシステムトレード【第2版】環境構築と売買戦略
「画像をクリックしていただくとpanrollingのホームページから書籍を購入していただけます。













