はじめに
早速ですが、皆さんは投資をしているでしょうか。しているとすれば、どのような投資をしていらっしゃるでしょうか。
世の中には様々な投資対象が存在し、またその投資手法も様々です。投資に関する情報は世の中に溢れています。氾濫していると言ったほうがよいかもしれません。書籍を例に取ると、甘い文句で投資を奨励するライトな入門書から金融の専門書までずらりと並びます。またブログやSNSも重要な情報源となっており、最近では投資向けのYouTubeも人気を集めているようです。
しかしこれだけ多様な情報ソースが存在するにも関わらず、投資で成功を収めることができるのはごく一握りです。少し古いリサーチになりますが2015年の野村證券の個人投資家リサーチでは、通算で利益が出ている個人投資家の割合は9.3%とのことです。どうしてこのような事態に陥ってしまうのでしょうか。投資の初級者の方はどのようなアプローチをしているのか、いくつか例を挙げてみましょう。
- 流行りのテーマ株の高騰に飛びついて高値掴みをしてしまう
- バフェットの真似をして四季報から割安成長株を選んでみる
- 聞きかじった知識を元にPERやROEで銘柄をスクリーニングしてみる
- 配当狙いで銘柄を保有してみる
- 雑誌のアナリストが推していた銘柄を購入してみる
- 企業決算や経済指標の発表に合わせて取引してみる
- 流行りのESG企業を買ってみる
- テクニカルを使ってチャートから上昇するか下落するか予想してみる
このような手法を鵜呑みにトレードしてしまい、勤労時間の昼休みが来るたびに含み損を見て落胆している方もいるのではないでしょうか。もう一度言いますが、なぜこのような事態に陥ってしまうのでしょうか。それは、そもそも初級者の方はどのようなトレーディングスタイルが本質的に優位であるか知らないからです。
本記事の目的
本記事では兎にも角にも、初級者の方が持つ投資観に対して全く新しい気付きを与えることを念頭に置きました。トレーディングは科学です。科学的なトレーディングとは、すなわち計量的・実証的なトレーディングのことです。
本記事では統計的な考えに基づき、どのようなトレーディングスタイルが優位であるか示します。記事中では、初級者の方が平易に読むことができるようになるべく分かりやすい用語を選び、難しい数式や専門的な考察は可能な限り省略するようにしました。
また、科学的なトレーディングを実行に移すための具体的な手法について手ほどきします。計量的・実証的なトレーディングを行うためにはプログラミングは必須です。本記事ではPythonという言語を用いて必要最低限の検証を行うためのプログラムを記しました。プログラミングと聞いて身構える人も多いでしょうが、この機会に勉強を始めてみるのもよいでしょう。何事も目的意識をもって取り組むことが上達への近道となります。
筆者の投資パフォーマンス
記事のはじめに、まず筆者のこれまでの投資のパフォーマンスを紹介しておきます。いきなり自分が儲けたカネの話をしていやらしいと思われるでしょうが、結果を出していない人が何を言っても説得力はないのです。なお一言加えておきますが、投資において最も重要なことは結果ではなくプロセス管理です。なぜなら後述するように、投資のパフォーマンスは運に左右される部分が大きく、結果のみに基づいて判断すると間違った結論を招いてしまう場合が多いからです。
筆者はもともと本職のエンジニアの傍らで投資をしていましたが、2014年から本格的に資産運用を開始しました。このときのスタートアップ資金は5000万円でした。2016年に現在主力となっている運用システムを開発しました。この運用システムは運用開始から現在までにおよそ1億4000万円を稼ぎ出している我々の旗艦システムです。運用は日本株のTOPIX500と呼ばれる大型銘柄が対象で、直近4年間の平均利回りは40%程度となっています。
問題提起
では冒頭に上げた初級者のアプローチではなぜ成功することができないのか、何が問題であるのか解説します。ここでいう成功とは、少なくとも5年以上の投資期間において途中で挫折することなく継続的・安定的に所望の利回りを得ることを指します。
まず冒頭の手法によって利益が出るのか出ないのか考えてみましょう。一例として割安でクオリティが高いと考えられる銘柄への投資を考えてみます。株の個別銘柄を選定するとき、証券会社のツールを使って銘柄をスクリーニングする方が多いでしょう。ここではPERが10倍以下でROEが10%以上の銘柄に1ヶ月間投資したときのパフォーマンスを見てみましょう。
集計期間は2017年4月から2020年10月であり、スクリーニングした銘柄を月初に購入して(正確には前月の終値で購入して)、当月の月末の終値で決済した場合の獲得利益の分布図(ヒストグラム)です。このとき、個別銘柄のリターンからマーケットのリターンを差し引いています。こうしなければ日経平均の急騰などの影響を受けてしまい、その獲得した利益が銘柄選定のスキルに依るものなのか、それとも運よくマーケット上昇の恩恵を受けただけなのか、切り分けできないからです。
さて、この投資手法で利益は出るのでしょうか。
結論として、利益が出る場合もあれば出ない場合もあります。あなたの投資した銘柄は、上図の分布のうち、最も右端(つまり利益が出た銘柄)かもしれません。はたまた、分布の最も左端(つまり損失が出た銘柄)かもしれません。上図の分布に含まれている銘柄は全てあなたの基準にとって「割安でクオリティが高い」銘柄です。この中から最終的な損益を決定づけるのは「割安でクオリティが高い」という事実ではなく、対象となった数ある銘柄の中からその時期にその銘柄をピックアップした、いわゆるあなたの指運だと言えます。それは例えば、たまたまその企業の名称が気に入ったものであるだとか、たまたまスクリーニングの一番上の段に表示されただとか、雑誌を読んで急に投資を思い立っただとか、そのような些細なことでしょう。
投資は、つまりガチャのようなもの
投資の結果は運に左右されます。というよりも殆どが運で決まるのです。ここでいう運とはすなわち上図のような確率分布のことを指します。上記の事例から分かるように投資の結果だけに着目すると、その投資そのものが上手くいったかどうかという判定は決してできないのです。あなたの行ったトレードの結果はご自身のスキルに依るものなのか偶然の産物なのか全く区別は付きません。
このように結果を正しく判断できないということは、その手法に依存してよいかどうかは分からないということです。つまりビジネスでいうところの改善のプロセスを回すことはできません。冒頭のようなアプローチでは、改善プロセスを回すという考え方が抜けているのです。改善プロセスを回すためには、定量的な結果を短期間でフィードバックさせる仕組みが必要となるのです。
もしも改善プロセスを回せないのであれば、あなたの投資スキルは未来永劫、決して向上することはないのです。これは中身の分からないガチャをひたすら回しているのと同じことです。あなたは年に数回ほど上記の排出分布の中から数個のガチャを引いて、その結果が良ければ歓喜し悪ければ落胆するという不毛な行為を繰り返しているにすぎないのです。
解決手段
ではこのガチャを攻略する方法はあるのでしょうか。当然、答えは「YES」です。
ガチャ台の素性
ガチャ台にも良いもの悪いものがあります。中身に当たりがたくさん入っている台は良い台、クズのようなものしか入っていない台は悪い台です。これらの台を排出分布で見てみるとどのようになっているのでしょうか。以下に3つの例を示します。
まず一番左の台はどうでしょうか。この台の排出分布の中心は殆ど0となっています。すわなちこの台の期待値は0です。この台でいくらガチャを引いてもあなたの資産が伸びることはないでしょう。一時的に利益が出ても、それは偶然パフォーマンスが上振れしているだけのことです。長い目でみると獲得した損益の平均値は必ず0に近づきます。
続いて真ん中の台はどうでしょう。この台の排出分布をよく見てみると、わずかに右側(プラス側)にシフトしていることが分かります。これは「期待値がプラス」である状態です。トレードでは、「エッジがある」「アルファ(期待超過収益)がある」などという表現をします。この台は、ガチャを引き続けていくことで右肩上がりに資産を伸ばすことができます。しかし道中の資産の上下変動は激しく、もしかするとあなたは途中でやめてしまうかもしれません。
最後に一番右の台です。この台の排出分布は真ん中の台と同じようにわずかに右側にシフトしています。そしてさらに目を凝らして見てみると、そのバラツキ(中心からの広がり)が真ん中の台と比べて小さな範囲に収まっていることが分かります。期待値がプラスで且つバラツキが小さい、すなわち「シャープレシオ」の高い台です。このようなガチャ台を見つけることができ、さらにそれを引き続けていくことであなたの成功は盤石なものとなるでしょう。
とにかく何回もガチャを回し続ける
ガチャを1回か2回引いた程度では、ガチャ台の中身がどのようなものなのか傾向を掴むことはできません。従ってもしも本当に中身の傾向を掴みたいのであれば、とにかく何回も何回も回し続けるしかありません。ここで重要なことは、わき目をふらず1つのガチャだけを回し続けるということです。途中まで引いて、こっちのガチャはあまり当たりが入ってなさそうだからこっちのガチャに替えてみる、というやり方では一生かかっても傾向を掴むことはできません。
つまりトレードでは、1つの手法だけに基づいて一貫してトレードを継続しなければならないのです。それも10回や100回そこらでは足りません。1000回を超えるような多数回施行をしなければならないのです。雑誌などの投資手法に次から次へと目移りしてはいけないのです。投資の持つ不確実性と戦うためには、とにかく試行回数を稼ぐしかありません。我々が持つ唯一の武器は大数の法則なのです。
従って、優位なトレーディング戦略とは「数をこなせる」戦略となります。個人投資家が行うトレードに月次以上のスパンのものは不適です。少なくともトレーディングは毎日行う必要があります。その最たるものはスキャルピングでしょう。また、トレーディングの期間が短くなればなるほど、価格の動きの不確実性は少なくなりその傾向を掴みやすくなるのです(具体的には投資の不確実性は投資期間の平方根に比例します)。
ガチャを引き続けるとお金がなくなっていく
しかし、当然の話ですがガチャを引くためにはお金が必要です。引けば引くだけあなたの財布からお金は消えていきます。これはトレードでも同じことです。トレードでは1回の取引に必ずコストが掛かります。株式であれば銘柄購入の際の手数料や信用金利、FXであれば1回のトレードでスプレッドを業者に支払っていることになります。何回もトレードを繰り返すとあっという間にあなたの資金は尽きてしまうでしょう。
実弾投入しながらガチャの中身の傾向を推定するのは、正直全く割りに合わない行為です。そんなことをするよりも、予め割の良いガチャ台に当たりを付ける方法があります。
データから予めガチャ台の中身を推定する
投資の世界では、我々は予めガチャ台に使われているデータの一部を集めることができます。そしてこのデータを使ってどのようなガチャ台の素性が良いのか、すなわちどのようなトレーディング戦略にベットすべきか、ガチャを引く前に検証することができます。これが計量的・実証的と呼ばれるトレーディング手法です。取引コストに打ち勝つことのできる期待値をデータ分析によって探すのです。
計量的・実証的なトレーディングに関する研究は長年行われています。特に最近では、これに機械学習を用いる事例も多数見掛けるようになりました。機械学習はデータの持つ統計的な有意性を確認できるだけではなく、誰も知らないような隠れた市場特性を抽出するためにも非常に有用です。プログラミングのライブラリは充実しており、データをダウンロードするためのAPIも整備されつつあります。個人投資家にも充分なデータ分析はできるのです。
そして運用へ
実際の運用では検証結果に基づいて可能な限り条件を固定して機械的にトレードを行わなければなりません。そこに人間の感情を挟んではいけないのです。そしてどうせ機械的に多数回のトレードを繰り返すのであれば、これを自動化しない理由はありません。このようなトレーディングスタイルは、包括的に「システムトレーディング」とも呼ばれています。
実際には、事前の検証(すなわちバックテスト)と実際の運用のパフォーマンスを比較しながら改善のプロセスを回すことが成功する投資のアプローチとなるのです。データ分析などしたことがないという人のために、以降の章ではPythonを使ったデータ分析の方法について紹介します。
計量的・実証的トレーディングの準備
さて、それではまずデータを集めます。データはYahoo FinanceのAPIを使って集めます。このときyfinanceというライブラリを使用しますので、まずこれをインストールします。yfinanceの詳細はこちらをご参照下さい。
pip install yfinance
(追記)yfinanceのバグについて
yfinanceにはバグがあり、デフォルトのままでは財務諸表データを取得できません。以下のようにyfinanceのbase.pyファイルを修正することで取得できるようになります。
# base.py 353行目付近
# get fundamentals
# data = utils.get_json(url+'/financials', proxy) ←デフォルトプログラム。マスクします。
url = "{}/{}/financials".format(self._scrape_url, self.ticker) # 追加
data = utils.get_json(url, proxy) # 追加
価格のヒストリカルデータ
では、まず日本の株式市場の銘柄について、価格データをダウンロードしてみましょう。
以下のコードを実行すると、一瞬で7203トヨタ自動車のヒストリカルデータを取得することができます。日本市場の銘柄のtickerシンボルは、証券コード+".T"で表されるため、特にYahoo Financeのシンボルを調べなくとも簡単にほぼ全ての銘柄のデータを取得することができます。
import yfinance as yf
ticker = yf.Ticker("7203.T")
hist = ticker.history(period="max")
print(hist)
<実行画面>
Open High Low Close Volume Dividends Stock Splits
Date
1999-05-06 2259.74 2337.44 2233.84 2337.44 3115000 0.0 0
1999-05-07 2324.49 2330.96 2233.84 2253.27 3033000 0.0 0
1999-05-10 2253.27 2279.16 2233.84 2246.79 1261000 0.0 0
1999-05-11 2266.22 2279.17 2227.37 2227.37 1686000 0.0 0
1999-05-12 2227.37 2266.21 2227.37 2266.21 2596000 0.0 0
... ... ... ... ... ... ... ...
2020-11-02 6866.00 7016.00 6850.00 6949.00 5721200 0.0 0
2020-11-04 7024.00 7054.00 6976.00 6976.00 6278100 0.0 0
2020-11-05 6955.00 7032.00 6923.00 6984.00 5643400 0.0 0
2020-11-06 7070.00 7152.00 7015.00 7019.00 11092900 0.0 0
2020-11-09 7159.00 7242.00 7119.00 7173.00 7838600 0.0 0
[5324 rows x 7 columns]
損益計算書
続いて財務諸表のデータを見てみましょう。まずは損益計算書からです。
以下のコードから直近およそ3年分の損益計算書を取得できます。この中で特に重要なのは、Total Revenue(売上高)、Operating Income(営業利益)、Net Income(当期純利益)でしょうか。
financials = ticker.financials
print(financials)
<実行画面>
2020-03-31 2019-03-31 2018-03-31 2017-03-31
Research Development None None None None
Effect Of Accounting Charges None None None None
Income Before Tax 2.82576e+12 2.64553e+12 3.09051e+12 2.55588e+12
Minority Interest 6.77064e+11 7.18985e+11 6.9412e+11 6.68264e+11
Net Income 2.07618e+12 1.88287e+12 2.49398e+12 1.83111e+12
Selling General Administrative 2.97317e+12 2.9867e+12 3.0905e+12 2.86848e+12
Gross Profit 5.40763e+12 5.4439e+12 5.49036e+12 4.86286e+12
Ebit 2.43446e+12 2.4572e+12 2.39986e+12 1.99437e+12
Operating Income 2.43446e+12 2.4572e+12 2.39986e+12 1.99437e+12
Other Operating Expenses None None None None
Interest Expense -3.2217e+10 -2.8078e+10 -2.7586e+10 -2.9353e+10
Extraordinary Items None None None None
Non Recurring None None None None
Other Items None None None None
Income Tax Expense 6.8343e+11 6.59944e+11 5.04406e+11 6.289e+11
Total Revenue 2.993e+13 3.02257e+13 2.93795e+13 2.75972e+13
Total Operating Expenses 2.74955e+13 2.77685e+13 2.69796e+13 2.56028e+13
Cost Of Revenue 2.45224e+13 2.47818e+13 2.38892e+13 2.27343e+13
Total Other Income Expense Net 3.91297e+11 1.8833e+11 6.9065e+11 5.61513e+11
Discontinued Operations None None None None
Net Income From Continuing Ops 2.14233e+12 1.98559e+12 2.58611e+12 1.92698e+12
Net Income Applicable To Common Shares 2.0589e+12 1.86808e+12 2.48169e+12 1.82131e+12
貸借対照表(バランスシート)
次は貸借対照表です。
以下のコードから直近およそ3年分の貸借対照表を取得できます。この中で特に重要なのは、Total Assets(総資産)、Total Liab(総負債)、Total Stockholder Equity(自己資本)でしょうか。
balance_sheet = ticker.balance_sheet
print(balance_sheet)
<実行画面>
2020-03-31 2019-03-31 2018-03-31 2017-03-31
Capital Surplus 4.893340e+11 4.871620e+11 4.875020e+11 4.840130e+11
Total Liab 3.194275e+13 3.186981e+13 3.087815e+13 3.056711e+13
Total Stockholder Equity 2.006062e+13 1.934815e+13 1.873598e+13 1.751481e+13
Minority Interest 6.770640e+11 7.189850e+11 6.941200e+11 6.682640e+11
Other Current Liab 4.102642e+12 4.479344e+12 4.399669e+12 3.979935e+12
Total Assets 5.268044e+13 5.193695e+13 5.030825e+13 4.875019e+13
Common Stock 3.970500e+11 3.970500e+11 3.970500e+11 3.970500e+11
Other Current Assets 2.469880e+11 1.425310e+11 2.022920e+11 1.235700e+10
Retained Earnings 2.342761e+13 2.198752e+13 1.947346e+13 1.760107e+13
Other Liab 2.746823e+12 2.887743e+12 2.902003e+12 3.163780e+12
Treasury Stock -4.253379e+12 -3.523575e+12 -1.622034e+12 -9.673210e+11
Other Assets 8.905140e+11 1.182809e+12 1.067759e+12 1.012639e+12
Cash 2.774498e+12 2.790212e+12 2.390524e+12 2.257064e+12
Total Current Liabilities 1.790238e+13 1.822694e+13 1.779689e+13 1.731896e+13
Deferred Long Term Asset Charges 3.547850e+11 5.018720e+11 4.941200e+11 5.039850e+11
Short Long Term Debt 1.418710e+11 1.560380e+11 1.674550e+11 2.285990e+11
Other Stockholder Equity -1.166273e+12 -9.166500e+11 4.356990e+11 6.409220e+11
Property Plant Equipment 1.087864e+13 1.068549e+13 1.026767e+13 1.019711e+13
Total Current Assets 1.864253e+13 1.887924e+13 1.815266e+13 1.783370e+13
Long Term Investments 1.184489e+13 1.090829e+13 1.133854e+13 1.069452e+13
Net Tangible Assets 2.006062e+13 1.934815e+13 1.873598e+13 1.751481e+13
Short Term Investments 1.477202e+12 2.234892e+12 2.447703e+12 2.522598e+12
Net Receivables 2.659748e+12 2.940890e+12 2.708900e+12 2.552805e+12
Long Term Debt 1.029678e+12 7.655860e+11 5.910860e+11 5.784750e+11
Inventory 2.434918e+12 2.656396e+12 2.539789e+12 2.388617e+12
Accounts Payable 2.434180e+12 2.645984e+12 2.586657e+12 2.566382e+12
キャッシュフロー計算書
財務諸表の最後はキャッシュフロー計算書です。
以下のコードから直近およそ3年分のキャッシュフロー計算書を取得できます。この中で特に重要なのは、Total Cashflows From Operating Activities(営業キャッシュフロー)、Total Cashflows From Financing Activities(財務キャッシュフロー)、Total Cashflows From Investing Activities(投資キャッシュフロー)でしょうか。
cashflow = ticker.cashflow
print(cashflow)
<実行画面>
2020-03-31 2019-03-31 2018-03-31 2017-03-31
Investments 2.334300e+11 6.166420e+11 -3.322730e+11 6.950000e+08
Change To Liabilities -7.641000e+10 9.488700e+10 4.664800e+10 1.459570e+11
Total Cashflows From Investing Activities -3.150861e+12 -2.697241e+12 -3.660092e+12 -2.969939e+12
Net Borrowings 1.558199e+12 7.229710e+11 6.893390e+11 1.030929e+12
Total Cash From Financing Activities 3.971380e+11 -5.408390e+11 -4.491350e+11 -3.751650e+11
Change To Operating Activities -2.703900e+11 4.084000e+11 4.857250e+11 7.724320e+11
Net Income 2.076183e+12 1.882873e+12 2.493983e+12 1.831109e+12
Change In Cash 7.056750e+11 4.868760e+11 7.031300e+10 2.098980e+11
Repurchase Of Stock -4.761290e+11 -5.496370e+11 -4.478180e+11 -7.039860e+11
Effect Of Exchange Rate -1.312450e+11 -4.164100e+10 -4.358800e+10 -1.348600e+10
Total Cash From Operating Activities 3.590643e+12 3.766597e+12 4.223128e+12 3.568488e+12
Depreciation 1.605383e+12 1.792375e+12 1.734033e+12 1.610950e+12
Dividends Paid -6.299870e+11 -6.448060e+11 -6.268920e+11 -6.381720e+11
Change To Inventory -1.140960e+11 -1.669020e+11 -1.711480e+11 -2.463260e+11
Change To Account Receivables 2.488950e+11 -2.468450e+11 -1.054350e+11 -2.647840e+11
Other Cashflows From Financing Activities -5.494500e+10 -6.936700e+10 -6.376400e+10 -6.393600e+10
Change To Netincome 2.228170e+11 1.431380e+11 -4.994310e+11 -1.598180e+11
Capital Expenditures -3.595131e+12 -3.738887e+12 -3.598707e+12 -3.541437e+12
銘柄のサマリー
最後に銘柄のサマリーの入手方法です。
以下のコードからその銘柄の基本情報を取得できます。この中で特に重要なのは、marketCap(時価総額)、sharesOutstanding(発行株数)、forwardPE(予測PER)、dividendYield(配当利回り)、profitMargins(純利益比率)など、様々なものがあります。
info = ticker.info
print(info)
<実行画面>
ディクショナリ型のため省略
複数銘柄の取得
複数銘柄を同時に取得する場合はTickersクラスを使い、引数はスペースで区切ります。
tickers = yf.Tickers("7203.T 9984.T 6861.T")
hists = []
for i in range(len(tickers.tickers)):
hists.append(tickers.tickers[i].history())
print(hists[0])
<実行画面>
Open High Low Close Volume Dividends Stock Splits
Date
2020-10-09 7026.0 7029.0 6947.0 6967.0 3395900 0 0
2020-10-12 6932.0 6945.0 6900.0 6911.0 2638200 0 0
2020-10-13 6977.0 7030.0 6946.0 7030.0 3667700 0 0
2020-10-14 6962.0 6970.0 6919.0 6935.0 3065400 0 0
2020-10-15 6898.0 6933.0 6895.0 6915.0 2844800 0 0
2020-10-16 6940.0 6944.0 6825.0 6829.0 3770200 0 0
2020-10-19 6874.0 6948.0 6870.0 6945.0 3047000 0 0
2020-10-20 6926.0 6945.0 6889.0 6897.0 2342400 0 0
2020-10-21 6962.0 7052.0 6956.0 7009.0 4795000 0 0
2020-10-22 6967.0 6984.0 6941.0 6966.0 3207500 0 0
2020-10-23 7009.0 7010.0 6944.0 6973.0 3963300 0 0
2020-10-26 6970.0 7003.0 6955.0 6990.0 2675000 0 0
2020-10-27 6970.0 6993.0 6924.0 6961.0 3234300 0 0
2020-10-28 6888.0 6927.0 6845.0 6895.0 3760200 0 0
2020-10-29 6795.0 6924.0 6780.0 6893.0 4099900 0 0
2020-10-30 6848.0 6878.0 6803.0 6803.0 5207800 0 0
2020-11-02 6866.0 7016.0 6850.0 6949.0 5721200 0 0
2020-11-04 7024.0 7054.0 6976.0 6976.0 6278100 0 0
2020-11-05 6955.0 7032.0 6923.0 6984.0 5643400 0 0
2020-11-06 7070.0 7152.0 7015.0 7019.0 11092900 0 0
2020-11-09 7159.0 7242.0 7119.0 7173.0 7838600 0 0
株価以外のデータ取得(為替)
Yahoo Financeに存在するティッカーであれば、株式銘柄でなくともデータを取得することができます。一例として為替のデータを取得してみましょう。
import pandas as pd
fxs = ["JPY=X", "EURUSD=X", "GBPUSD=X"]
tickers = yf.Tickers(" ".join(fxs))
closes = []
for i in range(len(tickers.tickers)):
closes.append(tickers.tickers[i].history(period="max").Close)
df = pd.DataFrame(closes).T
df.columns = fxs
print(df)
<実行結果>
JPY=X EURUSD=X GBPUSD=X
Date
1996-10-30 114.180 NaN NaN
1996-11-01 113.500 NaN NaN
1996-11-04 113.880 NaN NaN
1996-11-05 114.250 NaN NaN
1996-11-06 113.950 NaN NaN
... ... ... ...
2020-11-03 104.725 1.1643 1.2924
2020-11-04 104.546 1.1762 1.3122
2020-11-05 104.438 1.1733 1.2967
2020-11-06 103.603 1.1818 1.3139
2020-11-09 104.871 1.1910 1.3193
[6243 rows x 3 columns]
世界の主要株価指数
以下、世界の主要株価指数の取得方法です。ここに挙げたもの以外でも取れる指標はあります。ご自身でYahoo Financeで探してみることをお勧めします。
indices = ["^N225", "^DJI", "^GSPC", "^IXIC", "^GDAXI", "^FTSE", "^FCHI", "^HSI", "^SSEC", "^BVSP", "^KOSPI"]
# 以下、省略
計量的・実証的トレーディングの実行
では例題として本記事の問題提起で挙げた、割安でクオリティが高いと考えられる株(PERが10倍以下、ROEが10%以上)への投資したときのリターンの検証方法について、プログラムを元に解説します。なお、筆者のプログラミング能力は学生未満ですので、お見苦しい記述があるかと思います。コーディングに関する指摘があればコメント頂けると助かります。
銘柄リストの読み込み
まず東証の銘柄リストをCSVで準備します。東証の上場銘柄一覧はこちらから取得できますので検証したい銘柄をピックアップしてください。この例題では、TOPIXの中でも比較的大型であるTOPIX500構成銘柄を対象としました。
import datetime
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
data = pd.read_csv("topix500.csv")
print(data)
<実行画面>
code
0 1332
1 1333
2 1414
3 1605
4 1721
.. ...
494 9962
495 9983
496 9984
497 9987
498 9989
[499 rows x 1 columns]
ティッカーの設定
yfinanceのtickerを設定します。上記の銘柄にマーケットデータである日経平均も加えておきます。
stocks = [str(s)+".T" for s in data.code]
stocks.append("^N225")
tickers = yf.Tickers(" ".join(stocks))
終値データフレームの作成
続いてyfinanceで価格系列のヒストリカルデータを入手し、その中から終値のデータをデータフレームにまとめます。
closes = [] # 終値
for i in range(len(tickers.tickers)):
closes.append(tickers.tickers[i].history(period="max").Close)
closes = pd.DataFrame(closes).T # DataFrame化
closes.columns = stocks # カラム名の設定
closes = closes.ffill() # 欠損データの補完
print(closes)
<実行画面>
1332.T 1333.T 1414.T 1605.T 1721.T 1801.T ... 9962.T 9983.T 9984.T 9987.T 9989.T ^N225
Date ...
1965-01-05 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN 1257.72
1965-01-06 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN 1263.99
1965-01-07 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN 1274.27
1965-01-08 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN 1286.43
1965-01-12 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN 1288.54
... ... ... ... ... ... ... ... ... ... ... ... ... ...
2020-11-04 417.0 2240.0 5200.0 526.0 2789.0 3325.0 ... 3135.0 74380.0 6535.0 3865.0 3945.0 23695.23
2020-11-05 417.0 2211.0 5220.0 506.0 2782.0 3335.0 ... 3200.0 74400.0 6870.0 3965.0 4020.0 24105.28
2020-11-06 421.0 2219.0 5270.0 507.0 2826.0 3385.0 ... 3245.0 75480.0 6722.0 3785.0 4155.0 24325.23
2020-11-09 423.0 2252.0 5360.0 500.0 3010.0 3440.0 ... 3360.0 78310.0 7083.0 3770.0 4185.0 24839.84
2020-11-10 437.0 2319.0 5410.0 541.0 3040.0 3535.0 ... 3455.0 77910.0 6860.0 3865.0 4145.0 25108.21
[13862 rows x 500 columns]
当期純利益データフレームの作成
次に財務諸表データをデータフレームにまとめます。まずはPERとROEを算出するための当期純利益です。各銘柄の決算期が揃っていないためNAN値が多いように見えますが、必要な箇所にはちゃんとデータが入っていますのでご安心ください。
earnings = [] # 当期純利益
dummy = tickers.tickers[0].financials.T["Net Income"]
dummy[:] = np.nan
for i in range(len(tickers.tickers)):
try:
earnings.append(tickers.tickers[i].financials.T["Net Income"])
except:
earnings.append(dummy) # エラー発生時はダミーを入れる
earnings = pd.DataFrame(earnings).T # DataFrame化
earnings.columns = stocks # カラム名の設定
print(earnings)
<実行画面>
1332.T 1333.T 1414.T 1605.T 1721.T 1801.T ... 9962.T 9983.T 9984.T 9987.T 9989.T ^N225
...
2006-08-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2007-08-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2009-03-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2010-03-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2011-03-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ...
2020-05-20 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2020-05-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2020-06-30 NaN NaN 9.005000e+09 NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2020-08-31 NaN NaN NaN NaN NaN NaN ... NaN 9.035700e+10 NaN NaN NaN NaN
2020-09-30 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
[69 rows x 500 columns]
自己資本データフレームの作成
次はROEを算出するための自己資本です。
equity = [] # 自己資本
dummy = tickers.tickers[0].financials.T["Total Stockholder Equity"]
dummy[:] = np.nan
for i in range(len(tickers.tickers)):
try:
equity.append(tickers.tickers[i].balance_sheet.T["Total Stockholder Equity"])
except:
equity.append(dummy) # エラー発生時はダミーを入れる
equity = pd.DataFrame(equity).T # DataFrame化
equity.columns = stocks # カラム名の設定
print(equity)
<実行画面>
1332.T 1333.T 1414.T 1605.T 1721.T 1801.T ... 9962.T 9983.T 9984.T 9987.T 9989.T ^N225
...
2006-08-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2007-08-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2009-03-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2010-03-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2011-03-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ...
2020-05-20 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2020-05-31 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2020-06-30 NaN NaN 8.359900e+10 NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
2020-08-31 NaN NaN NaN NaN NaN NaN ... NaN 9.565620e+11 NaN NaN NaN NaN
2020-09-30 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN
[69 rows x 500 columns]
発行株数データフレームの作成
PERを算出するためにはEPS(一株益)が必要です。EPSを算出するために発行株数データフレームを作ります。
shares = [] # 発行株数
for i in range(len(tickers.tickers)):
try:
shares.append(tickers.tickers[i].info["sharesOutstanding"])
except:
shares.append(np.nan) # エラー発生時はNAN値を入れる
shares = pd.Series(shares) # Series化
shares.index = stocks # インデックス名の設定
print(shares)
<実行画面>
1332.T 3.111410e+08
1333.T 5.262460e+07
1414.T 5.382810e+07
1605.T 1.460200e+09
1721.T 1.260270e+08
...
9983.T 1.020820e+08
9984.T NaN
9987.T 8.917480e+07
9989.T 1.169000e+08
^N225 NaN
Length: 500, dtype: float64
EPS、ROEデータフレームの作成
当期純利益、自己資本、発行株数のデータからEPS、ROEデータフレームを作ります。
eps = earnings/shares.values # EPS
roe = earnings/equity # ROE
eps = eps.ffill() # 欠損データの補完
roe = roe.ffill()
eps = eps.drop(["^N225"], axis=1) # ^N225カラムは削除しておく
roe = roe.drop(["^N225"], axis=1)
print(eps)
print(roe)
<実行画面>
1332.T 1333.T 1414.T 1605.T ... 9983.T 9984.T 9987.T 9989.T
...
2006-08-31 NaN NaN NaN NaN ... NaN NaN NaN NaN
2007-08-31 NaN NaN NaN NaN ... NaN NaN NaN NaN
2009-03-31 NaN NaN NaN NaN ... NaN NaN NaN NaN
2010-03-31 NaN NaN NaN NaN ... NaN NaN NaN NaN
2011-03-31 NaN NaN NaN NaN ... NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ...
2020-05-20 47.464013 238.23459 150.107472 107.557873 ... 1592.621618 NaN 316.378618 202.668948
2020-05-31 47.464013 238.23459 150.107472 107.557873 ... 1592.621618 NaN 316.378618 202.668948
2020-06-30 47.464013 238.23459 167.291805 107.557873 ... 1592.621618 NaN 316.378618 202.668948
2020-08-31 47.464013 238.23459 167.291805 107.557873 ... 885.141357 NaN 316.378618 202.668948
2020-09-30 47.464013 238.23459 167.291805 107.557873 ... 885.141357 NaN 316.378618 202.668948
[69 rows x 499 columns]
1332.T 1333.T 1414.T 1605.T 1721.T ... 9962.T 9983.T 9984.T 9987.T 9989.T
...
2006-08-31 NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN
2007-08-31 NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN
2009-03-31 NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN
2010-03-31 NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN
2011-03-31 NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ...
2020-05-20 0.096428 0.094528 0.103502 0.05165 0.08434 ... 0.078191 0.173209 NaN 0.068505 0.126817
2020-05-31 0.096428 0.094528 0.103502 0.05165 0.08434 ... 0.078191 0.173209 NaN 0.068505 0.126817
2020-06-30 0.096428 0.094528 0.107717 0.05165 0.08434 ... 0.078191 0.173209 NaN 0.068505 0.126817
2020-08-31 0.096428 0.094528 0.107717 0.05165 0.08434 ... 0.078191 0.094460 NaN 0.068505 0.126817
2020-09-30 0.096428 0.094528 0.107717 0.05165 0.08434 ... 0.078191 0.094460 NaN 0.068505 0.126817
[69 rows x 499 columns]
終値データフレームの整形、および月次リターンデータフレームの作成
ここからはゴリ押しでデータ整形していきます。まず月次データ用にデータ整形し、それから月次リターンデータフレーム(マーケットリターンを差し引いたもの)を作ります。
closes["month"] = closes.index.month # 月カラムの作成
closes["end_of_month"] = closes.month.diff().shift(-1) # 月末フラグカラムの作成
closes = closes[closes.end_of_month != 0] # 月末のみ抽出
monthly_rt = closes.pct_change().shift(-1) # 月次リターンの作成(ラグあり)
monthly_rt = monthly_rt.sub(monthly_rt["^N225"], axis=0) # マーケットリターン控除
closes = closes[closes.index > datetime.datetime(2017, 4, 1)] # 2017年4月以降
monthly_rt = monthly_rt[monthly_rt.index > datetime.datetime(2017, 4, 1)]
closes = closes.drop(["^N225", "month", "end_of_month"], axis=1) # 不要なカラムを削除
monthly_rt = monthly_rt.drop(["^N225", "month", "end_of_month"], axis=1)
print(closes)
print(monthly_rt)
<実行画面>
1332.T 1333.T 1414.T 1605.T 1721.T 1801.T ... 9861.T 9962.T 9983.T 9984.T 9987.T 9989.T
Date ...
2017-04-28 511.60 3064.04 2390.82 994.50 1964.87 3873.35 ... 1758.93 2063.02 35232.05 4138.08 3573.51 3686.69
2017-05-31 550.67 3049.61 2498.64 947.96 2172.28 4310.81 ... 1730.92 2443.18 35949.09 4413.07 3529.87 4063.84
2017-06-30 625.93 2855.29 2687.68 1006.13 2141.72 4675.36 ... 1810.12 2507.68 36259.17 4459.15 3617.15 3950.69
2017-07-31 613.54 2895.69 2763.53 998.68 2094.50 4812.06 ... 1801.43 2673.82 32092.56 4391.01 3573.51 3875.26
2017-08-31 589.73 3068.85 2886.77 978.21 2188.95 5026.24 ... 1818.65 2756.89 30664.60 4373.37 3883.83 4294.85
2020-07-31 435.19 2021.00 4530.00 599.10 3058.94 3557.00 ... 1791.22 2489.70 55837.62 6595.00 3713.65 3580.46
2020-08-31 471.87 2398.00 5010.00 673.80 2922.77 3601.22 ... 2098.00 2777.20 63280.00 6598.00 3907.01 3912.72
2020-09-30 447.00 2412.00 5220.00 563.50 2921.00 3550.00 ... 1970.00 2935.00 65860.00 6469.00 4005.00 3965.00
2020-10-30 401.00 2182.00 5020.00 492.00 2646.00 3245.00 ... 1915.00 3090.00 72710.00 6793.00 3765.00 3875.00
2020-11-10 437.00 2319.00 5410.00 541.00 3040.00 3535.00 ... 1998.00 3455.00 77910.00 6860.00 3865.00 4145.00
[44 rows x 499 columns]
1332.T 1333.T 1414.T 1605.T 1721.T ... 9962.T 9983.T 9984.T 9987.T 9989.T
Date ...
2017-04-28 0.052727 -0.028350 0.021457 -0.070438 0.081918 ... 0.160633 -0.003289 0.042813 -0.035853 0.078659
2017-05-31 0.117186 -0.083203 0.056174 0.041880 -0.033552 ... 0.006917 -0.010858 -0.009042 0.005243 -0.047327
2017-06-30 -0.014391 0.019553 0.033625 -0.002001 -0.016644 ... 0.071656 -0.109508 -0.009877 -0.006661 -0.013689
2017-07-31 -0.024808 0.073799 0.058595 -0.006498 0.059094 ... 0.045067 -0.030496 0.009982 0.100838 0.122273
2017-08-31 -0.013368 0.001479 0.016405 0.109973 0.112144 ... 0.018384 0.018514 -0.015500 -0.030392 -0.007134
2020-07-31 0.018428 0.120684 0.040103 0.058830 -0.110373 ... 0.049619 0.067429 -0.065402 -0.013790 0.026941
2020-08-31 -0.054665 0.003878 0.039956 -0.165659 -0.002566 ... 0.054860 0.038811 -0.021512 0.023120 0.011401
2020-09-30 -0.093937 -0.086386 -0.029343 -0.117915 -0.085175 ... 0.061782 0.112979 0.059056 -0.050954 -0.013728
2020-10-30 0.013155 -0.025898 -0.022349 0.013459 0.066692 ... 0.021076 -0.026613 -0.090432 -0.056213 -0.031199
2020-11-10 NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN
[44 rows x 499 columns]
PER、ROEデータフレームの作成(月次リターンと同次元)
最後に月次リターンど同次元になるようにPER、ROEデータフレームを作成します。
eps_df = pd.DataFrame(index=monthly_rt.index, columns=monthly_rt.columns) # 月次リターンと同次元のDF作成
roe_df = pd.DataFrame(index=monthly_rt.index, columns=monthly_rt.columns)
for i in range(len(eps_df)): # 各行への代入
eps_df.iloc[i] = eps[eps.index < eps_df.index[i]].iloc[-1]
for i in range(len(roe_df)):
roe_df.iloc[i] = roe[roe.index < roe_df.index[i]].iloc[-1]
per_df = closes/eps_df # PERデータフレームの作成
print(per_df)
print(roe_df)
<実行画面>
1332.T 1333.T 1414.T 1605.T 1721.T 1801.T ... 9861.T 9962.T 9983.T 9984.T 9987.T 9989.T
Date ...
2017-04-28 11.1972 10.4392 NaN 31.454 17.0954 NaN ... 91.0625 31.8555 NaN NaN 14.9553 18.4872
2017-05-31 12.0523 10.39 NaN 29.982 18.9 NaN ... 89.6124 37.7256 NaN NaN 14.7726 20.3785
2017-06-30 13.6995 9.72799 NaN 31.8218 18.6341 NaN ... 93.7127 38.7215 NaN NaN 15.1379 19.8111
2017-07-31 13.4284 9.86563 21.2599 31.5862 18.2232 NaN ... 93.2628 41.2869 NaN NaN 14.9553 19.4328
2017-08-31 12.9072 10.4556 22.208 30.9388 19.045 NaN ... 94.1543 42.5696 NaN NaN 16.254 21.5369
2020-07-31 9.16884 8.48323 27.0784 5.57002 14.8307 NaN ... 162.317 42.8301 35.0602 NaN 11.738 17.6665
2020-08-31 9.94164 10.0657 29.9477 6.26453 14.1705 NaN ... 190.117 47.7759 39.7332 NaN 12.3492 19.306
2020-09-30 9.41766 10.1245 31.203 5.23904 14.1619 NaN ... 178.518 50.4906 74.4062 NaN 12.6589 19.5639
2020-10-30 8.44851 9.15904 30.0074 4.57428 12.8286 NaN ... 173.534 53.157 82.1451 NaN 11.9003 19.1199
2020-11-10 9.20698 9.7341 32.3387 5.02985 14.7389 NaN ... 181.056 59.4361 88.0198 NaN 12.2164 20.4521
[44 rows x 499 columns]
1332.T 1333.T 1414.T 1605.T 1721.T ... 9962.T 9983.T 9984.T 9987.T 9989.T
Date ...
2017-04-28 0.117515 0.153443 NaN 0.0156865 0.071603 ... 0.11847 NaN NaN 0.0538158 0.170991
2017-05-31 0.117515 0.153443 NaN 0.0156865 0.071603 ... 0.11847 NaN NaN 0.0538158 0.170991
2017-06-30 0.117515 0.153443 NaN 0.0156865 0.071603 ... 0.11847 NaN NaN 0.0538158 0.170991
2017-07-31 0.117515 0.153443 0.101048 0.0156865 0.071603 ... 0.11847 NaN NaN 0.0538158 0.170991
2017-08-31 0.117515 0.153443 0.101048 0.0156865 0.071603 ... 0.11847 NaN NaN 0.0538158 0.170991
2020-07-31 0.0964277 0.0945276 0.107717 0.05165 0.0843397 ... 0.0781906 0.173209 NaN 0.0685051 0.126817
2020-08-31 0.0964277 0.0945276 0.107717 0.05165 0.0843397 ... 0.0781906 0.173209 NaN 0.0685051 0.126817
2020-09-30 0.0964277 0.0945276 0.107717 0.05165 0.0843397 ... 0.0781906 0.0944602 NaN 0.0685051 0.126817
2020-10-30 0.0964277 0.0945276 0.107717 0.05165 0.0843397 ... 0.0781906 0.0944602 NaN 0.0685051 0.126817
2020-11-10 0.0964277 0.0945276 0.107717 0.05165 0.0843397 ... 0.0781906 0.0944602 NaN 0.0685051 0.126817
[44 rows x 499 columns]
データの結合
それでは最後にこれらのデータフレームを1つにまとめます。
stack_monthly_rt = monthly_rt.stack() # 1次元にスタック
stack_per_df = per_df.stack()
stack_roe_df = roe_df.stack()
df = pd.concat([stack_monthly_rt, stack_per_df, stack_roe_df], axis=1) # 結合
df.columns = ["rt", "per", "roe"] # カラム名の設定
df["rt"][df.rt > 1.0] = np.nan # 異常値の除去
print(df)
<実行画面>
rt per roe
Date
2017-04-28 1332.T -0.047638 11.1972 0.117515
1333.T -0.070101 10.4392 0.153443
1414.T 0.026680 NaN NaN
1605.T -0.038959 31.454 0.0156865
1721.T 0.051664 17.0954 0.071603
... ... ... ...
2020-11-10 9962.T 0.025375 59.4361 0.0781906
9983.T -0.021231 88.0198 0.0944602
9984.T -0.082885 NaN NaN
9987.T -0.066187 12.2164 0.0685051
9989.T -0.023070 20.4521 0.126817
[21892 rows x 3 columns]
対象銘柄の抽出とプロット
それでは割安でクオリティが高いと考えられる銘柄(PER<10、ROE>0.1)を抽出し、リターンの分布図およびこれらを売買したときの累積リターンを観察してみましょう。
このようにデータ分析を行うことで、雑誌などで紹介されるようなツールを使った個別銘柄選定が全く当てにならないことがよく分かります。この検証はあくまでもマーケットリターンを差し引いたものであるため、相場が好況なときには幾分リターンが得られているのかもしれません。
value_df = df[(df.per < 10) & (df.roe > 0.1)] # 割安でクオリティが高い銘柄を抽出
plt.hist(value_df["rt"]) # ヒストグラムの描画
plt.show()
balance = value_df.groupby(level=0).mean().cumsum() # 累積リターンを作成
plt.clf()
plt.plot(balance["rt"]) # バランスカーブの描画
plt.show()
最後に
本記事で示した検証は、まだまだ計量的・実証的なトレーディングの入り口に過ぎません。この記事には「成功する投資」と銘打っていますが、実際にこの記事を読んで投資で成功する方は殆どいないでしょう。それは、記事を読んで頂いた方の大多数が行動にまで移すことがないからです。ここでいう行動とは、単にこの記事を読んで付け焼刃のトレーディングを行うことではなく、この記事から受けたインスピレーションを元に自らが創意工夫して計量的・実証的なトレーディングの検証を行うまでに至ることを指します。
この記事はできるだけ分かりやすくまとめていますが、それでも読者の方が単に記事を読んだだけでは、その過程に生じた細かで有用な知見を決して理解することはできません。これを自身のものにするためには、自分自身が手を動かすしかないのです。
筆者はこれまでにトレーディングに関する自身の知見をブログやnoteに書き綴ってきました。この記事を読んで、もしも読者の方が計量的・実証的なトレーディングに本気で取り組もうと考えているのであれば、以下の参考記事が必ずその道標になるでしょう。
繰り返しになりますが、トレーディングはその殆どが運で決まります。筆者がこれまで好成績を収めてこれたのも、そのパフォーマンスが運よく下振れすることがなかったからです。あなたにも幸運のご加護がありますように。