【仮想通貨・株・FX】海外で人気のSuperTrendインジケーターをPythonで再現してみる

![スクリーンショット 2021-09-25 20.18.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/be2236c3-80f1-f5e5-e9b2-e46d07999203.png)

## 概要

記事タイトル通り、巷で人気のSuperTrendインジケーターをPythonで再現してみます。

現在における相場の方向性が視覚的にわかりやすく、なおかつトレンドが明確な曲面においては強力なパワーを発揮するため、ぜひシステムトレードや分析などに組み込んでみたいと思いました。

## SuperTrendインジケーターとは

SuperTrendインジケーターは、フランスの投資家であるOliver Seban氏が考案したトレンドフォロー型のインジケーターです。

参照記事: [Olivier Seban – The Inventor of the SuperTrend Indicator](https://financestrategysystem.com/olivier-seban-supertrend/)

同氏は32歳という若さで億万長者となり、35歳でリタイアを決意。これまで投資に関する多くの著書も出版されているようで、一部の界隈では有名人とされています。

具体的にどんなインジケーターなのかというと、一定期間における価格ボラティリティを表すATR（Average True Rang: アベレージ・トゥルー・レンジ）に基づきトレンドの方向性を示してくれます。

![スクリーンショット 2021-09-14 16.13.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/dc7c9052-ca44-45b7-0b70-30530ccb083a.png)

こんな感じで緑のバンドと赤のバンドの２種類を表示させ、

- 緑のバンドの上にいる時は上昇トレンド
- 赤のバンドの下にいる時は下降トレンド

と判定。

また、それぞれのバンドをサポートライン、レジスタンスラインとして見立ててエントリーや利確・損切りの目安にも使用します。

上の例で言えば、

- 200EMA（指数平滑移動平均線）を長期トレンドの把握に使用。
    - 売買の方向性を決定。
        - 200EMAの上にいる時は買いのみを狙う。
        - 200EMAの下にいる時は売りのみを狙う。
- SuperTrendインジケーターを短期トレンドの把握に使用。
    - 売買のタイミングを決定。
        - 赤のバンド（レジスタンスライン）を上抜けたタイミングで買いエントリー。後に緑のバンド（サポートライン）を下抜けたタイミングでトレンドの転換を想定して利確または損切り。
        - 緑のバンド（サポートライン）を下抜けたタイミングで売りエントリー。後に赤のバンド（レジスタンスライン）を上抜けたタイミングでトレンドの転換を想定して利確または損切り。

といった戦略を採用しています。

トレンドフォロー型のインジケーターであるため、レンジ相場に弱い（実際、相場の7〜8割はレンジ相場と言われているため、上記写真のように上手く決まる事は少ない）という欠点はあるものの、綺麗にトレンドが出ている相場においてはそれなりに高い確率で利益を残してくれそうです。

![スクリーンショット 2021-09-25 20.58.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/d556ca9d-59ca-f31c-08a7-5dffb5bd0570.png)

海外発のインジケーターという事もあり日本での知名度はそこまで高くないみたいですが、欧米のトレーダー間では割と高い使用率を誇るようで、最近人気のトレーディングプラットフォーム「[TradingView](https://jp.tradingview.com/)」で公開されているライブラリの中でもいいね数は上位に食い込んでいます。

## 環境

- Google Colaboratory
    - ブラウザからPythonを記述、実行できるサービス。
- Twelve Data
    - 株、為替、仮想通貨といった金融商品の情報を取得できるサービス。

今回はPythonの実行環境として「[Google Colaboratory](https://research.google.com/colaboratory/)」を使っていきます。

![スクリーンショット 2021-09-19 23.19.07_censored.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/37f3c2d6-468d-016e-fb00-7b26054b8c47.jpeg)

Googleアカウントさえ持っていれば面倒な環境構築の必要も無くそのままPythonを実行していけるので非常に便利です。

また、ヒストリカルデータの取得元としては「[Twelve Data](https://twelvedata.com/)」を採用しました。

![スクリーンショット 2021-09-19 23.20.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/58106eaf-26f8-2b0f-cee9-67d8cdb364db.png)

海外のサービスではありますが、ドキュメントが豊富で使いやすいAPIを提供してくれているほか、取得できる情報も仮想通貨、株、為替と幅広いので何かと重宝しそうです。


![スクリーンショット 2021-09-19 23.17.20_censored.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/ab7d2823-efa4-655c-0a51-1be50eba1686.jpeg)

Twelve DataのAPIキーに関しては、ログイン後のダッシュボードでサクッと発行できるのであらかじめ済ませておいてください。

## 実装

前置きはほどほどに、コードを書いていきましょう。


### pip install

```py:セル1
pip install twelvedata websocket
```

Google Colaboratory にはすでに一部の基本的なライブラリがあらかじめ標準装備されていたりしますが、上記のライブラリは含まれていないため、「pip install」でインストールしておきます。

### コード全体

```py:セル2
# 各種パッケージを読み込み
import datetime
import pandas as pd
pd.set_option('display.max_rows', None)
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20, 10)
import requests
from termcolor import colored as cl
from twelvedata import TDClient
import warnings
warnings.filterwarnings('ignore')

# Twelve DataのAPIキー
TWELVE_DATA_API_KEY = 'Twelve DataのAPIキー'

# ヒストリカルデータを取得
def get_historical_data(symbol, interval, exchange, outputsize, start_date, end_date, timezone):
    td = TDClient(apikey = TWELVE_DATA_API_KEY)
    
    res = td.time_series(
              symbol = symbol,
              interval = interval,
              exchange = exchange,
              outputsize = outputsize,
              start_date = start_date,
              end_date = end_date,
              timezone = timezone
          ).as_json()
    
    df = pd.DataFrame(res).iloc[::-1].set_index('datetime').astype(float)
    df = df[df.index >= start_date]
    df.index = pd.to_datetime(df.index)

    return df

# SuperTrendを作成（look_back: 振り返り期間、 multiplier: 乗数）
def make_super_trend(df, look_back = 10, multiplier = 3):
    high = df['high']
    low = df['low']
    close = df['close']
    
    # ATR（アベレージ・トゥルー・レンジ = （当日の高値 - 当日の安値）、 （当日の高値 - 前日の終値）、 （当日の安値 - 前日の終値） のうち最も大きい値）
    tr1 = pd.DataFrame(high - low)                # 当日の高値 - 当日の安値
    tr2 = pd.DataFrame(abs(high - close.shift(1))) # 当日の高値 - 前日の終値
    tr3 = pd.DataFrame(abs(low  - close.shift(1)))  # 当日の安値 - 前日の終値    
    frames = [tr1, tr2, tr3]
    tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
    atr = tr.ewm(look_back).mean()
    
    # バンド上限、バンド下限を計算
    hl_avg = (high + low ) / 2
    st_lower_band = (hl_avg - multiplier * atr).dropna()
    st_upper_band = (hl_avg + multiplier * atr).dropna()
    is_up_trend = np.zeros(len(df)) # 「is_up_trend」が1なら上昇トレンド、0なら下降トレンド

    for i in range(len(df.index)):
        current = i
        previous = i - 1

        if close[current] > st_upper_band[previous]: # 現在の終値が前のバンド上限を上回っていた場合は上昇トレンドと判定
            is_up_trend[current] = 1
        elif close[current] < st_lower_band[previous]: # 現在の終値が前のバンド下限を下回っていた場合は下降トレンドと判定
            is_up_trend[current] = 0
        else:
            is_up_trend[current] = is_up_trend[previous]

            if is_up_trend[current] == 1 and st_lower_band[current] < st_lower_band[previous]: # 上昇トレンドかつ現在のバンド下限が前のバンド下限を下回っていた場合は前のバンド下限が継続
                st_lower_band[current] = st_lower_band[previous]
            elif is_up_trend[current] == 0 and st_upper_band[current] > st_upper_band[previous]: # 下降トレンドかつ現在のバンド上限が前のバンド上限を上回っていた場合は前のバンド上限が継続
                st_upper_band[current] = st_upper_band[previous]
    
    # 上昇トレンドの場合はバンド上限を消し、下降トレンドの場合はバンド下限を消す
    for i in range(len(df.index)):
        if is_up_trend[i] == 1:
            st_upper_band[i] = np.nan
        elif is_up_trend[i] == 0:
            st_lower_band[i] = np.nan
    
    return st_lower_band, st_upper_band

# 銘柄
symbol = 'BTC/USD'

# 時間軸
interval = '1day'

# 取引所
exchange = 'BitStamp'

# 取得件数
outputsize = 5000

# 取得開始日
start_date = '2020-01-01'

# 取得終了日
end_date = datetime.datetime.now().strftime('%Y-%m-%d')

# タイムゾーン
timezone = 'Asia/Tokyo'

# ヒストリカルデータを取得
df = get_historical_data(symbol, interval, exchange, outputsize, start_date, end_date, timezone)

# SuperTrendを作成
df['st_lower_band'], df['st_upper_band'] = make_super_trend(df, 10, 3)

# チャート描画
plt.plot(df['close'], linewidth = 2, label = 'PRICE')
plt.plot(df['st_lower_band'], color = 'green', linewidth = 2, label = 'ST LOWER BAND')
plt.plot(df['st_upper_band'], color = 'red', linewidth = 2, label = 'ST UPPER BAND')
plt.legend(loc = 'upper left')
plt.show()
```

SuperTrendインジケーターの計算式は以下の通り。

- st_lower_band（緑のバンド） = hl_avg（高値と安値の平均値） + multiplier（乗数） × ATR
- st_upper_band（赤のバンド） = hl_avg（高値と安値の平均値） - multiplier（乗数） × ATR

※ multiplier（乗数）は3前後が目安。
※ ATRを計算する際のlook_back（振り返り期間）は10前後が目安。

![スクリーンショット 2021-09-25 21.58.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/701cf149-89d8-83c3-a8e0-e8238b02e109.png)

また、各バンドは常に直近における最大値（最小値）のものが継続されるという性質を持つため、以下の条件式が必要となります。

- 上昇トレンドかつ現在のバンド下限が前のバンド下限を下回っていた場合は前のバンド下限が継続
    - if is_up_trend[current] == 1 and st_lower_band[current] < st_lower_band[previous]: st_lower_band[current] = st_lower_band[previous]
- 下降トレンドかつ現在のバンド上限が前のバンド上限を上回っていた場合は前のバンド上限が継続
    - elif is_up_trend[current] == 0 and st_upper_band[current] > st_upper_band[previous]: st_upper_band[current] = st_upper_band[previous]

![ダウンロード (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/7ad3fcc5-a05b-001f-00cd-70e25f198dc2.png)

実行した結果、こんな感じのチャートが表示されていれば成功です。

![スクリーンショット 2021-09-25 22.12.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/225db3ed-4e1e-69b9-660d-27aa70d7aae0.png)

TradingViewで表示したチャートと比較してもこの通り、おおむね問題無さそうですね。

## あとがき

以上、巷で人気のSuperTrendインジケーターをPythonで再現してみました。

前述の通り日本における知名度はまだ低めな気もしますが、ごちゃごちゃとした表示も無く比較的シンプルかつ強力なインジケーターだと思うのでぜひとも活用してみたいところです。