テクニカル分析ライブラリTA-Lib

April 28, 2021

テクニカル分析python

はじめに

pythonでテクニカル分析を行う場合、各種指標の算出にTA-Libを利用するケースがあります。ネット上にはTA-Libを利用したサンプルソースも数多く存在しています。本稿では、TA-Libとpythonで自前で用意したテクニカル指標算出関数の比較(答え合わせ)を行います。

TA-Libのインストール

TA-Libはpython専用のライブラリという訳ではなく、各種言語用のインターフェースを持つライブラリのようで、pythonのインターフェースとTA-Lib本体のバイナリの両方インストールする必要があります。

TA-Lib自体は、ご本尊のTA-Lib : Technical Analysis Libraryにあります。ここからソースコードを取得し、自分でバイナリをビルドすることも可能ですが、めんどいので、野良ビルドされた私設パッケージを使うのが吉。

Windowsの場合は、pip一発でインストールを可能にする私設パッケージがこちらに置かれています。

ここから、使用するpythonのバージョン用の私設パッケージを入手します。例えばwindowsがx64で、pythonのバージョンが3,8の場合、

TA_Lib‑0.4.19‑cp38‑cp38‑win_amd64.whl

をダウンロードします。ダウンロード後、以下のコマンドでインストールします。

C:\> pip install "c:\download_path\TA_Lib‑0.4.19‑cp38‑cp38‑win_amd64.whl"

TA-Libの利用

例えば、以下のようなcsvファイルを読み込み、macdを算出するコードは、次の通りです。

1332.csv
2015/01/05,375,376,366,373,1831000000
2015/01/06,367,372,362,363,1782900000
  ,,,,,.
2020/01/16,621,624,613,616,2468700000
2020/01/17,618,622,613,618,1676800000
2020/01/20,618,621,616,616,1231700000
import talib
import pandas as pd
path = './1332.csv'
df = pd.read_csv(path, header=None,
                 names=['Date','Open','High','Low','Close','Volume'], encoding='UTF-8')
df = df.set_index("Date")

macd = talib.MACD(df['Close'], fastperiod=26, slowperiod=12, signalperiod=9)

macd[0]がmacdの値、macd[1]がsignalの値、macd[2]がmacd-signalの値となります。

TA-Libの利用の注意点

テクニカル分析は、あくまで私見ですが、分析結果そのものに意味がある訳ではなく、他の市場参加者も自分と同じ指標に基づいて売買を行っているというところに意味があります。つまり、テクニカル信者がある指標での買いシグナルを根拠にある銘柄を買うとすると、同じテクニカルを信じる別の信者も同じタイミングで買いを入れるはずなので、買うから上がる、上がるから買うのレバレッジが働き、信者達の目論見通りに株価が動く(はず)という訳です。

そのため、「他の信者と同じ指標値」という点が特に重要です。

TA-Libはブラックボックスとして容易に利用できてしまうので、中でどういう計算が行われているのかは留意すべきです。つまり、「TA-Libで算出した指標が、ほんとうに他の信者と同じなのか?」を留意すべきです。これはTA-Libに限った話ではありませんが。

そこで、次節からはTA-Libとそれ以外の方法の指標の比較を行っていきます。

macd

移動平均線のゴールデンクロスよりもシグナルが早く出るということで人気のmacdを見ていきます。 pythonでmacdを自前で計算させると、次のようなコードになるでしょう。そりゃ、意識高い系プログラマーならもっと簡潔にエレガントに書くのでしょうが、泥グラマーが書けばこんなものでしょう。

def calc_macd(df, es, el, sg):
    macd = pd.DataFrame()
    macd['ema_s'] = df['Close'].ewm(span=es).mean()
    macd['ema_l'] = df['Close'].ewm(span=el).mean()
    macd['macd'] = macd['ema_s'] - macd['ema_l']
    macd['signal'] = macd['macd'].ewm(span=sg).mean()
    macd['diff'] = macd['macd'] - macd['signal']
    f_plus = lambda x: x if x > 0 else 0
    f_minus = lambda x: x if x < 0 else 0
    macd['diff+'] = macd['diff'].map(f_plus)
    macd['diff-'] = macd['diff'].map(f_minus)
    return macd

これと、TA-Libの計算結果を比較してみます。

import numpy as np
import talib
import pandas as pd

