数学がわからない

日々の勉強をアウトプットする。

Pythonで銘柄ピックアップ②安値切り下げ

株価が十分に下がっている銘柄をピックアップするスクリプトを書く。
株価に関する参考文献は下記。

世界一やさしい 株の練習帖 1年生

世界一やさしい 株の練習帖 1年生

はじめに

株価が十分に下がっている「上昇1回目の買いをしていい条件」を参考文献から引用する。

① 2~3カ月以上下げ続けたか?
② 3回以上安値切り下げ(3段下げ完成)があったのか?
③ 株価が直近になって75日移動平均線に近づいてきたか?
No.247/2268

条件①については以下。

gyokan.hatenablog.com

続いて、条件②を満たしている銘柄を取得するコードをPythonで書く。

方法

条件② 3回以上安値切り下げ(3段下げ完成)があったのか?

条件②を考えるための「安値」について。毎日の安値を曲線と考えての変曲点を求め、極小値を直近安値として求めるものとする。

gyokan.hatenablog.com

条件②は、直近安値が更新される際、更新後の直近安値が更新前の直近安値よりも安くなることが3回以上発生するということ。

直近安値の履歴

直近安値の履歴は次のように取得する。

import numpy as np

def calc_most_recent_low(data, width=5):
    """
    直近安値を求める。
    :param data: 対象データ列。安値のリストを想定。
    :param width: 平均計算等のウィンドウ幅
    :return: 最小値リスト、変曲点リスト
    """"""  """
    # widthデータからの移動平均と最小値を取得
    temp = [np.array(data[width - 1:])]
    for n in range(width - 1):
        temp = np.insert(temp, 0, np.array(data[width - 1 - 1 - n:-1 - n]), axis=0)
    average_data = np.mean(temp, axis=0)
    min_data = np.min(temp, axis=0)
    # 要素数をdataと同じ要素数に揃える
    for n in range(width - 1):
        average_data = np.insert(average_data, 0, average_data[0])
        min_data = np.insert(min_data, 0, min_data[0])

    # 移動平均を曲線と見なして微分、符号が負から正に変わる変曲点を取得
    diff = average_data[1:] - average_data[0:-1]
    inflection_point = (diff[0:-1] <= 0) * (diff[1:] > 0)
    # 要素数を揃える。
    inflection_point = np.insert(inflection_point, 0, False)
    inflection_point = np.append(inflection_point, False)

    # 変曲点で最小値が更新される直近安値リストを生成
    most_recent_low = np.zeros(len(data))
    for n in range(len(most_recent_low)):
        if n == 0 or inflection_point[n]:
            most_recent_low[n] = min_data[n]
        else:
            most_recent_low[n] = most_recent_low[n - 1]

    return [most_recent_low, inflection_point, average_data]

この関数では、毎日の直近安値most_recent_low、変曲点かどうかinflection_point、変曲点の取得に用いる移動平均線average_dataが返される。

実際の株価のチャートを表示してみると次の通り。
f:id:rettouseinosugakubenkyo:20190119071835p:plain

ロウソク足、移動平均、直近安値履歴を表示する関数
import matplotlib.pyplot as plt
import mpl_finance

