今週も特にありません

進捗どうですか?

xtsでmatplotのようなプロット

最近はdplyrやtidyrとの相性で、ほぼggplot2を使うようになり、めっきりmatplotを使う機会が無くなってきています。

しかし、時系列データを扱う際にdata.frameよりもxtsを使いたい場合もあり、その時にxtsのままでmatplotのような感じのプロットするにはどうするのがいいのか?と思っていたので調べたメモします。結局、xtsとxtsExtraをセットで使うのが良さそうということがわかりました。

まず、データとして、複数時系列データをQuandlから取ってきます。この時に、Quandl関数でtypeにxtsを指定すれば、xtsクラスでデータを取って来れますが、一旦、ggplot2でプロットするためにdata.frameで取って来ることにします。

library(Quandl)

rate_df <- Quandl("MOFJ/INTEREST_RATE_JAPAN", trim_start = "2015-01-01")

このデータをggplot2でプロットします。

library(dplyr)
library(tidyr)
library(ggplot2)
library(scales)

rate_df %>% gather(maturity, rate, -Date) %>% 
  ggplot(aes(x = Date, y = rate, colour = maturity)) + geom_line(size = 1) +
  scale_x_date(labels = date_format("%m/%d"))

f:id:masaqol:20151206233544p:plain

その他、色々と付け足していけば、思いどおりのプロットができるでしょう。しかし、本題はデータをdata.frameではなくxtsクラスで扱いたい場合です。まわりくどいですが、Quandlの中でやっている処理と同じようにdata.frameからxtsに変換します。

rate_xts <- xts(rate_df[, -1], order.by = rate_df[, 1])

ここで、そのままplotすれば複数系列を一辺にプロットしてくれると思いきや、警告メッセージとともに残念なプロットがされます。

> plot(rate_xts)
Warning message:
In plot.xts(rate_xts) : only the univariate series will be plotted

f:id:masaqol:20151206234012p:plain

matplotならばどうかというと、xtsプロットの良い部分は完全に失われます。

> matplot(rate_xts, type = "l")

f:id:masaqol:20151206234802p:plain

xtsはzooクラスとしても扱えるのだから、plot.zooで良い感じにプロットされるのではないかと思い、やってみるとmatplotとほぼ変わりません…

> plot.zoo(rate_xts, plot.type = "single")

f:id:masaqol:20151206235744p:plain

というところまで試してみて、CRANには上がっていないxtsExtraを使うのが一番良さそうということが分かり、使ってみるとxtsプロットの良い部分もあり、matplotのような複数系列のプロットが簡単にできました。

library(devtools)
devtools::install_github("joshuaulrich/xtsExtra")
library(xtsExtra)

plot(rate_xts, col = 1:15)

f:id:masaqol:20151207004738p:plain

ただ、デフォルトでplotするだけでは12系列までしかプロットされないようなので、色の指定を系列数分に増やす、または、addSeriesを使って追加する必要があるようです。matplotしたものの軸ラベル等をxtsプロット風に頑張って変更するよりも、断然xtsExtraを使うのが良さそうです。

pforeachで最尤推定した漸近分布を確認する

最尤推定で推定したパラメータの漸近分布をシミュレーションによって確認する
繰り返し推定を行う際にpforeach(hoxo-m/pforeach · GitHub)を使う

CIRモデルのパラメータ推定と関数もパラメータの設定も同じとする

r0 <- 0.01
n <- 1000
delta <- 1/52
kappa <- 0.2
mu <- 0.03
sigma <- 0.08
initparam <- c(kappa, mu, sigma)

パスを発生させ、パラメータ推定を繰り返す。収束しなかったものは取り除く

ggplot2で推定したパラメータの分布をプロットしたいので、最終的にdata.frameで返ってくるようにする

.c = data.frameとして、data.frameのまま結合してもパラメータの名前がついたdata.frameが返って来なかったため、rbindで結果を結合したあとにdata.frameに変換している(これが最適な方法なのかについては…)

library(sde)
library(dplyr)
library(pforeach)

est <- pforeach(i = 1:1000, .c = rbind)({
  sde.sim(X0 = r0, N = n, delta = delta, 
          theta = c(kappa * mu, kappa, sigma), model = "CIR") %>%
  CIRMLE(initparam, delta)
}) %>% as.data.frame %>% filter(convergence != 1)

foreachの少し込み入った使い方に面倒くささを感じていたが、pforeachを使えば簡単、簡潔に記述できる

