K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

Quantstats

05.1.12

Quantstats

最終更新 2020-07-01 読了時間 4 分
まとめ
QuantStats は、日次リターンの系列を渡すとシャープレシオ・最大ドローダウン・年率リターンなど数十の指標と、累積リターンやドローダウンの図をまとめた HTML レポートを 1 行で作る Python ライブラリです。このページでは qs.utils.download_returns("GOOG", period="2y") で Google の 2 年分の日次リターンを取り、qs.stats.sharpe で数値を 1 つ、qs.plots.snapshot で図を 1 枚、qs.reports.html(..., "VTI") で全米株式 ETF の VTI をベンチマークにしたレポートを出します。後半では yfinance で MSFT・AAPL・AMZN・GOOG・TSLA の株価を取り、pct_change() の変化率に比重 0.3 / 0.3 / 0.2 / 0.1 / 0.1 を np.dot で掛けてポートフォリオのリターン系列を作り、同じ qs.reports.html に渡します。読み終えると、「1 本のリターン系列を用意すればあとは QuantStats に任せる」という流れで任意の銘柄やポートフォリオの成績表が作れます。

直感 #

ある銘柄やポートフォリオが「良かったか」を判断するには、リターンの大きさだけでなく、途中でどれだけ下落したか(最大ドローダウン)、揺れに対してどれだけ報われたか(シャープレシオ)、市場平均に勝っていたかを見る必要があります。これらを自分で計算して図にするのは数十行の作業ですが、QuantStats は「日ごとに何 % 増えたか」の 1 本の Series を渡すだけで全部を計算し、ベンチマークと並べた HTML を出力します。ポートフォリオも、各銘柄の日次変化率を比重で加重平均すれば 1 本の Series になるので、個別銘柄とまったく同じ手順でレポートが作れます。

1
2
3
4
5
6
import numpy as np
import pandas as pd
import quantstats as qs
import yfinance as yf

qs.extend_pandas()

VTI と特定の銘柄の比較レポート #

GOOG の 2 年分のリターンを取り、シャープレシオ、スナップショット図、VTI との比較レポートの 3 つを順に出します。

1
2
3
4
5
GOOG_returns = qs.utils.download_returns("GOOG", period="2y")
GOOG_returns.head(10)
qs.stats.sharpe(GOOG_returns)
qs.plots.snapshot(GOOG_returns, title="GOOG Performance")
qs.reports.html(GOOG_returns, "VTI", download_filename="GOOGとVTIの比較.html")

VTIと特定の銘柄の比較レポート figure

複数銘柄のパフォーマンス比較レポート #

MSFT・AAPL・AMZN・GOOG・TSLA を 0.3 / 0.3 / 0.2 / 0.1 / 0.1 の比重で保有したポートフォリオのリターンを、VTI と比較します。QuantStats に渡せるのは「日ごとの変化率」の系列なので、株価から pandas.DataFrame.pct_change で変化率を求め、比重で加重平均して 1 本の Series にします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ポートフォリオ内での各銘柄の比重
stock_dict = {
    "MSFT": 0.3,
    "AAPL": 0.3,
    "AMZN": 0.2,
    "GOOG": 0.1,
    "TSLA": 0.1,
}

# 株価の系列
stock_prices_df = yf.download(
    list(stock_dict.keys()), start="2021-01-01", end="2022-01-01", adjusted=True
).dropna()

stock_prices_df.head()

Adj CloseClose...OpenVolume
AAPLAMZNGOOGMSFTTSLAAAPLAMZNGOOGMSFTTSLA...AAPLAMZNGOOGMSFTTSLAAAPLAMZNGOOGMSFTTSLA
Date
2020-12-31131.333542162.84649787.594002219.139343235.223328132.690002162.84649787.594002222.419998235.223328...134.080002163.75000086.771004221.699997233.33000299116600591440002023800020942100148949700
2021-01-04128.087051159.33149786.412003214.479111243.256668129.410004159.33149786.412003217.690002243.256668...133.520004163.50000087.876999222.529999239.820007143301900882280003803800037130100145914600
2021-01-05129.670715160.92550787.045998214.685989245.036667131.009995160.92550787.045998217.899994245.036667...128.889999158.30050786.250000217.259995241.2200019766490053110000229060002382300096735600
2021-01-06125.305786156.91900686.764503209.119339251.993332126.599998156.91900686.764503212.250000251.993332...127.720001157.32400585.131500212.169998252.830002155088000878960005204200035930700134100000
2021-01-07129.581635158.10800289.362503215.070267272.013336130.919998158.10800289.362503218.289993272.013336...128.360001157.85000687.002998214.039993259.209991109578200702900004530000027694500154496700

5 rows × 30 columns

1
2
returns_df = stock_prices_df["Adj Close"].pct_change().dropna()
returns_df.head()

AAPLAMZNGOOGMSFTTSLA
Date
2021-01-04-0.024719-0.021585-0.013494-0.0212660.034152
2021-01-050.0123640.0100040.0073370.0009650.007317
2021-01-06-0.033662-0.024897-0.003234-0.0259290.028390
2021-01-070.0341230.0075770.0299430.0284570.079447
2021-01-080.0086310.0064960.0111680.0060930.078403

5 rows × 5 columns

1
2
pf_returns = np.dot(list(stock_dict.values()), returns_df.T)
print(pf_returns[:10])
[-0.01530147  0.00900607 -0.01796814  0.02928912  0.0152212  -0.02670266
  0.00147395  0.01133958 -0.01243583 -0.00920841]
1
2
3
pf_returns_series = pd.Series(pf_returns, index=returns_df.index)

pf_returns_series.head(10)
Date
2021-01-04   -0.015301
2021-01-05    0.009006
2021-01-06   -0.017968
2021-01-07    0.029289
2021-01-08    0.015221
2021-01-11   -0.026703
2021-01-12    0.001474
2021-01-13    0.011340
2021-01-14   -0.012436
2021-01-15   -0.009208
dtype: float64
1
qs.reports.html(pf_returns_series, "VTI", download_filename="ポートフォリオのパフォーマンス.html")

使いどころと落とし穴 #

np.dot の加重平均は stock_dict のキーの順番と returns_df の列の順番が一致していることが前提で、yf.download は銘柄をアルファベット順に並べ替えることがあるので、returns_df[list(stock_dict.keys())] で列順を明示してから掛けるのが安全です。固定比重の加重平均は「毎日リバランスする」計算になるため、買い持ちの成績を正確に出したいときは株数を固定して評価額を追う必要があります。download_returns と yf.download はどちらも Yahoo Finance に依存しており、仕様変更で Adj Close 列が無くなることがあるので、エラーが出たら auto_adjust の既定値や列名を確認してください。

まとめ #

QuantStats の入力は「日次リターンの Series」1 本です。個別銘柄なら qs.utils.download_returns("GOOG", period="2y") で直接取れ、ポートフォリオなら yf.download の Adj Close を pct_change() して比重で np.dot すれば同じ形になります。あとは qs.stats.sharpe で数値、qs.plots.snapshot で図、qs.reports.html(returns, "VTI", download_filename=...) でベンチマーク比較の HTML が出るので、リターン系列を用意する部分だけに集中すれば任意の銘柄・配分の成績表が作れます。