【Python】株価データ分析 株価チャートを描く～mplfinance トレンドライン編～

## 修正（2021.11.27)

データ（株価データ）によっては，
高値と安値の線を引くためのデータ（hlinesに渡すデータ）がfloatでないというエラーが発生しましたので，

```python
# エラー箇所
hlines=[df['High'].max(), df['Low'].min()]
```

次のように修正しています

```python
# float変換
max = float(df['High'].max())
min = float(df['Low'].min())
hlines=[max, min]
```

## はじめに

[【Python】株価データ分析 株価チャートを描く～mplfinance編 その１～](https://qiita.com/tnozoo/items/60151108f946c48b666d)で描いた線が思ったより良い感じだったので，トレンドラインっぽい線を描きたいと思った

今回は線形回帰トレンドラインを描いてみる

株価チャートに，線形回帰で導いた一次関数
$y = ax + b$
$y$ … 株価
$x$ … 日付
$a$ … 係数
$b$ … 切片
を重ね，トレンドを把握しようというものだ

## 準備

線形回帰は，scikit-learn を使うのでインストール

conda であれば

```
conda install scikit-learn
```

## データ用意

自分銘柄一覧（my.xlsx）と東証上場銘柄一覧（date_j.xls）を読み込む

[この記事](https://qiita.com/tnozoo/items/fd684087f4d9b3dec8cb)などを参考にしてほしいが，自分銘柄一覧（my.xlsx）とは，自分の保有銘柄の一覧で（仮に，トヨタ，日産，ホンダ，ＡＮＡ，ＪＡＬを保有しているとしている），
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385512/ca9b4323-fabf-cbb3-ecce-717176ef5c00.png)
東証上場銘柄一覧（data_j.xls）とは，[日本取引所グループ](https://www.jpx.co.jp/markets/statistics-equities/misc/01.html)にある東証上場銘柄の一覧ファイルのことだ

まずは，自分銘柄と東証上場銘柄一覧を読み込んで，自分銘柄のDataFrameを作る

```python
import numpy as np
import pandas as pd
import pandas_datareader.data as pdr
import datetime
from dateutil.relativedelta import relativedelta
import mplfinance as mpf

my_df = pd.read_excel('my.xlsx', sheet_name = 'Sheet1', dtype = str)
topix_df = pd.read_excel('data_j.xls', dtype = str)

code_set = set(my_df['コード'].tolist())
df = topix_df[topix_df['コード'].isin(code_set)]
df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>日付</th>
      <th>コード</th>
      <th>銘柄名</th>
      <th>市場・商品区分</th>
      <th>33業種コード</th>
      <th>33業種区分</th>
      <th>17業種コード</th>
      <th>17業種区分</th>
      <th>規模コード</th>
      <th>規模区分</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2886</th>
      <td>20211029</td>
      <td>7201</td>
      <td>日産自動車</td>
      <td>市場第一部（内国株）</td>
      <td>3700</td>
      <td>輸送用機器</td>
      <td>6</td>
      <td>自動車・輸送機</td>
      <td>2</td>
      <td>TOPIX Large70</td>
    </tr>
    <tr>
      <th>2888</th>
      <td>20211029</td>
      <td>7203</td>
      <td>トヨタ自動車</td>
      <td>市場第一部（内国株）</td>
      <td>3700</td>
      <td>輸送用機器</td>
      <td>6</td>
      <td>自動車・輸送機</td>
      <td>1</td>
      <td>TOPIX Core30</td>
    </tr>
    <tr>
      <th>2926</th>
      <td>20211029</td>
      <td>7267</td>
      <td>本田技研工業</td>
      <td>市場第一部（内国株）</td>
      <td>3700</td>
      <td>輸送用機器</td>
      <td>6</td>
      <td>自動車・輸送機</td>
      <td>1</td>
      <td>TOPIX Core30</td>
    </tr>
    <tr>
      <th>3793</th>
      <td>20211029</td>
      <td>9201</td>
      <td>日本航空</td>
      <td>市場第一部（内国株）</td>
      <td>5150</td>
      <td>空運業</td>
      <td>12</td>
      <td>運輸・物流</td>
      <td>4</td>
      <td>TOPIX Mid400</td>
    </tr>
    <tr>
      <th>3794</th>
      <td>20211029</td>
      <td>9202</td>
      <td>ＡＮＡホールディングス</td>
      <td>市場第一部（内国株）</td>
      <td>5150</td>
      <td>空運業</td>
      <td>12</td>
      <td>運輸・物流</td>
      <td>2</td>
      <td>TOPIX Large70</td>
    </tr>
  </tbody>
</table>
</div>

トヨタのトレンドを調べてみる
まずは普通にローソク足

```python
code = df['コード'].tolist()[1]
company = df['銘柄名'].tolist()[1]

ed = datetime.datetime.now() # 本日
st = ed - relativedelta(months = 12) # 12か月前
df = pdr.DataReader(code + '.T', 'yahoo', st, ed)

kwargs = dict(type = 'candle', style = 'starsandstripes', title = code)
mpf.plot(data = df, **kwargs)
```
![mpl2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385512/5f4858c7-f710-b17c-df19-102dfa780c85.png)

この辺までは，[前回](https://qiita.com/tnozoo/items/60151108f946c48b666d)やってきたことだ

## 線形回帰（fitでモデルを作成）

インポートと線形回帰を使う準備

```python
from sklearn.linear_model import LinearRegression
linearModel = LinearRegression()
```

### 正解のやり方

```python
# fitでモデルを作成
linearModel.fit(X = np.arange(len(df.index)).reshape(-1,1), y = df['Close'].values)
# 係数
coef = linearModel.coef_[0]
# 切片
intercept = linearModel.intercept_
# 係数と切片
(coef, intercept)
```

結果は
係数（coef） = 2.615044958208052, 切片（intercept） = 1476.6834173686243

係数がプラスなので，右肩上がりのトレンドだ
$y = 2.615044958208052x + 1476.6834173686243$

正解にたどり着くまでに，何度か失敗したので，その失敗例を残しておく

### 失敗例１

```python
# fitでモデルを作成
linearModel.fit(X = df.index.values.reshape(-1,1), y = df['Close'].values)
# 係数
coef = linearModel.coef_[0]
# 切片
intercept = linearModel.intercept_
# 係数と切片
(coef, intercept)
```

結果は，おかしい値に
係数（coef） = 2.0299990617873803e-14, 切片（intercept） = -31132.82774519947

fit の X に日付データを渡したのがまずかったようだ

### 失敗例２

```python
# fit
linearModel.fit(np.arange(len(df.index)), df['Close'].values)
```

結果は，エラー
ValueError: Expected 2D array, got 1D array instead:
array=[  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17 …以降省略]

本来は，
array=[[0], [1], …以降省略]
となってなければならない

reshape(-1, 1) で2次元配列にする必要がある
列数は1を指定し，行数は-1でよろしくやってもらう

## 線形回帰（predictで予測値を出す）

fit で与えた X を predict に与えればよい

```python
# predict で予測値を出す
predict = linearModel.predict(X = np.arange(len(df.index)).reshape(-1,1))
predict
```
結果は
array([1476.68341737, 1479.29846233, 1481.91350729, 1484.52855224, …以降省略])

## トレンドラインを引く

[前回](https://qiita.com/tnozoo/items/60151108f946c48b666d)の線の引き方と同じやり方でやってみる

```python
# 全部の点を結ぶ線を引く場合は
#multi_points = [(pd.to_datetime(idx), price) for idx, price in zip(df.index.values, predict)]
#multi_points
# ２点を結ぶ線を引く場合は
multi_points = [(pd.to_datetime(df.index.values[0]), predict[0]), (pd.to_datetime(df.index.values[-1]), predict[-1])]
multi_points
```
multi_pointsには，（株価データの最初の日，最初の日の予測値），（株価データの最後の日，最後の日の予測値）を渡している

multi_pointsの中身は
[(Timestamp('2020-11-26 00:00:00'), 1476.7034957070596),
(Timestamp('2021-11-26 00:00:00'), 2117.32902858791)]

チャート描画

```python
max = float(df['High'].max())
min = float(df['Low'].min())
mpf.plot(df, **kwargs,
         hlines = dict(hlines=[max, min], linewidths=(.5, .5), colors=('r','r')),
         alines = dict(alines=two_points, linewidths=(40), colors=('b'), alpha=0.2))
```
![mpl2-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385512/eade10b1-2b9b-afab-db9c-247735df360e.png)

うん，教科書のような？右肩上がり

## まとめると

```python:mplfinance2.py
import numpy as np
import pandas as pd
import pandas_datareader.data as pdr
import datetime
from dateutil.relativedelta import relativedelta
import mplfinance as mpf
from sklearn.linear_model import LinearRegression


my_df = pd.read_excel('my.xlsx', sheet_name = 'Sheet1', dtype = str)
topix_df = pd.read_excel('data_j.xls', dtype = str)

code_set = set(my_df['コード'].tolist())
df = topix_df[topix_df['コード'].isin(code_set)]

code = df['コード'].tolist()[1]
company = df['銘柄名'].tolist()[1]

ed = datetime.datetime.now() # 本日
st = ed - relativedelta(months = 12) # 12か月前
df = pdr.DataReader(code + '.T', 'yahoo', st, ed)


# fitでモデルを作成
linearModel = LinearRegression()
linearModel.fit(X = np.arange(len(df.index)).reshape(-1,1), y = df['Close'].values)
# 係数
coef = linearModel.coef_[0]
# 切片
intercept = linearModel.intercept_
# 係数と切片
#(coef, intercept)


# 係数
coef = linearModel.coef_[0]
# 切片
intercept = linearModel.intercept_

# predict で予測値を出す
predict = linearModel.predict(X = np.arange(len(df.index)).reshape(-1,1))

# 全部の点を結ぶ線を引く場合は
#multi_points = [(pd.to_datetime(idx), price) for idx, price in zip(df.index.values, predict)]
# ２点を結ぶ線を引く場合は
multi_points = [(pd.to_datetime(df.index.values[0]), predict[0]), (pd.to_datetime(df.index.values[-1]), predict[-1])]

# チャート描画
kwargs = dict(type = 'candle', style = 'starsandstripes', title = code)
max = float(df['High'].max())
min = float(df['Low'].min())
mpf.plot(df, **kwargs,
         hlines = dict(hlines=[max, min], linewidths=(.5, .5), colors=('r','r')),
         alines = dict(alines=multi_points, linewidths=(40), colors=('b'), alpha=0.2))
```

## おわりに

今回のトヨタの例は，きれいな右肩上がりだったのでトレンドラインを描くまでもなかったが
複数の銘柄のトレンドラインを並べて表示するような場合は
トレンド判断が一瞬でできそうなので良いなと思った

次回こそ，[前回](https://qiita.com/tnozoo/items/60151108f946c48b666d)宣言した通り，
MACDやボリンジャーバンドを描いたり，
複数銘柄を並べて描くようなことをやってみたい