def plot_ohlc_and_mrl(df, days = 100, width = 1):
    # 直近安値を取得
    most_recent_low, inflection_point, aline = calc_most_recent_low(df.安値, width=width)
    a75 = get_aveline(df.終値, 75)
    
    # 表示
    fig = plt.figure(figsize=[15, 5])
    ax = fig.add_subplot(111)

    # ローソク足(データの後半をdays日分切り取り)
    ohlc = np.array([list(range(len(df))), list(df.始値), list(df.安値), list(df.高値), list(df.終値)]).T
    mpl_finance.candlestick_ohlc(ax, ohlc[-days:], width=0.7, colorup='g', colordown='r')

    # x軸
    x_index = range(len(df) - days, len(df))
    # 移動平均線
    ax.plot(x_index, aline[-days:])
    ax.plot(x_index, a75[-days:])
    # 直近安値
    ax.plot(x_index, most_recent_low[-days:], 'b.')
    # 変曲点
    for i in x_index:
        if inflection_point[i]:
            ax.plot(i, most_recent_low[i], 'ro')

    # x軸ラベル
    skip = 5
    x_index_skip = range(len(df) - 1 - skip*(days//skip), len(df), skip)
    x_label = [x for x in df.index][-1-skip*(days//skip)::skip]
    plt.xticks(x_index_skip, x_label)

    # 表示データ範囲の指定
    ax.set_xlim(len(df) - days, len(df))

    ax.grid() # グリッド
    fig.autofmt_xdate() # x軸のオートフォーマット
    plt.show()

    return most_recent_low, inflection_point

直近安値の更新

この株の安値更新履歴は次のように取得することができる。
f:id:rettouseinosugakubenkyo:20190119072002p:plain

更新時に前回の安値との大小を求めることで切り下げかそうでないかを判断できる。この株は3カ月にわたって安値を切り下げ続けている事が分かる。

実験

以上の操作を手元に持っているすべての銘柄データに対して行い、条件②を満たす銘柄をピックアップする。

import glob
import os
import numpy as np
import matplotlib.pyplot as plt
import sys

def check_cond1and2(d=0):
    fs = glob.glob(r"..\..\stock_data\*.csv")

    error_num = 0
    declining_days_list = []
    num_of_kirisage_list = []
    pick_up_fs = []
    for f in fs:
        sys.stdout.write("\r%s" %f)
        sys.stdout.flush()
        try:
            stock_df = get_dataframe_from_csv(os.path.join(f))
            if d>0:
                stock_df = stock_df[:-d]
            a75 = get_aveline(stock_df.終値, 75)
            # 終値が75日移動平均よりも上となっている要素の番号リスト
            ids = np.where([a75<stock_df.終値][0])[0]
            # 下落開始日
            id = ids[-1] + 1
            if id < len(stock_df):
                # 下落開始日からの日数        
                declining_days = len(stock_df) - (id)
                # 直近安値
                most_recent_low, inflection_point, aline = calc_most_recent_low(stock_df.安値, width=3)
                temp = most_recent_low[-declining_days:][inflection_point[-declining_days:]]
                num_of_kirisage = (temp[0:-1] > temp[1:]).sum()
                #print('下落中:', stock_df.終値[id-1], a75[id-1], stock_df.index[id], stock_df.終値[id], a75[id], declining_days)        
            else:
                declining_days = 0
                num_of_kirisage = 0
                #print('上昇中', stock_df.index[-1], stock_df.終値[-1], a75[-1], declining_days)        
            declining_days_list.append(declining_days)       
            num_of_kirisage_list.append(num_of_kirisage)
            if declining_days >= 60 and num_of_kirisage >= 3:
                pick_up_fs.append(f)
        except IndexError:
            error_num += 1


    fig = plt.figure()
    ax = fig.add_subplot(1,2,1)
    ax.hist(declining_days_list, 50)
    ax.set_title('declining_days')
    ax.set_xlabel('day')
    ax.set_ylabel('num')
    ax.grid()

    ax = fig.add_subplot(1,2,2)
    ax.hist(num_of_kirisage_list, 50)
    ax.set_title('num_of_kirisage')
    ax.set_xlabel('day')
    ax.set_ylabel('num')
    ax.grid()

    plt.show()

    print('調査銘柄数:', len(declining_days_list))
    print('最大下落日数:', np.max(declining_days_list), '日')
    print('平均下落日数:', np.mean(declining_days_list), '日')
    print('下落中:', np.sum(np.array(declining_days_list)>0), '銘柄')
    print('条件①達成(2,3ヵ月(60営業日)以上下落中):', np.sum(np.array(declining_days_list)>=60), '銘柄')
    print('条件②達成(3回以上安値切り下げ):', np.sum(np.array(num_of_kirisage_list)>=3), '銘柄')
    
    print('条件①かつ条件②達成:', np.sum((np.array(declining_days_list)>=60)*(np.array(num_of_kirisage_list) >=3)), '銘柄')

    return declining_days_list, num_of_kirisage_list, pick_up_fs

declining_days_list, num_of_kirisage_list, pick_up_fs = check_cond1and2()

結果

手元に持っているすべての株価データに対して条件①条件②の達成を調べると次のようになる。

f:id:rettouseinosugakubenkyo:20190120071506p:plain

これら「売られ過ぎ」としてピックアップしたいくつかの銘柄のチャートを示す。

f:id:rettouseinosugakubenkyo:20190120072914p:plain

f:id:rettouseinosugakubenkyo:20190120073002p:plain

どの銘柄も容赦なく株価を下げている。
しかし、3616銘柄中1082銘柄ピックアップでは、全くスクリーニングになっていない。

まとめ

参考文献記載の「上昇1回目の買いをしていい条件」のうち2つ目「② 3回以上安値切り下げ(3段下げ完成)があったのか?」までを満たす銘柄を取得するコードをPythonで書いた。手元にある3616データのうち条件を満たす銘柄の数は1082銘柄であり、かなり多くの銘柄が「売られ過ぎ」と言える。

次は、この中からさらに条件⓷を満たす、買うべき銘柄をピックアップする方法を検討する。