def compare_macd(df):
    macd = calc_macd(df, 12, 26, 9)

    close = np.array(df['Close']*1.0)
    tamacd = talib.MACD(close, fastperiod=26, slowperiod=12, signalperiod=9)

    compare = pd.DataFrame()
    compare['my_macd'] = macd['macd']
    compare['ta_macd'] = tamacd[0]
    compare['my_signal'] = macd['signal']
    compare['ta_signal'] = tamacd[1]
    compare['my_diff'] = macd['diff']
    compare['ta_diff'] = tamacd[2]
    pd.set_option('display.max_rows', 2000)
    print(compare)

def main():
    path = './1332.csv'
    df = pd.read_csv(path, header=None,
                     names=['Date','Open','High','Low','Close','Volume'], encoding='UTF-8')
    df = df.set_index("Date")
    compare_macd(df)

if __name__ == '__main__':
    main()

比較結果は以下の通り。

              my_macd    ta_macd  my_signal  ta_signal    my_diff    ta_diff
Date
2015/01/05   0.000000        NaN   0.000000        NaN   0.000000        NaN
2015/01/06  -0.224359        NaN  -0.124644        NaN  -0.099715        NaN
2015/01/07  -0.130828        NaN  -0.127178        NaN  -0.003650        NaN
...              ...       ...        ...        ...       ...       ...
2015/02/19  -4.321830        NaN  -4.100060        NaN  -0.221770        NaN
2015/02/20  -3.521985        NaN  -3.984371        NaN   0.462387        NaN
2015/02/23  -1.813523  -0.809359  -3.549981  -1.676602   1.736458   0.867243
2015/02/24  -0.231990   0.594899  -2.886114  -1.222302   2.654124   1.817201
2015/02/25   1.233390   1.927638  -2.061946  -0.592314   3.295335   2.519952
2015/02/26   3.120983   3.747560  -1.025091   0.275661   4.146074   3.471899
...              ...       ...        ...        ...       ...       ...
2015/10/30  10.144012  10.144011   6.522003   6.521999   3.622010   3.622012
2015/11/02   9.210587   9.210586   7.059719   7.059716   2.150867   2.150869
2015/11/04   8.613623   8.613622   7.370500   7.370498   1.243123   1.243125
2015/11/05   8.685932   8.685931   7.633586   7.633584   1.052345   1.052347
2015/11/06   8.164967   8.164966   7.739862   7.739861   0.425104   0.425106
...              ...       ...        ...        ...       ...       ...
2016/01/14  50.708566  50.708567  54.114835  54.114836  -3.406269  -3.406269
2016/01/15  46.351386  46.351387  52.562146  52.562146  -6.210759  -6.210759
2016/01/18  42.249876  42.249877  50.499692  50.499692  -8.249815  -8.249816
2016/01/19  38.156102  38.156103  48.030974  48.030974  -9.874872  -9.874872
2016/01/20  34.593674  34.593674  45.343514  45.343514 -10.749840 -10.749840
...              ...       ...        ...        ...       ...       ...
2020/01/15  -4.451281  -4.451281  -0.986967  -0.986967  -3.464313  -3.464313
2020/01/16  -5.508248  -5.508248  -1.891224  -1.891224  -3.617025  -3.617025
2020/01/17  -6.114041  -6.114041  -2.735787  -2.735787  -3.378254  -3.378254
2020/01/20  -6.678533  -6.678533  -3.524336  -3.524336  -3.154196  -3.154196
  • 2015/01/05~2015/02/20:最初の算出不能期間の扱いが異なる。
  • 2015/02/23~2016/01/19:最初は差が大きいが徐々に差がなくなる
  • 2016/01/20~:一致

いまいち意味不明な結果に。

直近の結果はTA-Libと自前計算は一致するものの、算出期間の初期は微妙に異なる。バックテストなんかでTA-Libを利用する場合は要注意か。

stochastic

ストキャスティクスは、売られすぎ/買われすぎをあらわすオシレーター。

泥グラマーが書いたコードはコチラ。

def calc_stochastic(df, term):
    stochastic = pd.DataFrame()
    stochastic['%K'] = ((df['Close'] - df['Low'].rolling(term).min()) \
                        / (df['High'].rolling(term).max() - df['Low'].rolling(term).min())) * 100
    stochastic['%D'] = stochastic['%K'].rolling(3).mean()
    stochastic['%SD'] = stochastic['%D'].rolling(3).mean()
    return stochastic

これと、TA-Libの計算結果を比較してみます。

