9 株式投資への応用例

会計発生高アノマリーの検証

2024-06-07


1 会計利益とアクルーアルズ

1.1 会計利益の構成要素

1.2 CFOとアクルーアルズの持続性の相違

  • 実際,推定してみるとざっくり…

\[ \begin{align*} \underbrace{\hat{\beta}_{2}}_{\substack{\textbf{Persistence of accruals}\\{\textbf{0.45}}}} < \underbrace{\hat{\alpha}_{1}}_{\substack{\textbf{Persistence of net income}\\{\textbf{0.55}}}} < \underbrace{\hat{\beta}_{1}}_{\substack{\textbf{Persistence of CFO}\\{\textbf{0.60}}}} \end{align*} \]

1.3 両者を区別することなく投資すれば…

  • Sloan (1996)の主張 — Stock prices are found to act as if investors “fixate” on earnings, failing to reflect fully information contained in the accrual and cash flow components of current earnings…
  • 予測: 過大に評価された\(ACC_{i,t}\)が大きい銘柄ほど,将来リターンは有意に低い

2 アクルーアルズ・アノマリーの分析

2.1 検証の大まかな流れ

  • 検証の大まかな流れは,以下の通りである.

2.2 \(\mathit{ACC}_{i,t}\)を計算しよう

\[ \underbrace{\mathit{ACC}_{i,t}}_{\textbf{$t$期のアクルーアルズ}} \equiv \frac{\overbrace{X_{i,t}}^{\textbf{$t$期の当期純利益}}~~~ - ~~~\overbrace{\mathit{CFO}_{i,t}}^{\textbf{$t$期のCFO}}}{\underbrace{\mathit{TA}_{i,t-1}}_{\textbf{$t-1$期末の資産合計}}} \]

2.3 財務データの読み込みとACCの計算

  • \(\mathit{ACC}_{i,t}\)を計算するため,simulation_dataフォルダに格納されている財務データfinancial_data2.csvを改めて読み込もう(5行目)
  • その上で,group_by()関数によりfirm_IDでグループ化(9行目)した後,1年前の資産合計をlagged_TAとして計算(10行目)し,\(t\)期のアクルーアルズACCも前スライドの定義に従って計算(11行目)したのが以下のコードである.
# tidyverseの読み込み
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# 財務データの読み込み
financial_data <- read_csv("../simulation_data/financial_data2.csv")
Rows: 22855 Columns: 7
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl  (6): firm_ID, year, macc, X, TA, CFO
date (1): fiscal_year_end

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# アクルーアルズACCを計算し,データフレームに追加
financial_data <- financial_data %>% 
  group_by(firm_ID) %>% # firm_IDでグループ化
  mutate(lagged_TA = lag(TA), # 前期末の資産合計をlagged_TAと定義
         ACC = ifelse(macc == 12, (X - CFO) / lagged_TA, NA)) %>% # 定義に従いACCを計算
  ungroup() # グループ化の解除

2.4 投資ユニバースの確定

目標

    • (1) ACCが計算可能で,かつ,(2) 3月決算企業のみを抽出し,そのデータフレームを投資ユニバースuniverseとして定義しよう.
# lubridateの読み込み
library(lubridate)

# ACCが計算可能で,かつ3月決算企業のみを抽出してuniverseとして保存
universe <- financial_data %>% 
  drop_na(ACC) %>% # ACCが欠損のものを除外
  filter(month(fiscal_year_end) == 3) # 3月決算企業のみを抽出
  • 初出のlubridateパッケージは,日付型を扱うのに便利な関数を提供している(教科書376頁)
  • 例えば,上述のコードではYYYY-MM-DD形式で収録されているfiscal_year_endlubridateパッケージのmonth()関数を適用することにより,決算月を取り出している.

2.5 年度ごとに十分位ポートフォリオの作成

目標

  • 先に作成したuniverseを用いて,年度yearごとACCの大きさに応じた十分位ランクACC_rank10列を作成し,それを新しいデータフレームACC_rank10_dataとして定義しよう.
  • ただし,ACC_rank10_dataは,firm_ID, year, ACC_rank10の三列のみによって構成すること.
    • (ヒント) ランク分けにはdplyrパッケージに含まれるntile()関数を使うのが便利である.ntile()関数は,最初の引数としてグループ分けしたいデータ(ここではACC)を取り,その次の引数にグループの数(ここでは10)を取る.

2.6 目標を達成するためのコード例

