【第7回】テクニカル指標による銘柄スクリーニング

December 18, 2020

株価サーバーテクニカル分析python

無一物中Project記事一覧


はじめに

無一物中Project第1回~第4回の連載で、無尽蔵サイトで公開されている株価データを利用して株価データベースを構築しました。第5回~第6回の連載では、テクニカルチャートを表示してみました。

今回は、構築した株価データベースを使用して銘柄スクリーニングをしてみます。巷のサイトのようにWebベースで操作できる訳でなく、GUIがある訳でもないので、使い勝手は悪いですが、pythonでプログラミングすることにより、相当柔軟性が高いスクリーニングが可能になります。

全銘柄を舐めるスクリーニングフレームワーク

まずは、株価データベース中の全銘柄を舐めまわすコードを考えます。 株価データベースは以下のように特定の名前のディレクトリに銘柄毎の時系列csvファイルが置かれた構成を想定しています。 dataディレクトリには、証券コードの1000の桁毎のサブディレクトリを切り、その中に、銘柄毎の株価日足時系列データのcsvファイルが置かれているものとします。

working_root/
    data/
        1000/
            1001.csv
            1002.csv
            ...
        2000/
        ...
        9000/
            9001.csv
            ...

以下のコードは、株価データベース中の全銘柄を舐めまわすための簡単なスクリーニングフレームワークです。関数process()が全銘柄に対して呼び出されます。この例では何もしていませんが、process()には、時系列株価のDataFrameオブジェクトが引数で渡ってくるので、ここにテクニカル分析するなりのコードを書けば、銘柄スクリーニングができる訳です。

walk_around.py
import os
import glob
import muzinzo.adjust_close_value as mzl

def process(code, df):
    result = False
    # ここにテクニカル分析かなにかのコードを入れる
    # 抽出した銘柄ならTrueを返す
    return result
    
def walk_around():
    for i in range(1, 10):
        csv_files = glob.glob('./data/{}/*.csv'.format(i*1000))
        for csv_file in csv_files:
            filename = os.path.basename(csv_file)
            code = filename[0:4]
            df = mzl.load_stock_price_csv(csv_file, term=100)
            if process(code, df):
                print('buy: '+code)

def main():
    walk_around()

if __name__ == '__main__':
    main()

テクニカル分析

筆者が参考にするテクニカル指標は、スローストキャスティクス、macdあたりでしょうか。あとは古典的な移動平均線とか。テクニカルは絶対視はしていないので、まぁどうでもよいというか、刺身のつまというか。

ストキャスティクスは相場の過熱感をあらわす指標で、80%以上で買われすぎ、20%以下なら売られすぎ(閾値を80%, 20%固定ではなくお好みで)をあらわします。ストキャスティクスには、%K, %D, %SDの3つの指標があり、

  • %Dと%SDが売られすぎ領域(20%以下)でゴールデンクロスならば買い
  • %Dと%SDが買われすぎ領域(80%以上)でデッドデンクロスならば売り

という判断がよく使われます。%Dと%SDではなく、%Kと%Dを使うほうがより敏感に反応するらしいのですが、感度が良すぎると弊害として虚報の多くなるので一長一短です。%Dと%SDを使う方法を「スロー」と呼んでいるようです。

macdには、macdとsignalの2つの指標があり、

  • macdとsignalがゴールデンクロスならば買い
  • macdとsignalがデッドデンクロスならば売り

という判断がよく使われます。これもmacdとsignalが互いに絡み合うようなときは虚報が多数発生することになるので、短期間でクロスを繰り返すようなケースは取り除く必要もあるでしょう。

あとは、古典的なテクニカル分析の王道である、移動平均線mavのゴールデンクロスもいれときましょう。

なわけで、銘柄毎にmav, stochastic, macdを算出し、

直近日に、

  • 中期移動平均線と長期移動平均線がゴールデンクロス
  • %Dと%SDが売られすぎ領域でゴールデンクロス
  • macdとsignalがゴールデンクロス

のいずれかのシグナルが点灯した銘柄を抽出するコードを書いてみます。

テクニカルスクリーニングの実装

ストキャスティクスとmacdを計算するコードは、本連載【第6回】mplfinanceを使用してテクニカルチャート表示 で紹介したコードをそのまま利用します。再掲すると、以下の関数です。

import pandas as pd

def calc_mav(df, s, m, l):
    mav = pd.DataFrame()
    mav["mav_s"] = df["Close"].rolling(s).mean()
    mav["mav_m"] = df["Close"].rolling(m).mean()
    mav["mav_l"] = df["Close"].rolling(l).mean()
    return mav

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()
    stochastic['UL'] = 80
    stochastic['DL'] = 20
    return stochastic

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