replicateを使う場合には、データを発生させて、パラメータを推定してという一連の処理を書いた関数を書いておいて、その関数を繰り返すということをしなければならないが、pforeachではループの中にザッと処理を書いておくだけで繰り返し推定が行える。返り値もあるので、その後一度に加工して扱える。素晴らしい

推定したパラメータの分布をプロットする

library(ggplot2)

ggplot(est, aes(x = kappa)) + geom_histogram(binwidth = 0.1)
ggplot(est, aes(x = mu)) + geom_histogram(binwidth = 0.005)
ggplot(est, aes(x = sigma)) + geom_histogram(binwidth = 0.0001)

平均回帰パラメータkappaの分布のみここでは確認する。推定された値の平均を計算すると真値よりも大きい値となる。そして、プロットしても上方に推定するバイアスが生じることが確認できる(TRUE = 0.2)

f:id:masaqol:20150831010738p:plain

CIRモデルのパラメータ推定

CIRモデルについてもVasicekモデルと同様にシンプルに最尤推定でモデルのパラメータを推定できる関数を用意しておく

# maximum likelihood estimation for CIR model
#
# rate      : interest rate
# param     : model parameter
# delta     : time step
# initparam : initial parameter
#
CIRloglik <- function(rate, param, delta) {
  n <- length(rate)
  c <- 4 * param[1] / (param[3] ^ 2 * (1 - exp(-param[1] * delta)))
  df <- 4 * param[1] * param[2] / (param[3] ^ 2)
  ncp <- c * rate[1:(n - 1)] * exp(-param[1] * delta)
  sum(dchisq(c * rate[2:n], df = df, ncp = ncp, log = TRUE) + log(c))
}

CIRMLE <- function(rate, initparam, delta) {
  opt <- optim(initparam, CIRloglik, rate = rate, delta = delta, control = list(fnscale = -1))
  c(kappa = opt$par[1], mu = opt$par[2], sigma = opt$par[3], loglik = opt$value, convergence = opt$convergence)
}

パラメータを設定して、パスを発生させる

library(sde)

r0 <- 0.01
n <- 500
delta <- 1/52
kappa <- 0.2
mu <- 0.03
sigma <- 0.08
initparam <- c(kappa, mu, sigma)
set.seed(12345)
rate <- sde.sim(X0 = r0, N = n, delta = delta, theta = c(kappa * mu, kappa, sigma), model = "CIR")

推定結果を確認する

> CIRMLE(rate, initparam, delta)
       kappa           mu        sigma       loglik  convergence 
2.789964e-01 6.105776e-02 8.106622e-02 2.376850e+03 0.000000e+00

初期値の与え方にもよるが、Vasicekモデルと比べて多少パラメータの推定値にバイアスが出ることになります。 特に平均回帰パラメータkappaについては上方に推定する傾向にあります

Rからアメリカ雇用統計データ収集

FXをやっている人なら誰しも気になる経済統計といえば、アメリカの雇用統計。 【米国】雇用統計 - 経済指標 - Yahoo!ファイナンスでも直近の数年間のデータを詳しく見ることができますが、簡単にRから直接データを収集して、分析したいという人はblsAPIパッケージを使ってみるのが良さそうです

こちらmikeasilva/blsAPI · GitHubにもサンプルがありますが、発表時に一番注目される非農業部門雇用者数と失業率のデータを使った例がないようなので、そのデータを取得してから可視化するところまでをメモ

使い方は簡単で、パッケージに唯一含まれているblsAPIという関数に欲しいデータのIDコードを与えてやればデータを取って来れます。その他、list形式でパラメータを指定してやれば、いろいろと自由にデータ収集可能です

library(rjson)
library(blsAPI)
library(dplyr)

payload <- list(
  'seriesid' = c('CES0000000001', 'LNS14000000'),
  'startyear' = 2007,
  'endyear' = 2015)

response <- blsAPI(payload) %>% fromJSON()

これだけで、2007年から2015年までの雇用者数と失業率のデータが取って来れます。 responseの中に2系列入っており、年月についても整形してdata.frameに変換してやります

series1 <- response$Results$series[[1]]$data
series2 <- response$Results$series[[2]]$data

date_str <- 0
value1 <- value2 <- 0
for (i in 1:length(series1)) {
  date_str[i] <- paste(series1[[i]]$year, sub('M', '', series1[[i]]$period), "01", sep = "-")
  value1[i] <- series1[[i]]$value
  value2[i] <- series2[[i]]$value
}

