2019-02-03

Rで株価を扱う 〜 始値を予測してみた

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)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# GLOBAL SETTINGS

options("getSymbols.warning4.0" = FALSE)

# selection for modeling method
#model.method <- "pls"
model.method <- "rf"
#model.method <- "xgbLinear"
#model.method <- "xgbTree"
if (!exists("model.method")) {
    model.method <- "rf"
}

# prediction table object for store
predTblFile <- paste(Sys.getenv("HOME"), "preidction.RDS", sep = "/")

# for multi-threading
cl <- makePSOCKcluster(detectCores())
registerDoParallel(cl)

# -----------------------------------------------------------------------------
#  Function: get.stock
#  get table of certain stock data
#
#  code:   stock name/code
#  start:  start date
#  source: data source, default = 'yahooj'
#
#  return
#    data frame for specified stock data including Date column
# -----------------------------------------------------------------------------
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)
}

# -----------------------------------------------------------------------------
#  Function: train.model
#  train model with specified ML method
#
#  df:          data frame for training including Y column
#  name.method: ML method
#
#  return
#    trained model
# -----------------------------------------------------------------------------
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)
}

# =============================================================================
#  MAIN
# =============================================================================
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

# Yahoo Japan
list.code <- append(list.code, "4689.T") 
# SBT, SoftBank Technology
list.code <- append(list.code, "4726.T") 
# Rakuten
list.code <- append(list.code, "4755.T") 
# KDDI
list.code <- append(list.code, "9433.T") 
# docomo
list.code <- append(list.code, "9437.T")
# SBG, SoftBank group
list.code <- append(list.code, "9984.T")

# get stock data
for (code in list.code) {
    tbl <- merge(tbl, get.stock(code, date.start))
}

# NIKKEI225
tbl <- merge(tbl, get.stock("NIKKEI225", date.start, "FRED"))

# add data and Y data
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

# start model training
model <- train.model(tbl[1:(nrow(tbl) - 1),], model.method)
model

# update prediction table
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)

# prediction performance
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")
)

# ---
# PROGRAM END

トレーニング用のデータフレームでは 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 が小さくなるかを監視し、また予測精度向上に寄与する他の要因を加えられないかの検討を続けます。

参考 サイト

  1. bitWalk's: Rで株価を扱う 〜 quantmod パッケージ [2019-01-16]
  2. MathJax | Beautiful math in all browsers.

関連しそうな書籍を Amazon.co.jp からピックアップしましたが、現時点ではまだ金融データを扱うために特定の参考書を読んではいません。まずは自分が思うようにデータを処理してみて、しかる後に世の中の専門家が考えていることを付き合わせてみたいと思っています。

ブログランキング・にほんブログ村へ
にほんブログ村

0 件のコメント: