AIを使った株価予想をPythonのscikit-learnライブラリRandam Forestで試してみた 

### 概要
Pythonの機械学習のライブラリscikit-learnを使って任意の銘柄の明日の株価を予想する。scikit-learnの中のRandam Forestを使う。Randam Forestは小さな独立した決定木を複数作成して学習させ、それぞれの決定木から得られた解の多数決によって解を導き出すアルゴリズムである。
これを株価予想に使い、予想する銘柄と関係のある複数の銘柄で過去の株価の動きより決定木を作成し、その多数決で目的の銘柄の株価を予想する。
![16306.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606353/72056560-ec7c-ecb7-bbcb-b42b1daf478c.jpeg)
木々(決定木)の色(解)はそれぞれ違う(Randam)かもしれないが、森(Forest)として見れば、色が見えてくる。Randam forestになる訳である。

### 目標
- 日本の任意の銘柄の明日の株価を予想する。
- Pythonのscikit-learnのRandam Forestを使う。

### 株価データの準備
過去の株価のデータを集めて、扱いやすいようにデーターベースに格納する。過去３年分の日本の全株価のデータをダウンロードして終値をデータベースに格納する。データベースは、ローカルデータベースのSQLiteを使う。Pythonには、SQLiteを扱うライブラリも揃っており扱い易い。下記のようなSQLiteのテーブルを作成して、過去の株価を格納する。

<table>
<tr><td>scode</td><td>証券コード</td><td>文字列</td><td>key1</td></tr>
<tr><td>tday</td><td>日付</td><td>DATE</td><td>key2</td></tr>
<tr><td>val</td><td>終値</td><td>FLOAT</td><td></td></tr>
</table>
検索速度を得るために、証券コード(scode)と日付(tday)にインディクスを張る。

株価データは、例えば下記のサイトよりダウンロードする。
http://souba-data.com/

ダウンロードした株価のZIPファイルを解凍して、CSVファイルの中から証券コード、終値を読込んでSQLiteに保存するサンプルコードを記載する。

```python:data_dl()
def data_dl():

   conn = sqlite3.connect("chart.db");
   c = conn.cursor()
   d = datetime.datetime.today();
   for i in range(0,365):
      d = datetime.datetime.today() - datetime.timedelta(days=i);
      zip_path = "data\\T%s.zip" % (d.strftime("%y%m%d"));
      if not zipfile.is_zipfile(zip_path):
         continue;
      zf = zipfile.ZipFile(zip_path, 'r')
      zf.extractall(path="data\\");
           
      csv_path = "data\\T%s.csv" % (d.strftime("%y%m%d"));
      f = open(csv_path, "r");
      csv_reader = csv.reader(f); 
      
      td = d.strftime("%Y-%m-%d");
      query = "delete from chrt where tday='"+td+"'";
      c.execute(query);
      
      for row in csv_reader:
         if (row[1]=="")or(row[7]=="")or(row[2]!="11") :
            continue;
         query = "insert into chrt (scode,tday,val) values('"+row[1]+"','"+td+"',"+row[7]+")";
         c.execute(query);

      f.close();      
      print csv_path;

   conn.commit();
   conn.close();

   return;
```

### Randam Forestを使った株価予想の実践

ターゲットとする株価として、ソニー(株)[T-6758]を予想する事とする。特に深い理由はない。一般的で名の通った代表株としてサンプルとして取り上げた。木となる指数としては、為替相場、消費者物価指数なども適当なのかもしれないが、データベースに過去の株価データを揃えたので、株価データの中で指数となりそうなETFなどのデータを使う事とした。

<pre>
1309 &#009; 上海上証５０
1313 &#009; KODEX200
1314 &#009; 日本新興株
1322 &#009; 上場パンダ
1326 &#009; SPDRゴールド
1343 &#009; ＮＦ東ＲＥＩＴ
1543 &#009; パラジウム信託
1548 &#009; 上場中国Ｈ株
1551 &#009; ＪＴＯＰ２０投信
1633 &#009; NEXT不動産
1671 &#009; WTI原油投信
1673 &#009; ETFS銀上場
1678 &#009; ＮＦインド
1681 &#009; 新興国株式
1682 &#009; 東工取白金投信
1693 &#009; 銅上場投信
1698 &#009; 東証配当投信
1699 &#009; 原油イン投信
</pre>

これら各のETFの過去の株価とターゲットの株価の相関関係を学習させる。この時、前日から株価が増加した場合は+１、減少した場合は-1として正規化する。さらに注意点として、前日のETFの値から、本日のターゲットの株価を予想する必要があるので、ETFの株価とターゲットの株価は、一日ずらす必要がある。このようにして学習したRandam Forestデータ使って、前日のETFの値から本日のターゲットの株価を予想値を出す。
![無題.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606353/07b61105-43c1-ccf7-a286-d8b361e4422f.jpeg)
ソースの一式を下記に載せる。

```python:rnd_forest

import ConfigParser
import urllib
import sqlite3
import datetime
import os.path
import zipfile
import csv
from sklearn.ensemble import RandomForestClassifier

lscode=[];
ltday=[];

def learn_db_init():

   conn = sqlite3.connect("chart.db");
   c = conn.cursor();
   query = "select scode from chrt group by scode order by scode";
   c.execute(query)
   for row in c:
      lscode.append(row[0]);
   query = "select tday from chrt group by tday order by tday"; 
   c.execute(query)
   for row in c:
      ltday.append(row[0]);
   conn.close();

def rnd_forest(ltree_scode,tscode,tdate):

   conn = sqlite3.connect("chart.db");
   c = conn.cursor();

   ltra_x=[];    
   ltra_y=[];
   ltest_x=[];

   dp = -1;
   for i in range(0,len(ltday)):
      if ltday[i]==tdate:
         dp=i;
         break;
   if dp-102 &lt; 0:
      return 0;

   whe="";
   for sc in ltree_scode:
      if whe!="":
         whe = whe + "or";
      whe = whe + "(scode='"+sc+"')";

   lr={};
   lbr={};
   for s in ltree_scode:
      lr[s]=-1;
      lbr[s]=-1;   
   for d in range(dp-102,dp,1):      
      query = "select scode,val from chrt where (tday='%s')and(%s) order by scode" % \
            (ltday[d],whe);
      c.execute(query);
      for row in c:
         lr[row[0]]=row[1];      
      
      if d &gt; dp-102:      
         lx=[];
         for s in ltree_scode:
            if lr[s] &lt; lbr[s]:
               lx.append(-1);
            else:
               lx.append(1);   
            lbr[s]=lr[s];
         if d==dp-1:
            ltest_x=lx;            
         else:
            ltra_x.append(lx); 

      for s in ltree_scode:
         if lr[s] &gt; 0:
            lbr[s]=lr[s];
                        
   
   query = "select tday,val from chrt where (scode='%s')and(tday>='%s')and(tday<'%s') order by tday" % \
            (tscode,ltday[dp-101],ltday[dp]);
   c.execute(query)
   r=-1;
   for row in c:
      if r>0:
         if r &lt; row[1]:
            ltra_y.append(1);
         else:
            ltra_y.append(-1);
      r=row[1];

   conn.close();

   classifier = RandomForestClassifier(n_estimators=len(ltree_scode), random_state=0,max_depth=5);
   classifier.fit(ltra_x,ltra_y);
   ret = classifier.predict([ltest_x]);

   return ret;      
   print tdate,ret; 
#--------------------------------------------------------------------------------------------------
learn_db_init();
letf=["1309","1313","1314","1322","1326","1343","1543","1548","1551","1633","1671","1673","1678","1681","1682","1693","1698","1699"];
ans=rnd_forest(letf,'6758',"2017-03-07");  
```
ソースについての簡単な説明しておく。
learn_db_init()
でデータベースに格納されている証券コードのリストをlscode、日付のリストをltdayに保管する。
rnd_forest(ltree_scode,tscode,tdate)　で株価予想を行う。ltree_scodeには、指数とするETFなどのコードをリストで入れる。tscodeには、ターゲットの証券コード、今回の場合ソニーの証券コード6758をセットしている。tdateには、予想する日付を入れる。

rnd_forest(ltree_scode,tscode,tdate)　の関数の中でclassifierが出てくる最後の５行目までは、
データベースから、配列に過去の株価をセットする処理になる。
ltra_x: 各ETFの株価の上昇下降を1 or -1でtdateの２日前までセット
ltra_y: ターゲットの株価の上昇下降を1 or -1でtdateの１日前までセット
これを使って、classifier.fit(ltra_x,ltra_y);で学習させる。ltest_xには、tdateの１日前の各ETFの値を配列としてセット。これを使って、classifier.predict([ltest_x]);でtdate日のターゲットの株価を予想する。

テストの日付の株価は、1:上昇と実際の値と合致していた。しかし、日によっては外れる事もある。

### 予想結果の検証と考察
前章でソニー(株)[T-6758]の株価を予想する為に、目ぼしいETFを18個選択して、前日までのソニー(株)[T-6758]の株価と18個のETFの株価と相関関係を100日分を使って学習させ、本日のETFの株価を与えて、明日のターゲットの株価を予想した。2017/8/29-2018/3/16 までのソニー(株)[T-6758]の株価を予想した結果は下図の通り。青色はソニーの株価変動、紫は累積の的中率、緑色は間近２０日間平均の的中率を示している。
![無題a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606353/dbbfc46d-42bc-fe47-be28-b7b213454535.jpeg)
累積的中率は43％。　低すぎる。これだったら、予想の逆を買った方が儲かる構図になる。考えられる理由として、

* 選択した18個ETFは、適当に選んだ為、ソニーの株価の連動と関連性が低く適切でなかった可能性がある。いくつかは、関連性が高かったのかもしれないが、多数決で掻き消されてしまったのかもしれない。
* 本日のETFの値を使って、明日のターゲットの株価を予想しており１日ずれている。 動きの早い現在の株式市場では１日前の株価の連動性はほとんどないのかもしれない。

### Randam Forestの改良と成果
そこで、ソニーの株価と１日差を付けて、連動性の高い銘柄を探す事にする。的中率の高い有能な助言者のみを集めて多数決を行うイメージ。そこで、１銘柄だけでRandam Forestを計算し、先と同じように的中率を求めていく事にする。その結果で、的中率の高い関連性の高い上位１０～２０銘柄を使って、Randam Forestすれば、的中率も上がるのでは無いかと考えた。今回は、ETF銘柄に絞らずに一般的な銘柄も含めてすべてでチェックしてみる。関連会社と取引会社など想像もしなかった銘柄が見つかるかもしれないので。その結果が下表になる。
![無題b.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606353/206eff16-a531-7d0f-1a2b-065b877898a8.jpeg)
最も高かった銘柄でも、的中率は61%だった。大丈夫だろうか。上位に上がった銘柄を見てみると、やはり関係の無い銘柄が多く上がってきた。関連会社とかではなくて、株価の動きが似たような動きをする銘柄になっているのか。
この結果を元に関連性が高いと思われる上位20銘柄を使って、Randam Forestを掛けてみた。その結果が下図になる。
![無題c.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606353/f0b0febf-1028-bee0-fd78-c6b691a21e07.jpeg)
累積的中率が64%と大幅にアップした。20日の累積的中率に限っては最大で85％と驚異的となった。

### 最後に
最後まで読んで頂いてありがとうございます。本稿で述べたRandamForestをC言語で実装したソフトウェアについて下記の記事で紹介していますので、合わせてお読み下さい。
[AI(機械学習)Randam Forestで株価を予想するWinodwsアプリケーションの開発](https://qiita.com/koutarou-yamada/items/91fba46a5f11787064b4)

更には、上記のソフトウェアを強化した有償版を利用した株式投資法についても下記で紹介している。　
[G-Chartを用いた株式投資法の紹介](http://sin3rou.g1.xrea.com/gchart.html)  

### 追記（2023年10月）
最初の投稿から日時が経過して投資環境も変化する中、本手法が現時点でも有効か検証した。今回は、C言語で開発したG-Chartを使ってRandam Forestを検証した。
![gctotal4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606353/ed2e90b0-3ea6-ab7a-b9dd-93d151261f69.jpeg)
記載の通り、20日平均で72.4%、100日平均で71.4%の的中率と、現時点においても有効に株価予想できある事が確認できた。