PythonでVCPスクリーニング(続き)

前回(PythonVCPスクリーニング)の続きです。

スクリーニング手順

前回のスクリーニング手順から2点追加しました。

  • yfinance(https://pypi.org/project/yfinance/)で180日間の終値データを収集

  • Numpyのpolyfitで10次関数に近似

  • 極大値と極小値を順に並べ、それぞれの低下率を取得

  • 2Tをスクリーニング(直近の低下率<直近より一つ前の低下率)

  • 3Tをスクリーニング(直近の低下率<直近より一つ前の低下率<=直近より二つ前の低下率)

  • 2Tもしくは3Tでトレンドテンプレートに該当する銘柄を抽出

(追加)

  • 直近の極大値(凸)は一つ前の極大値から10%以上離れていない

  • 出来高が50日移動平均未満


検証

これでどれぐらいVCPパターンを拾えるか、検証のため「ミネルヴィニの成長株投資法 」の中でミネルヴィニ先生がVCPパターンの例で載せているチャートで試してみました。

以下の画像は、「第10章 百聞は一見にしかず」の「ピボットポイント」から「自然な反落とテニスボールの動き」で紹介されたチャートとの比較です。
なお、フォスターウィーラーとバラシスコミュニケーションズは株価データが取得できなかったため比較できませんでした。
また、メルカドリブレはブレイク時にチャートが180日間ないため、90日間のデータとしています。

結果

メリディアン・バイオサイエンス(VIVO)が出来高50日移動平均未満をクリアしなかった以外は、VCP判定となりました。(VIVOもチャートのみであればVCP判定)

ミネルヴィニ先生が自著で紹介するぐらい明らかなVCPパターンはほぼ拾えるのではないかと思います。
個人的にはスクリーニングでがっちり絞るよりも、スクリーニングを参考にできるだけチャートを実際に見たいというのと、スクリーニングの基準を厳しくして明らかなVCPパターンを見逃したくないので、これぐらいの基準で運用しようかなと思っています。
(後できるとしたら直近1週間で何%以上変動していないとかかな……、何か思いついたら改修するかもです)


おまけ

更新したスクリーニング手順を反映したコードです。
トレンドテンプレートの判定はしていません。

前回の投稿時にTwitterでみきりさん@mikirisenryoにいただいたアドバイスを受けて、コードの進行とプログレスバーを表示するようにしてみました、みきりさんありがとうございます!

import numpy as np
import pandas as pd
import datetime as dt
from scipy import signal
from tqdm import tqdm

print('株価データ読み込み開始')
df_365d2 = pd.read_excel('365price2.xlsx', header=[01], index_col=0#Excelから株価情報を取得
df_365d = df_365d2["Adj Close"]
df_365dv = df_365d2["Volume"]
vcp_t2 = []
vcp_t3more = []
ticker_list = list(df_365d.columns.values)

df_180d = df_365d[-180:]

print('株価データ読み込み終了')

#近似値計算--------------------------------------------------------------------------------
print('近似値計算開始')

df_180d['date_no.'] = list(range(len(df_180d),0,-1))
df_app = df_180d.copy()

x = np.array(df_app['date_no.'])

for ticker in tqdm(ticker_list):
    y = np.array(df_app[ticker])
    coe = np.polyfit(x, y, 10#10次関数に近似
    y3 = np.poly1d(coe)(x)
    df_app[ticker] = y3
    
for ticker in tqdm(ticker_list):
    maxid = signal.argrelmax(np.array(df_app.loc[:,ticker])) #極大値のindex取得
    minid = signal.argrelmin(np.array(df_app.loc[:,ticker])) #極小値のindex取得
    
    app_maxlist = df_app.index[maxid[0]] #極大値の日付取得
    app_minlist = df_app.index[minid[0]] #極小値の日付取得
    
    applist = pd.concat([df_app.loc[app_maxlist,ticker], df_app.loc[app_minlist,ticker]]).sort_index() #極大値と極小値を1つのリストにしてソート
    
    t_depth = []
    maxima_list = list(df_app.loc[app_maxlist,ticker])
    
    if len(applist) >= 4#極値が4以上(2T以上)
        if applist[0] > applist[1]: #最初の極値が極大値の時
            if len(applist) % 2 == 1#極値の数が奇数の時
                for i in range(0, len(applist)-12):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])
            else:
                for i in range(0, len(applist), 2):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])

        else:
            if len(applist) % 2 == 0:
                for i in range(1, len(applist)-12):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])
            else:
                for i in range(1, len(applist), 2):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])
                
    
        if (1 < len(t_depth) <= 2and (t_depth[-1] < 5and (maxima_list[-1] * 0.9 <= maxima_list[-2] <= maxima_list[-1] * 1.1):
            if t_depth[-1] < t_depth[-2]:
                vcp_t2.append(ticker)
        elif (len(t_depth) > 2and (t_depth[-1] < 10and (maxima_list[-1] * 0.9 <= maxima_list[-2] <= maxima_list[-1] * 1.1):
            if t_depth[-1] < t_depth[-2] <= t_depth[-3]:
                vcp_t3more.append(ticker)
            elif (t_depth[-1] < t_depth[-2]) and (t_depth[-2] > t_depth[-3]):
                vcp_t2.append(ticker)
print('近似値計算終了'#出来高判定--------------------------------------------------------------------------------------
print('出来高判定開始') 

df_365dv.loc['50ma',:] = list(df_365dv.rolling(50).mean().iloc[-1,:])
v_underave_list = df_365dv.columns[df_365dv.iloc[-1,:] > df_365dv.iloc[-2,:]].tolist() #直近出来高が50日平均未満

print('出来高判定終了') 

print('VCP出力') 
tt_vcpt2 = list(set(vcp_t2) & set(v_underave_list))
tt_vcpt3more = list(set(vcp_t3more) & set(v_underave_list))
tt_vcpt2.sort()
tt_vcpt3more.sort()
print(tt_vcpt2)
print(tt_vcpt3more)


おまけ2

週足での判定も載せておきます。ほとんど同じですが……

import numpy as np
import pandas as pd
import datetime as dt
from scipy import signal
from tqdm import tqdm

print('株価データ読み込み開始')
df_365d2 = pd.read_excel('365price2.xlsx', header=[01], index_col=0#Excelから株価情報を取得
df_365d = df_365d2["Adj Close"]
df_365dw = df_365d2["Adj Close"].resample("W").agg('last')
df_365dv = df_365d2["Volume"].resample("W").agg('sum')
vcp_t2 = []
vcp_t3more = []
ticker_list = list(df_365d.columns.values)

print('株価データ読み込み終了')

#近似値計算--------------------------------------------------------------------------------
print('近似値計算開始')
df_365dw['date_no.'] = list(range(len(df_365dw),0,-1))
df_app = df_365dw.copy()

x = np.array(df_app['date_no.'])

for ticker in tqdm(ticker_list):
    y = np.array(df_app[ticker])
    coe = np.polyfit(x, y, 10#10次関数に近似
    y3 = np.poly1d(coe)(x)
    df_app[ticker] = y3
    
for ticker in tqdm(ticker_list):
    maxid = signal.argrelmax(np.array(df_app.loc[:,ticker])) #極大値のindex取得
    minid = signal.argrelmin(np.array(df_app.loc[:,ticker])) #極小値のindex取得
    
    app_maxlist = df_app.index[maxid[0]] #極大値の日付取得
    app_minlist = df_app.index[minid[0]] #極小値の日付取得
    
    applist = pd.concat([df_app.loc[app_maxlist,ticker], df_app.loc[app_minlist,ticker]]).sort_index() #極大値と極小値を1つのリストにしてソート
    
    t_depth = []
    maxima_list = list(df_app.loc[app_maxlist,ticker])
    
    if len(applist) >= 4#極値が4以上(2T以上)
        if applist[0] > applist[1]: #最初の極値が極大値の時
            if len(applist) % 2 == 1#極値の数が奇数の時
                for i in range(0, len(applist)-12):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])
            else:
                for i in range(0, len(applist), 2):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])

        else:
            if len(applist) % 2 == 0:
                for i in range(1, len(applist)-12):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])
            else:
                for i in range(1, len(applist), 2):
                    t_depth.append(( applist[i + 1] - applist[i] ) * -100 / applist[i])
                
    
        if (1 < len(t_depth) <= 2and (t_depth[-1] < 5and (maxima_list[-1] * 0.9 <= maxima_list[-2] <= maxima_list[-1] * 1.1):
            if t_depth[-1] < t_depth[-2]:
                vcp_t2.append(ticker)
        elif (len(t_depth) > 2and (t_depth[-1] < 10and (maxima_list[-1] * 0.9 <= maxima_list[-2] <= maxima_list[-1] * 1.1):
            if t_depth[-1] < t_depth[-2] <= t_depth[-3]:
                vcp_t3more.append(ticker)
            elif (t_depth[-1] < t_depth[-2]) and (t_depth[-2] > t_depth[-3]):
                vcp_t2.append(ticker)
print('近似値計算終了') 
                
#出来高判定--------------------------------------------------------------------------------------
print('出来高判定開始') 

df_365dv.loc['10wma',:] = list(df_365dv.rolling(10).mean().iloc[-1,:])
v_underave_list = df_365dv.columns[df_365dv.iloc[-1,:] > df_365dv.iloc[-2,:]].tolist() #直近出来高が50日平均未満

print('出来高判定終了') 

tt_vcpt2 = list(set(vcp_t2) & set(v_underave_list))
tt_vcpt3more = list(set(vcp_t3more)& set(v_underave_list))
tt_vcpt2.sort()
tt_vcpt3more.sort()
print(tt_vcpt2)
print(tt_vcpt3more)