date <- as.Date(date_str[order(date_str)])[-1]
data1 <- diff(as.numeric(value1[order(date_str)]))
data2 <- as.numeric(value2[order(date_str)])[-1]
df <- data.frame(date, data1, data2)

データを確認します

> head(df)
        date data1 data2
1 2007-02-01    88   4.5
2 2007-03-01   188   4.4
3 2007-04-01    78   4.5
4 2007-05-01   145   4.4
5 2007-06-01    71   4.6
6 2007-07-01   -34   4.7

> tail(df)
          date data1 data2
95  2014-12-01   329   5.6
96  2015-01-01   201   5.7
97  2015-02-01   266   5.5
98  2015-03-01   119   5.5
99  2015-04-01   221   5.4
100 2015-05-01   280   5.5

前月比で雇用者数の変化を棒グラフにする

library(ggplot2)
library(scales)

df %>% ggplot(aes(x = date)) +
  geom_bar(aes(y = data1), stat = "identity", fill = "blue") + 
  theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  scale_x_date(labels = date_format("%m/%y"), breaks = date_breaks("6 months")) +
  scale_y_continuous(breaks = seq(-1000, 1000, by = 200)) + ylab("1-Month Net Change")

f:id:masaqol:20150630011930p:plain

失業率もプロットする

df %>% ggplot(aes(x = date)) +
  geom_line(aes(y = data2), size = 1, colour = "red") +
  theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  scale_x_date(labels = date_format("%m/%y"), breaks = date_breaks("6 months")) + 
  scale_y_continuous(breaks = seq(0, 12, by = 1)) + ylab("Unemployment Rate")

f:id:masaqol:20150630011941p:plain

Vasicekモデルのパラメータ推定

SMFI5パッケージでもVasicekモデルのパラメータ推定はできるが、参考程度にパラメータを推定する際、シンプルに最尤推定できるものを用意しておいて、簡単に使い回せるようにしておきたい

# maximum likelihood estimation for Vasicek model
#
# rate      : interest rate
# param     : Vasicek model parameter
# delta     : time step
# initparam : initial parameter
#
Vasicekloglik <- function(rate, param, delta) {
  n <- length(rate)
  m <- param[2] + (rate[1:(n-1)] - param[2]) * exp(-param[1] * delta)
  v <- param[3]^2 * (1 - exp(-2 * param[1] * delta)) / (2 * param[1])
  sum(dnorm(rate[2:n], mean = m, sd = sqrt(v), log = TRUE))
}
  
VasicekMLE <- function(rate, initparam, delta) {
  opt <- optim(initparam, Vasicekloglik, rate = rate, delta = delta, control = list(fnscale = -1))
  c(kappa = opt$par[1], mu = opt$par[2], sigma = opt$par[3], loglik = opt$value, convergence = opt$convergence)
}

optimhessian = TRUEを足したり、2つの関数を1つにしてしまったりということもできる

sdeパッケージを使ってパスを発生させ、パラメータ推定する

library(sde)

r0 <- 0.01
n <- 500
delta <- 1/52
kappa <- 0.2
mu <- 0.03
sigma <- 0.01
initparam <- c(kappa, mu, sigma)
set.seed(123)
rate <- sde.sim(X0 = r0, N = n, delta = delta, theta = c(kappa * mu, kappa, sigma), model = "OU")

結果を確認する

> VasicekMLE(rate, initparam, delta)
       kappa           mu        sigma       loglik  convergence 
5.670395e-01 3.114274e-02 9.740629e-03 2.596687e+03 0.000000e+00 

最適化の初期値に真の値を入れていることもあり、良い値に推定できています

金利の平均回帰速度kappaはだいぶ真の値よりも上方に推定されていますが、推定バイアスを改善させましょうというような論文がいくつかありますので、この辺をじっくり読むというのも大変面白いです

Rの金利期間構造パッケージ ycinterextra

金利の期間構造関連パッケージを使ってみるシリーズ。今回はycinterextraを調査。このパッケージもこれといった情報がないので、普通にパッケージの説明書を読んで、日本の金利データに適用してみる

パッケージの中身は、CRAN Task View: Empirical Financeにも書かれている通り、Nelson-Siegelモデル等の様々なモデルでイールドカーブの補間と補外を行うことができるパッケージだと言うこと

主な関数はycinterとycextraで、その他ユーティリティ的な関数が用意されている