# 年度ごとにACCの大きさに応じて十分位ポートフォリオを作成
ACC_rank10_data <- universe %>% 
  group_by(year) %>% # 年でグループ化
  mutate(ACC_rank10 = as.factor(ntile(ACC, 10))) %>% # 十分位ランクを作成
  ungroup() %>% # グループ化の解除 
  select(firm_ID, year, ACC_rank10) # 必要なデータだけ残す
  • 3行目: group_by()関数を使ってyearでグループ化
  • 4行目: ntile()関数を使ってACCの大きさに基づいて十分位ランクACC_rank10を作成.ntile()関数の返り値は整数型であることを思い出そう.十分位ランクを表すACC_rank10はカテゴリカル変数(教科書第3.6.1節)であるため,ファクター型へと変換することを目的としてas.factor(ntile(ACC, 10))を用いている.
  • 5行目: ungroup()関数によってグループ化の解除.
  • 6行目: 三変数のみ選択.

2.7 クロス集計による確認

# table()関数を用いてクロス集計

table(ACC_rank10_data$year, ACC_rank10_data$ACC_rank10)
      
         1   2   3   4   5   6   7   8   9  10
  2006 104 104 104 104 104 104 103 103 103 103
  2007 107 107 106 106 106 106 106 106 106 106
  2008 109 109 109 109 109 109 109 109 108 108
  2009 111 111 111 111 111 111 111 110 110 110
  2010 113 113 113 113 113 112 112 112 112 112
  2011 116 116 116 115 115 115 115 115 115 115
  2012 119 119 118 118 118 118 118 118 118 118
  2013 119 119 119 119 119 119 119 119 119 119
  2014 121 121 121 121 121 120 120 120 120 120
  2015 118 118 118 118 118 118 118 118 118 118
  2016 116 116 115 115 115 115 115 115 115 115
  2017 114 113 113 113 113 113 113 113 113 113
  2018 113 113 112 112 112 112 112 112 112 112
  2019 110 110 110 110 110 110 109 109 109 109
  2020 109 109 109 108 108 108 108 108 108 108
  • 基本パッケージに含まれる table()関数は,引数に集計対象となる変数(数値ベクトル)を与えると,クロス集計の結果を返してくれる.

2.8 月次リターンデータの読み込み

  • 次に,分析の中心となる月次リターンデータ(simulation_dataフォルダのmonthly_return_data.csv)をread_csv()関数により読み込もう.
# 月次リターン・データの読み込み
monthly_return_data <- read_csv("../simulation_data/monthly_return_data.csv")
Rows: 202548 Columns: 3
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl  (2): firm_ID, Re
date (1): date

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(monthly_return_data)
# A tibble: 6 × 3
  firm_ID date          Re
    <dbl> <date>     <dbl>
1       8 2009-07-01 11.5 
2       8 2009-08-01 13.7 
3       8 2009-09-01 20.7 
4       8 2009-10-01 -2.11
5       8 2009-11-01  6.04
6       8 2009-12-01  2.15
  • date: 年月 (YYYY-MM-DD形式)
  • Re: 各銘柄の無リスク金利に対する月次超過リターン (\(= \underbrace{R_{i,t}}_{\textbf{銘柄iの月次リターン}} - \underbrace{R_{F,t}}_{\textbf{同月の無リスク金利}}\))

2.9 ACC_rank10情報結合に向けた前処理

  • \(t\)年6月末時点でポートフォリオを組成することを鑑み,\(t\)年7月から\(t + 1\)年6月までの月次リターンデータに\(t\)年度のACC_rank10を結合する必要がある点がポイントである.

  • それを実現するために,次頁のコードではmonthly_return_datayear列を追加する際,if_else()関数を適用してyearを調整している.
    • dateが7月以上ならば,dateの年をそのまま適用
    • そうでなければ(dateが6月以下ならば),dateの年から1を差し引く

2.10 コードの実行結果

# t年7月から運用を開始することを前提にyear列の追加
monthly_return_data <- monthly_return_data %>% 
  mutate(year = if_else(month(date) >= 7, 
                        year(date), 
                        year(date) - 1)) 

2.11 ACC_rank10情報の結合

  • こうしてfirm_IDyearをキーとして,monthly_return_dataACC_rank10_dataを結合させることができるので,monthly_return_dataを基にleft_join()関数を使ってACC_rank10_dataを結合したのが以下のコードである.
# firm_IDとyearをキーに,月次リターン・データにACC_rank10情報を結合
monthly_return_data <- monthly_return_data %>% 
  left_join(ACC_rank10_data, c("firm_ID", "year")) 

# 結合後のデータ確認
head(monthly_return_data) 
# A tibble: 6 × 5
  firm_ID date          Re  year ACC_rank10
    <dbl> <date>     <dbl> <dbl> <fct>     