直近日のゴールデンクロス、デッドクロスの判定関数は次のようにしました。やや強引なコードですが。

def calc_todays_gc(shorts, longs):
    # 直近日がゴールデンクロスならTrueを返す
    if len(shorts)<75:
        return False
    today = len(shorts) - 1
    yesterday = len(shorts) - 2
    short0 = shorts[yesterday]
    long0 = longs[yesterday]
    short = shorts[today]
    long = longs[today]
    if not math.isnan(short0) and not math.isnan(long0) and not math.isnan(short) and not math.isnan(long):
        if (short0-long0)<0 and (short-long)>0:
            return True
    return False

データ蓄積日数が75日未満なら判定を諦めていますが、使用する指標がストキャスティクスとmacdだけならもっと短い期間でもよいのですが、後々75日移動平均線なんかも使おうかと考えており、あとあと面倒がないようにざっくり75日で足切りしました。

期間内での過去のゴールデンクロスを全て求める場合は以下のようなコードになります。

def calc_gc(shorts, longs):
    # ゴールデンクロス検出
    gcs = []
    short0 = shorts[0]
    long0 = longs[0]
    for i in range(len(shorts)):
        short = shorts[i]
        long = longs[i]
        if not math.isnan(short0) and not math.isnan(long0) and not math.isnan(short) and not math.isnan(long):
            if (short0-long0)<0 and (short-long)>0:
                gcs.append(shorts.index[i])
        short0 = short
        long0 = long
    return gcs

ここまで準備した状態で、先に紹介した全銘柄を舐めるフレームワークのprocess()関数を書いてやれば、スクリーニングコードが完成です。

移動平均線のゴールデンクロス

例えば、5日移動平均線と25日移動平均線のゴールデンクロス銘柄を見つけたいなら次のようになります。

walk_around.py
def process(code, df):
    mav = calc_mav(df, 5, 25, 75)
    mav_buy = calc_todays_gc(mav['mav_s'], mav['mav_m'])
    if calc_todays_gc(mav['mav_s'], mav['mav_m']):
         return True
     else:
         return False

macdのゴールデンクロス

macdとsignalのゴールデンクロス銘柄を見つけたいなら次のようになります。

walk_around.py
def process(code, df):
    macd = calc_macd(df, 12, 26, 9)
    macd_buy = calc_todays_gc(macd['macd'], macd['signal'])
    days = calc_macd_accuracy(macd)
    if macd_buy and days>10:
        return True
    else:
        return False

短期間のうちにmacdとsignalが交差をくりかえすようなダマしを回避するため、macdとsignalの直近の1つ前の交点までの期間を以下のコードで算出し、その期間が10日以下だったらダマしと判断するようにしています。見逃しが発生するリスクもありますが。。。

walk_around.py
def calc_macd_accuracy(macd):
    gc1 = calc_gc(macd['macd'], macd['signal'])
    gc2 = calc_gc(macd['signal'], macd['macd'])
    gc1.extend(gc2)
    gc = sorted(gc1)
    if len(gc)>=2:
        term = gc[len(gc)-1] - gc[len(gc)-2]
        return term.days
    return 0

ストキャスティクスのゴールデンクロス

ストキャスティクス%Dと%SDが売られすぎ領域(20%以下)でゴールデンクロスとなった銘柄を見つけたいなら次のようになります。

walk_around.py
def process(code, df):
    stochastic = calc_stochastic(df, 14)
    stochastic_buy = calc_todays_gc(stochastic['%D'], stochastic['%SD'])
    if stochastic_buy and stochastic['%D'][today]<20 and stochastic['%SD'][today]<20:
        return True
    else:
        return False

次のステップ

あとは複数の指標を組み合わせてand/orをとったりして、自分なりの複雑なスクリーニングを実装しましょう。

デッドクロスを算出する場合は、ゴールデンクロス算出関数 calc_todays_gc(), calc_gc()関数の引数を入れ替えるだけで、使いまわしできます。

ここで紹介したコードは、スクリーニングで抽出した銘柄の証券コードを表示するだけなのですが、これではあまりにも不親切なので、抽出した銘柄のチャートをファイルに出力するようにしておくとよいでしょう。

チャートのファイル出力例

    mpf.plot(df, type='candle', volume=True,
             addplot=apd_oscilator,
             savefig=dict(fname=code+'.png',dpi=100))

まとめ

無一物中Project第7回では、これまでに構築した株価データベースを使って、テクニカル指標により銘柄スクリーニングする方法を紹介しました。


Written by questions6768 who lives in Uji, Kyoto.