[python3]FXチャートにトレンドラインを引く

# この記事の想定読者
* pythonを使って、自力で為替データを取得することができる
* 為替レートにトレンドラインを引きたい、けど引けない
* トレードで儲けたお金を、慈善事業に寄付したりして誰かを助けたい！（MUST）(`・ω・´)

全部に該当する人だけ読んでね（^ワ^*）←

# 完成図！
![2018-12-16_16h48_58.png](https://qiita-image-store.s3.amazonaws.com/0/327407/355c6992-ff17-b10d-d9c3-bd3e00a0c0b6.png)

pointは、一定期間内のレートだけを対象にトレンドラインを引いているところ。
期間の範囲指定も可能。


# 参考にさせていただいた記事
#### PythonでFXのトレンドラインを引いてみる
[http://www.algo-fx-blog.com/python-fx-trend-line/](http://www.algo-fx-blog.com/python-fx-trend-line/)

単回帰分析を繰り返して徐々に高値（安値）を絞り込んでいく手法を参考にさせていただきました。

# ソースコード
## 1　モジュールインポート

```python:jupyter_notebook
# 一般的なモジュール
import pandas as pd
import numpy  as np
import datetime
import seaborn
import matplotlib.pyplot as plt
from scipy.stats import linregress

# 投資分析にのみ必要なモジュール
import mplfinance.original_flavor as mpf
from   oandapyV20 import API
import oandapyV20.endpoints.instruments as oandapy

# For suppressing warning
# https://github.com/numpy/numpy/issues/11448
np.seterr(invalid='ignore')
```

## 2　データ取得

２（データ取得）は自前で処理を書いて下さい。
一応サンプルとして私の処理も載せます。（が、汚いので参考にならないと思います）

```python:jupyter_notebook
class FXBase():
    candles = None
    def __init__(self):
        cols = ['col1', 'col2']
        FXBase.candles = pd.DataFrame(index=[], columns=cols)
```

```python:jupyter_notebook
class RequestRate(FXBase):
    def __init__(self, days=3):
        # request用パラメータ設定
        num_candles  = int(days * 24 * 12) # 24h * 60min / 5分刻み
        minutes      = num_candles * 5 # 60 = 12 * 5 分
        now          = datetime.datetime.now() - datetime.timedelta(hours=9) # 標準時に合わせる
        start_time   = now - datetime.timedelta(minutes=minutes)
        start_time   = start_time.strftime("%Y-%m-%dT%H:%M:00.000000Z")
        params       = {
            "alignmentTimezone": "Japan",
            "from":  start_time,
            "count": num_candles,
            "granularity": "M5" # per 5 Minute 
        }
        access_token = "アクセストークンを書いてね"
        api          = API(access_token = access_token, environment="practice")
        request      = oandapy.InstrumentsCandles(
            instrument = "USD_JPY",
            params     = params
        )
        
        # request処理
        api.request(request)

        # request結果データの整形 / クラス外から呼出し可能にする
        candle         = pd.DataFrame.from_dict([ row['mid'] for row in request.response['candles'] ])
        # astype による cast を複数列へ https://qiita.com/driller/items/af1369a5c0fc2ec61af3
        candle         = candle.astype({'c': 'float64', 'l': 'float64', 'h': 'float64', 'o': 'float64'})
        candle.columns = ['close', 'high', 'low', 'open']
        candle['time'] = [ row['time'] for row in request.response['candles'] ]
        # 冗長な日時データを短縮整形 https://note.nkmk.me/python-pandas-datetime-timestamp/
        candle['time'] = pd.to_datetime(candle['time']).astype(str)
        FXBase.candles = candle

        # 表示可能な最大行数を設定
        pd.set_option("display.max_rows", num_candles)
```

## 3　取得したデータの確認

```python:jupyter_notebook
if __name__ == '__main__':
    requester = RequestRate(days=2)
    FXBase.candles['time_id']= FXBase.candles.index + 1

FXBase.candles.head()
```
![2018-12-16_17h21_16.png](https://qiita-image-store.s3.amazonaws.com/0/327407/d0acf270-54d2-1d83-d961-30e642179d08.png)

こんな感じのデータさえあれば、データの取得方法は問いません。

## 4　メイン：トレンドライン生成
```python:jupyter_notebook

## チャートを単回帰分析し、得られる単回帰直線よりも上（下）の値だけで再度単回帰分析...
## これを繰り返し、高値（安値）を2～3点に絞り込む

# 高値の始点/支点を取得
def get_highpoint(start, end):
    chart = FXBase.candles[start:end+1]
    while len(chart)>3:
        regression = linregress(
            x = chart['time_id'],
            y = chart['high'],
        )
        chart = chart.loc[chart['high'] > regression[0] * chart['time_id'] + regression[1]]
    return chart

# 安値の始点/支点を取得
def get_lowpoint(start, end):
    chart = FXBase.candles[start:end+1]
    while len(chart)>3:
        regression = linregress(
            x = chart['time_id'],
            y = chart['low'],
        )
        chart = chart.loc[chart['low'] < regression[0] * chart['time_id'] + regression[1]]
    return chart

def g_trendlines(span=20, min_interval=3):
    trendlines = []

    # 高値の下降トレンドラインを生成
    for i in FXBase.candles.index[::span/2]:
        highpoint = get_highpoint(i, i + span)
        # ポイントが2箇所未満だとエラーになるので回避する
        if len(highpoint) < 2:
            continue
        # 始点と支点が近過ぎたらトレンドラインとして引かない
        if abs(highpoint.index[0] - highpoint.index[1]) < min_interval:
            continue
        regression = linregress(
            x = highpoint['time_id'],
            y = highpoint['high'],
        )
        print(regression[0] < 0.0, 'reg_high: ', regression[0], ', ', regression[1], )

        # 下降してるときだけ
        if regression[0] < 0.0:
            trendlines.append(regression[0] * FXBase.candles['time_id'][i:i+span*2] + regression[1])

    # 安値の上昇トレンドラインを生成
    for i in FXBase.candles.index[::span/2]:
        lowpoint   = get_lowpoint(i, i + span)
        # ポイントが2箇所未満だとエラーになるので回避する
        if len(lowpoint) < 2:
            continue
        # 始点と支点が近過ぎたらトレンドラインとして引かない
        if abs(lowpoint.index[0] - lowpoint.index[1]) < min_interval:
            continue
        regression = linregress(
            x = lowpoint['time_id'],
            y = lowpoint['low'],
        )
        print(regression[0] > 0.0, 'reg_low: ', regression[0], ', ', regression[1], )

        # 上昇してるときだけ
        if regression[0] > 0.0:
            trendlines.append(regression[0] * FXBase.candles['time_id'][i:i+span*2] + regression[1])

    return trendlines

```
あとは、これをチャートと一緒に描画するだけです

デフォルトでは、ローソク足20本中の高値（安値）を元にトレンドラインを引きます。
g_trendlines関数の引数 span を変更すれば、何本分のローソク足を元にして高値（安値）を見つけるかを変更できます。


## 5　描画
コードが汚すぎて見せたくねぇ...
ここまでの内容を理解できる人は自分で描画できると思いますが、一応サンプルとして載せます。
（余計な処理が混じってるのでwarningが出ます。おゆるしを。。。）

```python:jupyter_notebook
figure, (axis1, axis2) = plt.subplots(2, 1, figsize=(20,10), dpi=200, gridspec_kw = {'height_ratios':[3, 1]})

# ローソク足
mpf.candlestick2_ohlc(
    axis1,
    opens  = FXBase.candles.open.values,
    highs  = FXBase.candles.high.values,
    lows   = FXBase.candles.low.values,
    closes = FXBase.candles.close.values,
    width=0.6, colorup='#77d879', colordown='#db3f3f'
)

# トレンドラインたちを引く
for i, line in enumerate(g_trendlines()):
    axis1.plot(line, label=i)

# X軸の見た目を整える
xticks_number  = 12 # 12本刻みに目盛りを書く
xticks_index   = range(0, len(FXBase.candles), xticks_number)
xticks_display = [FXBase.candles.time.values[i][11:16] for i in xticks_index] # 時間を切り出すため、先頭12文字目から取る

# axis1を装飾 ( plt.sca(axis): set current axis )
plt.sca(axis1)
plt.xticks( xticks_index, xticks_display )
plt.legend()
plt.show()

# axis2は他の勉強の名残…消したら動かなくなりそうで、怖くて消せてない(´；ω；`)
```

![2018-12-16_16h48_58.png](https://qiita-image-store.s3.amazonaws.com/0/327407/355c6992-ff17-b10d-d9c3-bd3e00a0c0b6.png)
こんな感じになるはずだょ☆彡（再掲）


引くラインの数を結構絞り込んでいるので、見やすくなっています。
ifでかけている制限を取れば、もっとたくさんのトレンドラインが引けるはず。