1       8 2009-07-01 11.5   2009 8         
2       8 2009-08-01 13.7   2009 8         
3       8 2009-09-01 20.7   2009 8         
4       8 2009-10-01 -2.11  2009 8         
5       8 2009-11-01  6.04  2009 8         
6       8 2009-12-01  2.15  2009 8         

2.12 各ポートフォリオの運用結果を集計

  • 等加重ウェイトで運用した場合の各月各ポートフォリオの実現超過リターンは,ポートフォリオ内の各銘柄の超過リターンを単純平均することで求められる(等加重ポートフォリオと時価総額加重ポートフォリオとの計算方法の相違は,教科書コラム6.1参照)
  • dateACC_rank10でグループ化し,summarize()関数を使って各ポートフォリオの運用結果を集計し,新しいデータフレームACC_sorted_portfolioを作成しよう.
# 等加重ウェイトでの運用を前提に,各月各ポートフォリオの実現超過リターンを計算
ACC_sorted_portfolio <- monthly_return_data %>% 
  group_by(date, ACC_rank10) %>% # dateとACC_rank10でグループ化
  summarize(Re = mean(Re)) %>% # 月次超過リターンの平均値を計算
  ungroup() # グループ化の解除
`summarise()` has grouped output by 'date'. You can override using the
`.groups` argument.

2.13 最小アクルーアルズ・ポートフォリオの運用結果

# 最小アクルーアルズ・ポートフォリオの運用結果のみを抽出して表示
ACC_sorted_portfolio %>%
  filter(ACC_rank10 == 1) %>% # 最小アクルーアルズ・ポートフォリオを抽出
  head() # head()関数により確認
# A tibble: 6 × 3
  date       ACC_rank10     Re
  <date>     <fct>       <dbl>
1 2006-07-01 1          -2.82 
2 2006-08-01 1           0.682
3 2006-09-01 1           0.569
4 2006-10-01 1           4.81 
5 2006-11-01 1           3.36 
6 2006-12-01 1           0.549

3 アルファの実務的な意味合い

3.1 CAPMを前提としたアルファ

3.2 パフォーマンス評価尺度としてのアルファ(教科書第6.4節)

  • 証券投資の実務においては,アルファはファンド運用者の運用スキルの評価基準として利用される.
  • この場合,CAPMを始めとするファクター・モデル1はファンド運用のリスクに応じた適切なリターンのベンチマークとして利用されており,アルファはファンド運用者の固有の付加価値を表すものとして,彼らの報酬決定の基準となる.

3.3 FF3アルファの推定

  • 手始めに,アクルーアルズが最小のポートフォリオ (ACC_rank101)のアルファを推定する方法を考えてみよう.
  • なお,ベンチマークとするモデルはCAPMではなく,より実践的にFama-Frenchの3ファクター・モデル (FF3モデル)としよう.そのモデルを前提とした場合のアルファ,すなわちFF3アルファは,次の回帰モデルを推定することによって求められる.

\[ \begin{align*} \underbrace{R_{P,t}^{e}}_{\substack{\textbf{ポートフォリオ$P$の} \\ \textbf{実現超過リターン}}} & = \alpha_{P}^{\mathit{FF3}} + \beta_{P}^{M} \underbrace{R_{M,t}^{e}}_{\substack{\textbf{市場ポートフォリオの} \\ \textbf{実現超過リターン}}} \\ & \qquad + \beta_{P}^{\mathit{SMB}} \underbrace{\mathit{SMB}_{i,t}}_{\textbf{サイズ・ファクター}}+ \beta_{P}^{\mathit{HML}}\underbrace{\mathit{HML}_{i,t}}_{\textbf{バリュー・ファクター}} + \varepsilon_{P,t} \end{align*} \]

3.4 ファクター・データの読み込み

# ファクター・データの読み込み
factor_data <- read_csv("../simulation_data/factor_data.csv") 
Rows: 715 Columns: 5
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl  (4): R_F, R_Me, SMB, HML
date (1): date

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(factor_data) # head()関数を用いて確認
# A tibble: 6 × 5
  date         R_F   R_Me    SMB    HML
  <date>     <dbl>  <dbl>  <dbl>  <dbl>