ycinter(yM = NULL, p = NULL, matsin, matsout,
  method = c("NS", "SV", "SW", "HCSPL"), 
  typeres = c("rates", "prices"))

ycextra(yM = NULL, p = NULL, matsin, matsout,
  method = c("NS", "SV", "SW"), 
  typeres = c("rates", "prices"), UFR, T_UFR = NULL)

ycinterとycextraともに、ゼロクーポン債価格とゼロクーポンイールドを対象としているので、クーポン債に適用したい場合にはその前に処理しておく必要がある

まず、財務省国債金利情報ページからデータを持って来る

jgbcme <- read.csv("http://www.mof.go.jp/english/jgbs/reference/interest_rate/jgbcme.csv", skip = 1)
JGB_rate <- as.numeric(jgbcme[1, -1]) / 100
maturity <- c(1:10, 15, 20, 25, 30, 40)

Nelson-SiegelモデルとSvenssonモデルをデータに適用していく

library(ycinterextra)

NSyc <- ycinter(yM = JGB_rate, matsin = maturity, matsout = 1:40, method = "NS", typeres = "rates")
SVyc <- ycinter(yM = JGB_rate, matsin = maturity, matsout = 1:40, method = "SV", typeres = "rates")

求まったパラメータの値は次の通り

> coeffs(NSyc)
[1]  0.01847065 -0.01415590 -0.03796077  3.00000000
> coeffs(SVyc)
[1]  0.02019232 -0.02168073  0.04297268 -0.07961120  2.01990134  3.00000000

3.00000000と出ていることからも上手く最適化できていない可能性が高そうです…
ycplotを使って、当てはまりの診断を見てみる

> ycplot(NSyc)
> ycplot(SVyc)

Nelson-Siegelモデル f:id:masaqol:20150430002854p:plain Svenssonモデル f:id:masaqol:20150430002905p:plain

残差が本当に正規なのというところは疑問が残るところですが、それなりに補間は上手く行っているようです

(method = "HCSPL"を指定すれば、Hermite cubic splineによる補間もできるのだが、上と同じようにしてもエラーが出てなぜか上手く適用できなかった…)

同じように補外の方も1年から60年までの設定で試してみる
ここで、UFRの値は上のパラメータの値から0.02を設定している

NSyc <- ycextra(yM = JGB_rate, matsin = maturity, matsout = 1:60, method = "NS", typeres = "rates", UFR = 0.02) 
SVyc <- ycextra(yM = JGB_rate, matsin = maturity, matsout = 1:60, method = "SV", typeres = "rates", UFR = 0.02) 

求まったパラメータの値は次の通り

> coeffs(NSyc)
[1]  0.02000000 -0.01457810 -0.04355021  3.00000000
> coeffs(SVyc)
[1]  0.02000000 -0.02162734  0.03623047 -0.07322875  1.90287193  3.00000000

やはり、上手く最適化できていないようです…

こちらもycplotで確認

> ycplot(NSyc)
> ycplot(SVyc)

Nelson-Siegelモデル f:id:masaqol:20150430005532p:plain Svenssonモデル f:id:masaqol:20150430005541p:plain

少々、簡単でしたがycinterextraパッケージの関数を使って、実際データに適用してみました。今の日本の金利データがこれらのモデルに適用するというのが難しい面もありそうですが、パラメータを求めるあたりはあまり上手くいっていないようです。このパッケージでは、これらのモデル以外にも、Smith-Wilson methodも使えるのでお手軽に試してみたいという時は良さそうです

CIRモデルの債券オプション

Vasicekモデルと同じ流れでCIRの債券オプションについて

モデルの一部が違うだけでも、CIRモデルとVasicekモデルでこのような違いが出てくるのかといった部分を追っていくのも面白い。ただ、プログラミングするのは少し面倒となる

