Rの quantmod パッケージを利用することにより、株価データを取り扱えるようになりました [1]。たくさんのデータを利用できるようになったので、機械学習でなにかを予測しようと考えました。今回も昨年末 (2018-12-19) に東証一部へ再上場したソフトバンク株式会社 (9434.T) を例に取り上げました。
関数関係を考える
\(9434.T\) の株価を、ある要因 \(x_{1}, x_{2}, ...\) を使って \(f\) という処理をして予測する、すなわち、
\(9434.T = f(x_{1}, x_{2}, ...)\)
というような関数関係を設定します。
株の値動きはそんなに単純ではないと、トレーダーからお叱りを受けそうですが、機械学習による予測はどんなものかを知りたかったので、問題を単純化して次のようにしてみました。
当日の株関連情報を用いて、翌日(次回)のソフトバンク株式会社 (9434.T) の始値を予測せよ。
当日の株関連情報は、予測対象のソフトバンクの情報に加えて、ソフトバンク・グループ企業と同業企業の情報および日経平均を加ました。
企業 | コード | ソース | 情報 |
ソフトバンク |
9434.T |
yahooj |
Open, High, Low, Close, Volume, Adjusted |
ヤフー |
4689.T |
ソフトバンク・テクノロジー |
4726.T |
楽天 |
4755.T |
KDDI |
9433.T |
NTT ドコモ |
9437.T |
ソフトバンク・グループ |
9984.T |
日経平均 |
NIKKEI225 |
FRED |
- |
R のコードを下記に示しました。機械学習には Random Forest (rf) を使用しています。
リスト:ソフトバンク (9434.T) の始値予測用プログラム
library(quantmod)
library(caret)
library(doParallel)
options("getSymbols.warning4.0" = FALSE)
model.method <- "rf"
if (!exists("model.method")) {
model.method <- "rf"
}
predTblFile <- paste(Sys.getenv("HOME"), "preidction.RDS", sep = "/")
cl <- makePSOCKcluster(detectCores())
registerDoParallel(cl)
get.stock <- function(code, start, source = "yahooj") {
tbl <- getSymbols(code, src = source, from = start, auto.assign = FALSE)
tmp <- data.frame(tbl)
tmp$Date <- rownames(tmp)
return(tmp)
}
train.model <- function(df, name.method) {
gc()
set.seed(0)
model <- train(
Y ~ .,
data = df,
method = name.method,
tuneLength = 10,
preProcess = c('center', 'scale'),
trControl = trainControl(method = "cv")
)
return(model)
}
date.start <- "2018-12-19"
code.target <- "9434.T"; # SoftBank
col.target <- "YJ9434.T.Open"
tbl <- get.stock(code.target, date.start)
list.code <- NULL
list.code <- append(list.code, "4689.T")
list.code <- append(list.code, "4726.T")
list.code <- append(list.code, "4755.T")
list.code <- append(list.code, "9433.T")
list.code <- append(list.code, "9437.T")
list.code <- append(list.code, "9984.T")
for (code in list.code) {
tbl <- merge(tbl, get.stock(code, date.start))
}
tbl <- merge(tbl, get.stock("NIKKEI225", date.start, "FRED"))
tbl$Date <- as.Date(tbl$Date)
y <- tbl[, col.target]
y <- y[2:length(y)]; # shifting for using training
y <- append(y, NA)
tbl$Y <- y
model <- train.model(tbl[1:(nrow(tbl) - 1),], model.method)
model
date.last <- tbl[nrow(tbl), "Date"]
open.next <- predict(model, tbl[nrow(tbl),]); # prediction for next opening
tmp <- data.frame(Date = date.last,
Open.Next = open.next,
Method = model.method)
rownames(tmp) <- NULL
tmp$Method <- as.character(tmp$Method)
if (!file.exists(predTblFile)) {
predTbl <- tmp
} else {
predTbl <- readRDS(predTblFile)
if (date.last %in% predTbl$Date) {
predTbl[predTbl$Date == date.last, "Open.Next"] <- tmp[1, "Open.Next"]
predTbl[predTbl$Date == date.last, "Method"] <- tmp[1, "Method"]
} else {
predTbl <- rbind(predTbl, tmp[1, ])
}
}
predTbl
saveRDS(predTbl, file = predTblFile)
tbl2 <- data.frame(Date = tbl$Date, Act = tbl[, col.target], Pred = NA)
for (d in predTbl$Date) {
if (d %in% tbl2$Date) {
r <- as.integer(rownames(tbl2[tbl2$Date == d,])) + 1
p <- predTbl[predTbl$Date == d, "Open.Next"]
tbl2[r, "Pred"] <- p
}
}
tbl2$Date <- as.character(tbl2$Date)
xData <-as.integer(rownames(tbl2))
plot(xData, tbl2$Act,
main = "Prediction",
xlab = NA, xaxt = "n",
ylab = col.target,
las = 1, type = "n")
axis(side = 1, at = xData, label = tbl2$Date, las = 2)
grid(NULL, NULL, lty = 2, col = "darkgray")
abline(v = xData[length(xData) - 1], lty = 1, col = "darkgray")
points(xData, tbl2$Act,
type = "b", col = "blue", pch = 21)
points(xData, tbl2$Pred,
type = "b", col = "red", pch = 19)
legend("bottom", bty="n", ncol = 2,
legend = c("Actual", "Prediction"),
pch = c(21, 19),
lty = c(1, 1),
col = c("blue", "red")
)
トレーニング用のデータフレームでは Y の列に、翌日の YJ9434.T.Open の値を一日前までの行にずらして設定しました。つまり毎回 2018-12-19 から前日までのデータをトレーニング用データにして学習させ、そのモデルで当日の Y 値(翌日の始値)を予測しています。したがって、日が経てばそれだけトレーニング用データが増えていきます。
リスト:実行例 (2019-02-02)
R version 3.5.2 (2018-12-20) -- "Eggshell Igloo"
Copyright (C) 2018 The R Foundation for Statistical Computing
Platform: x86_64-redhat-linux-gnu (64-bit)
R は、自由なソフトウェアであり、「完全に無保証」です。
...
(途中省略)
...
> # start model training
> model <- train.model(tbl[1:(nrow(tbl) - 1),], model.method)
> model
Random Forest
26 samples
44 predictors
Pre-processing: centered (44), scaled (44)
Resampling: Cross-Validated (10 fold)
Summary of sample sizes: 23, 23, 23, 24, 23, 24, ...
Resampling results across tuning parameters:
mtry RMSE Rsquared MAE
2 35.92638 0.8852441 26.70252
6 30.32361 0.8886168 22.45102
11 27.67208 0.8935603 20.18447
16 25.91685 0.8933522 19.27206
20 25.73431 0.8920262 19.11300
25 25.14702 0.9064315 18.88401
30 25.65043 0.8912953 18.91954
34 25.31299 0.9627597 18.95446
39 25.13421 0.8906889 18.39020
44 25.32338 0.9203712 18.77740
RMSE was used to select the optimal model using the smallest value.
The final value used for the model was mtry = 39.
>
> # update prediction table
> date.last <- tbl[nrow(tbl), "Date"]
...
(途中省略)
...
>
> predTbl
Date Open.Next Method
1 2019-01-21 1420.734 rf
2 2019-01-22 1426.954 rf
3 2019-01-23 1424.429 rf
4 2019-01-24 1420.733 rf
5 2019-01-25 1416.661 rf
6 2019-01-28 1419.768 rf
7 2019-01-29 1416.853 rf
8 2019-01-30 1379.564 rf
9 2019-01-31 1353.043 rf
10 2019-02-01 1339.745 rf
> saveRDS(predTbl, file = predTblFile)
>
...
(途中省略)
...
> # ---
> # PROGRAM END
>
毎日プログラムを実行して予測された値は、データフレーム predTbl で予測日の行の Open.Next 列に格納されて保存されます。ちなみに、データソースの FRED の更新タイミングが一番遅くて、正確に計測がしていませんが、その日の午後10時頃になります。
プロットでは翌日分の予測値をずらして赤点で表示して、青点の実績値との予実が見れるようにしました。
ソフトバンク (9434.T) の始値の予測例
まとめ
まだデータ数が少なく、また、要因としての入力情報もざっくり選んでいるので、値動きがあるときに適切に予測できるのかはまだ判りません。モデルのトレーニングを実施して平均二乗誤差 RMSE が 25 程度ということは、予実プロットでもっともらしい結果が出ていても±25円程度の予測精度しかないということです。データが増えるにつれて、RMSE の値がじわりと下がってきているので、どの程度まで RMSE が小さくなるかを監視し、また予測精度向上に寄与する他の要因を加えられないかの検討を続けます。
参考 サイト
- bitWalk's: Rで株価を扱う 〜 quantmod パッケージ [2019-01-16]
- MathJax | Beautiful math in all browsers.
関連しそうな書籍を Amazon.co.jp からピックアップしましたが、現時点ではまだ金融データを扱うために特定の参考書を読んではいません。まずは自分が思うようにデータを処理してみて、しかる後に世の中の専門家が考えていることを付き合わせてみたいと思っています。
にほんブログ村