1 1963-07-01 0.270 -0.390 -0.41  -0.970
2 1963-08-01 0.25   5.07  -0.800  1.8  
3 1963-09-01 0.270 -1.57  -0.520  0.13 
4 1963-10-01 0.290  2.53  -1.39  -0.1  
5 1963-11-01 0.270 -0.850 -0.88   1.75 
6 1963-12-01 0.290  1.83  -2.10  -0.02 
  • date: 年月 (YYYY-MM-DD形式)
  • R_F: 無リスク金利
  • R_Me: 市場ポートフォリオの実現超過リターン
  • SMB: サイズ・ファクターの実現リターン
  • HML: バリュー・ファクターの実現リターン

3.5 ファクター・データの結合

  • ACC_sorted_portfolioに,先ほど読み込まれたfactor_datadateをキーとして結合したのが以下のコードである.
# ACC_sorted_portfolioにファクター・データを追加
ACC_sorted_portfolio <- ACC_sorted_portfolio %>% 
  left_join(factor_data, by = "date")

head(ACC_sorted_portfolio) # ファクター・データ結合後のデータ確認
# A tibble: 6 × 7
  date       ACC_rank10    Re   R_F   R_Me   SMB   HML
  <date>     <fct>      <dbl> <dbl>  <dbl> <dbl> <dbl>
1 2006-07-01 1          -2.82 0.400 -0.780 -3.64  2.62
2 2006-07-01 2          -1.48 0.400 -0.780 -3.64  2.62
3 2006-07-01 3          -2.73 0.400 -0.780 -3.64  2.62
4 2006-07-01 4          -1.72 0.400 -0.780 -3.64  2.62
5 2006-07-01 5          -2.65 0.400 -0.780 -3.64  2.62
6 2006-07-01 6          -3.29 0.400 -0.780 -3.64  2.62

3.6 時系列回帰

  • ここでは手始めにアクルーアルズが最小のポートフォリオ(ACC_rank101) のデータのみを抽出して,先に提示した回帰モデルを推定してみよう.
  • これは時系列回帰 (time-series regression)といって,個々の銘柄やポートフォリオの実現超過リターンが,市場ポートフォリオの実現超過リターンなどのファクター・リターンによってどの程度説明できるかを回帰したモデルである.

3.7 時系列回帰の実行コード

# アクルーアルズが最小のポートフォリオのみで時系列回帰
lm_results <- ACC_sorted_portfolio %>%
  filter(ACC_rank10 == 1) %>% #最小ポートフォリオを抽出
  lm(Re ~ R_Me + SMB + HML, data = .) # .を使ってlm()関数の第二引数にデータを代入
  • 2行目: これから行う線形回帰の結果をlm_resultsへと保存
  • 3行目: filter()関数により,ACC_rank10が最小のポートフォリオのみを抽出.
  • 4行目: 線形回帰はlm()関数によって実行できる.その関数は,第一引数で演算子を用いて回帰モデルを記述(従属変数 独立変数1 + 独立変数2 + 以降省略)し,第二引数でデータを指定.data引数内で登場するピリオド(.) は,パイプ演算子%>%を用いてデータフレームを第一引数以外に代入する場合に用いられる(教科書211頁)

3.8 リスト形式からデータフレーム形式へ

  • lm()関数の返り値はリスト形式のため,そのままだと扱いが厄介.例えば,lm_resultsの1つ目の要素には係数推定値のみが格納されている.
# リスト形式のlm_resultsの1つ目の要素を参照
print(lm_results[[1]])
(Intercept)        R_Me         SMB         HML 
  0.3003874   1.2175961   1.0029065   0.3734310 
  • broomパッケージに含まれるtidy()関数を使って,係数推定値のみならず,標準誤差,\(t\)値,\(p\)値の情報をも含んだ一つのデータフレームへ変換するのが便利である.
# broomパッケージの読み込み
library(broom)

3.9 tidy()関数の挿入

# アクルーアルズが最小のポートフォリオのみで時系列回帰
ACC_sorted_portfolio %>%
  filter(ACC_rank10 == 1) %>% #最小ポートフォリオを抽出
  lm(Re ~ R_Me + SMB + HML, data = .) %>% # .を使ってlm()関数の第二引数にデータを代入
  tidy() %>% # リスト形式からデータフレームへと変換
  mutate(ACC_rank10 = 1) # 失われたACC_rank10情報を追加
# A tibble: 4 × 6
  term        estimate std.error statistic   p.value ACC_rank10
  <chr>          <dbl>     <dbl>     <dbl>     <dbl>      <dbl>
1 (Intercept)    0.300    0.0736      4.08 6.72e-  5          1
2 R_Me           1.22     0.0175     69.7  4.38e-130          1
3 SMB            1.00     0.0317     31.6  1.60e- 74          1
4 HML            0.373    0.0254     14.7  1.85e- 32          1
  • 5行目: broomパッケージのtidy()関数を使って,係数推定値等をデータフレームに変換している.
  • 6行目: どのポートフォリオの結果であるかが分かるように,ACC_rank10列を追加.