# zero coupon bond option price under Cox-Ingersoll-Ross model
# 
# Args:
#   kappa : speed of reversion
#   mu    : long term mean level
#   sigma : instaneous volatility
#   r0    : current short rate
#   t     : current time
#   T     : bond maturity
#   S     : option maturity
#   K     : strike price
#   L     : face value
#  
# Return:
#   data.frame of moneyness, call and put option prices 
#
CIRZCBOption <- function(kappa, mu, sigma, r0, t, T, S, K, L) {
  tau <- S - T
  g <- sqrt(kappa^2 + 2 * sigma^2)
  A <- ((2 * g * exp((kappa + g) * tau / 2)) / 
       (2 * g + (kappa + g) * (exp(tau * g) - 1)))^(2 * kappa * mu / sigma^2)
  B <- 2 * (exp(tau * g) - 1) / (2 * g + (kappa + g) * (exp(tau * g) - 1))
  ra <- log(L * A / K) / B
  l1 <- 2 * g / (sigma^2 * (exp((T - t) * g) - 1))
  l2 <- (kappa + g) / sigma^2
  d <- 4 * kappa * mu / sigma^2
  ncp1 <- 2 * l1^2 * r0 * exp((T - t) * g) / (l1 + l2 + B)
  ncp2 <- 2 * l1^2 * r0 * exp((T - t) * g) / (l1 + l2)
  q1 <- 2 * ra * (l1 + l2 + B)
  q2 <- 2 * ra * (l1 + l2)
  PT <- CIRZCBond(kappa, mu, sigma, r0, t, T)$price
  PS <- CIRZCBond(kappa, mu, sigma, r0, t, S)$price
  M <- K / (L * PS / PT)
  CP <- L * PS * pchisq(q1, d, ncp1) - K * PT * pchisq(q2, d, ncp2)
  PP <- CP + K * PT - L * PS
  data.frame(moneyness = M, callprice = CP, putprice = PP)
}

パラメータをセットする

kappa <- 0.2
mu <- 0.015
sigma <- 0.05
r0 <- 0.001
t <- 0
T <- 1/4
S <- 3
K <- 0.98
L <- 1

オプション価格を確認する

> CIRZCBOption(kappa, mu, sigma, r0, t, T, S, K, L)
  moneyness  callprice     putprice
1 0.9928814 0.00702689 2.968141e-06

パラメータを調整して、CIRモデルとVasicekモデルを完全に同じ条件で比較することは難しいが、CIRモデルの債券価格の場合と同様にボラティリティの部分を調整することで、確認することができる

オプション満期で債券価格がほぼ同じに評価されるようにパラメータを調整して、債券価格と債券オプション価格を計算してみる

> sigma_v <- sigma * sqrt(mu)

> VasicekZCBond(kappa, mu, sigma_v, r0, t, T)
  maturity     price       yield
1     0.25 0.9996641 0.001343863
> CIRZCBond(kappa, mu, sigma, r0, t, T)
  maturity    price       yield
1     0.25 0.999664 0.001344209

> VasicekZCBOption(kappa, mu, sigma_v, r0, t, T, S, K, L)
  moneyness   callprice     putprice
1  0.992794 0.007500676 0.0003899409
> CIRZCBOption(kappa, mu, sigma, r0, t, T, S, K, L)
  moneyness  callprice     putprice
1 0.9928814 0.00702689 2.968141e-06

基本的に、CIRモデルとVasicekモデルの債券オプション価格を比較した場合には、Vasicekモデルの短期金利がマイナスになるという可能性がある点で違いが出てくる。CIRモデルは非心カイ自乗分布、Vasicekモデルは正規分布短期金利が従うことから、CIRモデルはVasicekモデルに比べて、債券価格を低く評価する傾向がある。そこから、債券オプション価格も権利行使価格が額面価格に近づくにつれて、CIRモデルはVasicekモデルよりも低く評価する傾向にある

権利行使価格を変化させながら、オプション価格を計算する

K <- seq(0.97, 1.01, by = 0.001)
df1 <- VasicekZCBOption(kappa, mu, sigma_v, r0, t, T, S, K, L)
df2 <- CIRZCBOption(kappa, mu, sigma, r0, t, T, S, K, L)
df <- cbind(rbind(df1, df2), model = c(rep("Vasicek", nrow(df1)), rep("CIR", nrow(df2))))

ggplot2でプロットする

library(ggplot2)
library(ggthemes)

ggplot(df, aes(x = moneyness, y = callprice, colour = model)) + 
geom_line(size = 1.2) + theme_economist() + ggtitle("call option price") +
scale_x_continuous(breaks = seq(0.98, 1.02, by = 0.01))

ggplot(df, aes(x = moneyness, y = putprice, colour = model)) + 
geom_line(size = 1.2) + theme_economist() + ggtitle("put option price") +
scale_x_continuous(breaks = seq(0.98, 1.02, by = 0.01))

f:id:masaqol:20150405233900p:plain

f:id:masaqol:20150405233911p:plain

ATM付近でモデルの違いがオプション価格に現れてくることがわかります。初期金利の設定の仕方によっては変わってくる部分もありますが、細かいところを論ずる部分は教科書等を参考にお願いします…