証券のリスクとリターンを計算するアプリケーションを作ろう


ファイナンス理論では証券のリターンとリスクを手がかりにポートフォリをを構築します。
ポートフォリオに個別証券をどれだけの割合で組み入れるかを判断するためには、以下のパラメータが必要です。

  1. リターン(期待収益率)
  2. リスク(標準偏差)
  3. 各銘柄についての相関係数

要件

シンボルを入力すると、上記パラメータが出力されれば良いので、コンソールアプリケーションでも良いのですが、商品化も視野に入れてるので最初からWebアプリケーションで開発します。
使用する言語は以下を想定します。

  1. R
  2. PHP
  3. HTML

設計方針

リスクとリターンを計算するためのヒストリカルデータのサンプリング期間については様々な議論がありますが、三菱信託UFJ銀行は過去 20 年間の月次のリターン実績から算定しているそうなので、これに倣うとしましょう。
計算式は以下の通り

リスク推計値\left(年率標準偏差\right)=\left(n年間の月次リターンの標準偏差\right)×\sqrt{12}\\\\
ただし、n=20

しかし、ヒストリカルデータが20年に満たない場合があるので、この際は設定来のデータを利用します。
最長20年間のデータを取得する詳しい方法は以下の記事を御覧ください。

ちなみにリターンは年率リターンの平均値で計算します。
相関係数はヒストリカルデータから分散、共分散を計算できるので、追加の情報は不要です。

Webサーバの構築

お手軽なWebサーバーの立て方というのが公開されていたので、これにしてみましょう。
ついでにSmartyというPHPテンプレートエンジンもインストールしておきます。
Smartyを使いやすくするために、Smartyクラスを継承するSmarty_PortfolioMakerクラスを作成するsetup.phpをPHPのinclude_pathに設置しておきます。

setup.php
<?php

// Smartyライブラリを読み込みます
require('Smarty.class.php');

// setup.phpはアプリケーションに必要なライブラリファイルを
// 読み込むのに適した場所です。それをここで行うことができます。例:
// require('guestbook/guestbook.lib.php');

class Smarty_PortfolioMaker extends Smarty
{

   function __construct()
   {

      // クラスのコンストラクタ。
      // これらは新しいインスタンスで自動的にセットされます。

      parent::__construct();

      $this->template_dir = 'C:/xampp/portfoliomaker/templates/';
      $this->compile_dir  = 'C:/xampp/portfoliomaker/templates_c/';
      $this->config_dir   = 'C:/xampp/portfoliomaker/configs/';
      $this->cache_dir    = 'C:/xampp/portfoliomaker/cache/';

      $this->caching = Smarty::CACHING_LIFETIME_CURRENT;
      $this->assign('app_name', 'Portfolio Maker');
   }
}

あとはRの環境を用意すれば準備OK。

PHPとRの結合

入出力の要件定義より、処理順を以下の通り設計しました。

  1. PHPで出力したHTMLよりシンボルを入力して送信
  2. 入力値をRに渡してヒストリカルデータを取得
  3. Rでヒストリカルデータを解析し、リスクとリターンを出力
  4. 出力されたデータをPHPで受け取り、HTMLに描画して表示

では作っていきましょう。

入力画面

これはHTMLを吐けばいいので簡単。
phpはdisplay関数を呼ぶだけにとどめ、マークアップはtplファイルに任せます。

input.php
<?php
require_once('portfoliomaker/setup.php');
$smarty = new Smarty_PortfolioMaker;
$smarty->display('input.tpl');
?>
input.tpl
<!DOCTYPE html>
<html lang=“ja”>

<head>
    <meta charset=“UFT-8”>
    <title>さいきょうのポートフォリオを作ろう!</title>
</head>

<body>
    <h1>ETFのリスクとリターンを検索する</h1>
    <form action="quants.php" method="POST">
        <p>シンボル<input type=“text” name=symbol></p>
        <p><button type=“submit” value=“送信“>送信</button></p>
    </form>
</body>

</html>

Rによる統計解析

ここではphpはさておき、Rだけでスクリプトを書いてみます。
以下の記事を参考にしました。

analyze_risk_and_return.R
# install.packages("tidyverse", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("tidyquant", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("timetk", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("PerformanceAnalytics", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("GGally", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("ggrepel", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("patchwork", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("formattable", repos = "http://cran.us.r-project.org", dependencies=TRUE)
# install.packages("rjson", repos = "http://cran.us.r-project.org", dependencies=TRUE)
library(tidyquant)
library(tidyverse)
library(timetk)
# library(PerformanceAnalytics)
# library(GGally)
# library(ggrepel)
# library(patchwork)
library(formattable)
library(rjson)

get_start_date <- function(data) {
    data %>%
    summarise(start_date =  min(date)) %>%
    pull(start_date) %>%
    max()
}

align_start_date <- function(data) {
  start_date <- get_start_date(data)
  filter(data, date >= start_date)
}

add_cumret_dd <- function(data) {
  suppressWarnings(
    data %>%
    mutate(cum_ret = cumprod(1 + return) - 1,drawdown = Drawdowns(return))
  )
}