3.10 for文を使って分位ごとに推定

3.11 for文を使ったコードの書き方

for文とは?

for (Variable Name in Sequence) {
  Do Something
}

# ACC_rank10ごとにFF3モデルを推定し,FF3_resultsに保存
FF3_results <- list(NA) # ①推定結果を保存するために空のリストを準備

# ②for文を使ってACC_rank10ごとに時系列回帰を行い,FF3_resultsの第i要素に代入
for (i in 1:10) {
  FF3_results[[i]] <- ACC_sorted_portfolio %>%
    filter(ACC_rank10 == i) %>% 
    lm(Re ~ R_Me + SMB + HML, data = .) %>% 
    tidy() %>% 
    mutate(ACC_rank10 = i) 
}
  • 第1ポートフォリオから第10ポートフォリオまで次々に推定を行いたいわけだからfor (i = 1:10) {}と書き,{}には先の第1ポートフォリオの推定コードをそのまま適用し,1の部分をiに変更すれば良い.

3.12 データフレームへの統合

  • 異なるポートフォリオ間で推定結果を比較するには,リスト内に格納されている各データフレームを一つのデータフレームに統合する方が便利である.FF3_results内に保存されている各データフレームを縦方向に統合していくにはdplyrbind_rows()関数を用いるのが簡単な方法である1
# 各ポートフォリオの推定結果を一つのデータフレームに統合

binded_FF3_results <- bind_rows(FF3_results) 
head(binded_FF3_results) # binded_FF3_resultsの確認
# A tibble: 6 × 6
  term        estimate std.error statistic   p.value ACC_rank10
  <chr>          <dbl>     <dbl>     <dbl>     <dbl>      <int>
1 (Intercept)    0.300    0.0736      4.08 6.72e-  5          1
2 R_Me           1.22     0.0175     69.7  4.38e-130          1
3 SMB            1.00     0.0317     31.6  1.60e- 74          1
4 HML            0.373    0.0254     14.7  1.85e- 32          1
5 (Intercept)    0.316    0.0720      4.39 1.97e-  5          2
6 R_Me           1.06     0.0171     62.3  7.58e-122          2

3.13 年次換算されたFF3アルファを出力

目標

  • binded_FF3_resultsからFF3アルファ(Intercept)の結果だけ抽出し,FF3_alpha.csvとしてtablesフォルダに出力しよう.
    • ただし,estimate列はFF3_alphaに,p.value列はp_valueへと名称変更しよう.
    • FF3_alphaを年次換算するため,12を掛け,また小数第三位までが表示されるよう桁数調整.
    • 出力するのは,ACC_rank10, FF3_alpha, p_valueの三変数のみ.
  • (ヒント1) term列から(Intercept)の行だけ抽出する場合,term列は文字列<chr>であるため,(Intercept)を指定する際はダブルクォーテーション (")で囲み,"(Intercept)"とする必要がある.
  • (ヒント2) 変数の名前変更は,rename(新しい変数名 = 古い変数名)により可能である.

3.14 目標を達成するためのコード例

# FF3アルファの結果だけをまとめてFF3_alpha.csvとして保存
binded_FF3_results %>% 
  filter(term == "(Intercept)") %>% # 定数項に関する推定結果のみを抽出
  rename(FF3_alpha = estimate, p_value = p.value) %>% # 列名を変更
  mutate(FF3_alpha = round(FF3_alpha * 12, 3)) %>% # 年率に換算
  select(ACC_rank10, FF3_alpha, p_value) %>% # 出力したい列を指定 
  write_csv("../tables/FF3_alpha.csv") # tablesフォルダに出力
  • 3行目: filter()関数により(Intercept)行のみ抽出.注意すべきは,term列は文字列<chr>であるため,(Intercept)を指定する際はダブルクォーテーション (")で囲む.
  • 4行目: rename()関数を使って,estimatep.valueのそれぞれを適切な名前へと変更.
  • 5行目: FF3_alphaを年次換算するため,12を掛け,またround()関数により桁数の調整も行う.
  • 6行目: select()関数に出力したい列のみ選択.
  • 7行目: write_csv()関数によりファイルを出力.

4 参考文献

Larson, C. R., R. Sloan, and J. Zha Giedt. 2018. Defining, measuring, and modeling accruals: a guide for researchers. Review of Accounting Studies 23 (3): 827–871.