def compare_stochastic(df):
    term = 14
    stochastic = calc_stochastic(df, term)

    high = np.array(df['High']*1.0)
    low = np.array(df['Low']*1.0)
    close = np.array(df['Close']*1.0)
    tastoch = talib.STOCH(high, low, close,
                          fastk_period=term, slowk_period=3)

    compare = pd.DataFrame()
    compare['my_%D'] = stochastic['%D']
    compare['ta_%D'] = tastoch[0]
    compare['my_%SD'] = stochastic['%SD']
    compare['ta_%SD'] = tastoch[1]
    pd.set_option('display.max_rows', 2000)
    print(compare)

比較結果は以下の通り。

$ python test_compare_ta.py
                my_%D      ta_%D     my_%SD     ta_%SD
Date
2015/01/05        NaN        NaN        NaN        NaN
2015/01/06        NaN        NaN        NaN        NaN
...               ...        ...        ...        ...
2015/01/23        NaN        NaN        NaN        NaN
2015/01/26        NaN        NaN        NaN        NaN
2015/01/27  62.183236        NaN        NaN        NaN
2015/01/28  81.098580        NaN        NaN        NaN
2015/01/29  87.844612  87.844612  77.042142  77.042142
2015/01/30  88.553114  88.553114  85.832102  85.832102
2015/02/02  80.476550  80.476550  85.624758  85.624758
...               ...        ...        ...        ...
2020/01/16  13.388334  13.388334  18.542362  18.542362
2020/01/17   8.988276   8.988276  13.962464  13.962464
2020/01/20   7.482993   7.482993   9.953201   9.953201
  • 2015/01/27~2015/02/28:算出不能期間の扱いが異なる。
  • 2015/01/29~:一致

TA-Libと自前計算の結果は一致している。

rsi

rsiは、売られすぎ/買われすぎをあらわすオシレーター。

泥グラマーが書いたコードはコチラ。

def calc_rsi(df, term):
    rsi = pd.DataFrame()
    rsi['diff'] = df['Close'].diff()    
    rsi['diff+'] = rsi['diff'].copy()
    rsi['diff-'] = rsi['diff'].copy()
    rsi['diff+'][rsi['diff']<0] = 0
    rsi['diff-'][rsi['diff']>0] = 0

    rsi['up-sum']=rsi['diff+'].rolling(term).sum() # ※
    rsi['down-sum']=rsi['diff-'].abs().rolling(term).sum() # ※
    rsi['rsi']=rsi['up-sum']/(rsi['up-sum']+rsi['down-sum'])*100.0
    return rsi

これと、TA-Libの計算結果を比較してみます。

def compare_rsi(df):
    term = 14
    rsi = calc_rsi(df, term)

    close = np.array(df['Close']*1.0)
    tarsi = talib.RSI(close, timeperiod=term)

    compare = pd.DataFrame()
    compare['my_rsi'] = rsi['rsi']
    compare['ta_rsi'] = tarsi
    pd.set_option('display.max_rows', 2000)
    print(compare)

比較結果は以下の通り。

$ python test_compare_ta.py
               my_rsi     ta_rsi
Date
2015/01/05        NaN        NaN
2015/01/06        NaN        NaN
...               ...        ...
2015/01/22        NaN        NaN
2015/01/23        NaN        NaN
2015/01/26  54.000000  54.000000
2015/01/27  65.384615  60.026738
2015/01/28  65.048544  61.575722
2015/01/29  63.207547  57.948333
...               ...        ...
2020/01/14  38.938053  44.722191
2020/01/15  41.904762  42.685062
2020/01/16  38.095238  41.333334
2020/01/17  36.893204  42.316929
2020/01/20  27.956989  41.566426
  • 2015/01/05~2015/01/23:算出不能期間は一致。
  • 2015/01/26:1日目の算出結果は一致。
  • 2015/01/27~:以降は全部異なる

おそらく算出方法が異なるのだとは思うが、2015/01/26だけ一致しているのがとても不思議。

直近の200日分を可視化してみる。

talib_rsi

TA-Libのrsiの方が、振れ幅が小さく、シグナルがより出にくいようなグラフです。

TA-Libを利用してシグナルを出すようなことを考える場合は要注意です。

Money Partnersのサイトの解説によれば、「rsiの算出方法として、平均上昇幅・下落幅を「指数平滑移動平均線」で算出する方法もある」という記載がありますが、そこには計算式は書かれておらず、TA-Libがその方法を採用しているのかもわからない。

まとめ

WindowsにTA-Libをインストールする方法と、使用上の注意点をまとめました。

ライブラリを利用してテクニカル分析による売買シグナルを検討する場合、使用するライブラリがどうやって計算しているかはよく調べるべし。TA-Libしかり。


Written by questions6768 who lives in Uji, Kyoto.