convert_to_xts <- function(data, name_col, value_col) {
  name_col <- enquo(name_col)
  value_col <- enquo(value_col)
  data %>%
    select(date, !!name_col, !!value_col) %>%
    pivot_wider(names_from = !!name_col, values_from = !!value_col) %>%
    tk_xts(-date, date)
}

calc_perf_metrices <- function(data) {
    xts_data <- convert_to_xts(data, symbol, return)

    map_dfr(names(xts_data), function(symbol) {
        ra <- xts_data[, symbol]
        data.frame(
            Symbol  = symbol,
            Sharpe  = round(as.numeric(SharpeRatio(ra, annualize = TRUE, FUN = "StdDev")), 2),
            Avg_Ret = as.numeric(Return.annualized(ra)),
            Cum_Ret = as.numeric(Return.cumulative(ra)),
            StdDev  = as.numeric(StdDev.annualized(ra)),
            MaxDD   = as.numeric(maxDrawdown(ra)),
            stringsAsFactors = FALSE
        )
    }) %>%
    mutate_at(vars(Avg_Ret, Cum_Ret, StdDev, MaxDD), percent, digits = 1) %>%
    arrange(-Sharpe)
}

args <- commandArgs(trailingOnly = TRUE)
symbol <- args[1]
start_date <- paste(as.POSIXlt(Sys.Date())$year + 1880, "-01-01", sep = "")
end_date <- paste(as.POSIXlt(Sys.Date())$year + 1899, "-12-31", sep = "")
rawdata <- tq_get(symbol, from = start_date, to = end_date)

data <- rawdata %>%
group_by(symbol) %>%
tq_transmute(adjusted, mutate_fun = dailyReturn, col_rename = "return") %>%
slice(-1)

data %>%
summarize(start = min(date), end = max(date), na = sum(is.na(return)))

analysis_result <- list()
analysis_result$data <- data %>%
    align_start_date() %>%
    add_cumret_dd()

analysis_result$pref_table <- calc_perf_metrices(analysis_result$data)
cat(toJSON(analysis_result$pref_table))

まず、phpの実行ユーザ権限でパッケージをインストールする必要があるので、初回実行時だけinstall.packages()関数でパッケージをインストールします。
インストールログはApacheのerror.logに出力されるので、これをtailで流しながらインストールすると安心です。
メインプログラムは以下の手順で処理を実行します。

  1. 引数からティッカーを取得する
  2. ティッカーのヒストリカルデータをYahoo!Financeからダウンロードする
  3. ヒストリカルデータからリターンを計算し、行列に追加する
  4. ヒストリカルデータからドローダウンを計算し、行列に追加する
  5. 各種指標を計算し、toJSON()関数でエンコードしてcat()関数で標準出力する

解析結果の表示

POSTされたシンボルをRに渡し、結果を表示する処理をphpに記述します。
こちらのサイトを参考に開発します。

quants.php
<?php
require_once('portfoliomaker/setup.php');
$smarty = new Smarty_PortfolioMaker;
if (isset($_POST['ticker'])) {
    $ticker = $_POST['ticker'];
    $enc_ticker = json_encode($ticker);

    if (file_exists('C:/xampp/php/pear/portfoliomaker/R/analyze_risk_and_return.R')) {
        $cmd = "Rscript.exe --vanilla C:/xampp/php/pear/portfoliomaker/R/analyze_risk_and_return.R $enc_ticker";
        exec($cmd, $response);
        $res = json_decode($response[4]);
        $smarty->assign('ticker', $symbol);
        $smarty->assign('Avg_Ret', $res->Avg_Ret);
        $smarty->assign('StdDev', $res->StdDev);
    } else {
        print_r("Rスクリプトがないよ。");
        print_r(__DIR__);
    }
}
$smarty->display('quants.tpl');
?>

今回はRに渡す値が1つしかないので、JSON形式である必要は全く無いですが、後学のためにフォームからPOSTされた値をjson_encode()関数でJSON形式にエンコードしてから、Rscript.exeの引数に渡します。
実行するスクリプトは先程作ったanalyze_risk_and_return.Rです。
出力したい値をassign()関数で割り当てておき、quants.tplでマークアップします。

quants.tpl
<!DOCTYPE html>
<html lang = “ja”>
    <head>
        <meta charset = “UFT-8”>
        <title>さいきょうのポートフォリオを作ろう!</title>
    </head>
    <body>
        <h1>ETFのリスクとリターン</h1>
        <div>
            <p>シンボル:{$symbol}</p>
            <p>リスク:{$StdDev}</p>
            <p>リターン:{$Avg_Ret}</p>
        </div>
    </body>
</html>

まとめ

これでコーディングは完了しました。
あとは、input.phpにお目当てのティッカーを入力して送信すれば、quants.phpがヒストリカルデータを引っ張ってきてリスクとリターンを出力してくれます。
次回は複数の銘柄を入力し、相関係数を出力するプログラムに発展させてみましょう。