目的
株式投資もいろんな解析手法があり、証券会社が用意しているツールもありますが、もっと網羅的に解析したいなと思い、ChatGPTに教わりながらPythonでいろいろやってみました。
財務指標フィルタリング条件
- PBRが0.6〜1.0くらいでいい感じに成長余地があるもの
- 増収増益している
- 営業キャッシュフローがプラス
ひとまずこの辺で銘柄を絞ってから時系列のグラフを作成して
- 半年間の株価が右肩上がり
- RSIが0.3近傍
つまり、成長余地があって調子がよさそうなのを抽出する。まあこの辺はいろんなやり方あると思うので、これが正解というのはないと思いますが、スクリプト調整すればいかようにでもできると思います。
使用ツール
pip install yfinance
pip install pandas
pip install ta numpy
pip install arch
財務指標抽出
import pandas as pd
import yfinance as yf
# 銘柄リスト
tickers = [
'1332.T', # 日本水産
'1333.T', # マルハニチロ
'1605.T', # 国際石油開発帝石
'1721.T', # コムシスホールディングス
'1801.T', # 大成建設
'1802.T', # 大林組
'1803.T', # 清水建設
### 中略 好きな銘柄をいれる ###
'9532.T', # 大阪ガス
'9601.T', # 松竹
'9602.T', # 東宝
'9613.T', # NTTデータ
'9735.T', # セコム
'9843.T', # ニトリホールディングス
'9983.T', # ファーストリテイリング
'9984.T', # ソフトバンクグループ
]
# データフレームを初期化
columns = ['Ticker', 'PBR', 'Revenue Growth', 'Profit Growth', 'ROE', 'PER', 'Volume', 'Operating Cash Flow']
data = []
# 各ティッカーについてデータを取得
for ticker in tickers:
stock = yf.Ticker(ticker)
info = stock.info
financials = stock.financials
cashflow = stock.cashflow
history = stock.history(period="1y") # 過去1年間のデータを取得
# PBR、ROE、PERを取得
pbr = info.get('priceToBook', None)
roe = info.get('returnOnEquity', None)
per = info.get('forwardPE', None)
# 売上高と利益の成長率を計算
try:
revenue_growth = (financials.loc['Total Revenue'][0] - financials.loc['Total Revenue'][1]) / financials.loc['Total Revenue'][1]
profit_growth = (financials.loc['Net Income'][0] - financials.loc['Net Income'][1]) / financials.loc['Net Income'][1]
operating_cash_flow = cashflow.loc['Operating Cash Flow'][0]
average_volume = history['Volume'].mean()
except:
print(f'Error: {ticker}')
continue
# PBRが1以下で、増収増益している企業をフィルタリング
if pbr is not None and pbr < 1.0 and pbr > 0.6 and revenue_growth > 0 and profit_growth > 0 and per is not None and per > 0 and operating_cash_flow > 0:
data.append([ticker, pbr, revenue_growth, profit_growth, roe, per, average_volume, operating_cash_flow])
# データフレームに変換
df = pd.DataFrame(data, columns=columns)
# フィルタリングされたティッカーのみを配列として抽出
tickers = df['Ticker'].tolist()
# 営業キャッシュフローの大きい順にソート
df = df.sort_values(by='Operating Cash Flow', ascending=False)
# 結果を表示
print(df)
Ticker PBR Revenue Growth Profit Growth ROE PER \
12 8411.T 0.808712 0.198773 0.222250 0.07001 11.776375
2 4502.T 0.900861 0.128459 0.377981 0.02116 29.982769
9 7267.T 0.657952 0.208253 0.699642 0.09651 7.328024
13 9022.T 0.809679 0.221471 0.751965 0.09682 8.573642
11 8316.T 0.927580 0.171142 0.194956 0.07022 11.606392
7 6201.T 0.699962 0.134121 0.186233 0.04695 14.279428
4 5406.T 0.740150 0.028568 0.509688 0.11067 7.254608
3 5101.T 0.708646 0.145101 0.464219 0.10695 7.008019
10 8233.T 0.916350 0.051170 0.135857 0.07214 11.494554
0 2531.T 0.927767 0.165317 0.021041 0.06631 11.607906
5 5801.T 0.870748 0.145976 0.774596 0.02460 14.360591
8 6472.T 0.632154 0.205502 0.412205 0.04695 8.857305
6 6103.T 0.925106 0.001573 0.009690 0.08681 9.664334
1 3978.T 0.842378 0.076320 1.405972 0.05733 9.349023
Volume Operating Cash Flow
12 1.205823e+07 1.884978e+12
2 4.124365e+06 9.771560e+11
9 1.560726e+07 7.472780e+11
13 2.818340e+06 6.728780e+11
11 7.459324e+06 6.428620e+11
7 6.187801e+05 4.435900e+11
4 8.656017e+06 2.052840e+11
3 6.376683e+05 1.597410e+11
10 1.344590e+06 5.953600e+10
0 7.894736e+05 4.547800e+10
5 7.098154e+05 3.651600e+10
8 5.438441e+06 3.421900e+10
6 1.771793e+05 5.251000e+09
1 1.746955e+05 2.909000e+09
上記でフィルタリングされたtickers(銘柄リスト)が取得できるので、以下でテクニカル指標を含んだ株価時系列グラフを作成。日付の期間は適宜変える必要があるが、現時点までの半年をプロットする。
株価時系列グラフ
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from arch import arch_model
# RSIを計算する関数
def calculate_RSI(data, window=14):
delta = data['Close'].diff()
gain = (delta.where(delta > 0, 0)).fillna(0)
loss = (-delta.where(delta < 0, 0)).fillna(0)
avg_gain = gain.rolling(window=window, min_periods=1).mean()
avg_loss = loss.rolling(window=window, min_periods=1).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
# MFIを計算する関数
def calculate_MFI(data, window=14):
typical_price = (data['High'] + data['Low'] + data['Close']) / 3
money_flow = typical_price * data['Volume']
positive_flow = money_flow.where(typical_price > typical_price.shift(1), 0)
negative_flow = money_flow.where(typical_price < typical_price.shift(1), 0)
positive_mf = positive_flow.rolling(window=window, min_periods=1).sum()
negative_mf = negative_flow.rolling(window=window, min_periods=1).sum()
mfi = 100 - (100 / (1 + (positive_mf / negative_mf)))
return mfi
# ボリンジャーバンドを計算する関数
def calculate_bollinger_bands(data, window=20):
sma = data['Close'].rolling(window=window).mean()
std = data['Close'].rolling(window=window).std()
data['Bollinger Middle'] = sma
data['Bollinger Upper'] = sma + (std * 2)
data['Bollinger Lower'] = sma - (std * 2)
return data
# 移動平均線を計算する関数
def calculate_moving_averages(data, short_window=5, long_window=25):
data['Short_MA'] = data['Close'].rolling(window=short_window).mean()
data['Long_MA'] = data['Close'].rolling(window=long_window).mean()
return data
# グラフのサイズを小さくして複数表示
plt.figure(figsize=(15, 60))
plt.subplots_adjust(hspace=0.5)
for i, ticker in enumerate(tickers):
data = yf.download(ticker, start="2024-01-01", end="2024-06-25", interval="1d")
if data.empty:
continue
# 必要な列を抽出
data = data[['Close', 'Volume', 'High', 'Low']].copy()
# VWAPを計算
data['cum_volume'] = data['Volume'].cumsum()
data['cum_volume_price'] = (data['Close'] * data['Volume']).cumsum()
data['VWAP'] = data['cum_volume_price'] / data['cum_volume']
# RSIを計算
data['RSI'] = calculate_RSI(data)
# MFIを計算
data['MFI'] = calculate_MFI(data)
# ボリンジャーバンドを計算
data = calculate_bollinger_bands(data)
# 移動平均線を計算
data = calculate_moving_averages(data)
# 日次リターンの計算
returns = 100 * data['Close'].pct_change().dropna()
# GARCHモデルの適用
model = arch_model(returns, vol='Garch', p=1, q=1)
model_fitted = model.fit(disp='off')
# サブプロットを設定
ax1 = plt.subplot(len(tech_tickers), 2, i * 2 + 1)
ax2 = plt.subplot(len(tech_tickers), 2, i * 2 + 2)
# 終値、VWAP、ボリンジャーバンド、移動平均線のプロット
ax1.plot(data.index, data['Close'], label='Close Price')
ax1.plot(data.index, data['VWAP'], label='VWAP', linestyle='--')
ax1.plot(data.index, data['Bollinger Middle'], label='Bollinger Middle', linestyle='--')
ax1.plot(data.index, data['Bollinger Upper'], label='Bollinger Upper', linestyle='--')
ax1.plot(data.index, data['Bollinger Lower'], label='Bollinger Lower', linestyle='--')
ax1.plot(data.index, data['Short_MA'], label='S-Day MA', linestyle='-', color='blue')
ax1.plot(data.index, data['Long_MA'], label='L-Day MA', linestyle='-', color='red')
ax1.set_title(f'{ticker} - Close Price, VWAP, Bollinger Bands & Moving Averages')
ax1.set_xlabel('Date')
ax1.set_ylabel('Price')
ax1.legend()
ax1.tick_params(axis='x', rotation=45)
# ゴールデンクロスのプロット
golden_cross = data[(data['Short_MA'] > data['Long_MA']) & (data['Short_MA'].shift(1) <= data['Long_MA'].shift(1))]
ax1.plot(golden_cross.index, data.loc[golden_cross.index, 'Short_MA'], 'o', markersize=5, label='Golden Cross', color='gold')
# RSIとMFIのプロット
ax2.plot(data.index, data['RSI'], label='RSI', color='orange')
ax2.plot(data.index, data['MFI'], label='MFI', color='purple')
ax2.axhline(70, color='red', linestyle='--')
ax2.axhline(30, color='green', linestyle='--')
ax2.set_title(f'{ticker} - RSI & MFI')
ax2.set_xlabel('Date')
ax2.set_ylabel('Indicator Value')
ax2.legend()
ax2.tick_params(axis='x', rotation=45)
data.to_csv(f'{ticker}_indicators.csv', index=False)
plt.tight_layout()
plt.show()
うーん、まあ8316.Tとか5801.TとかはRSI的に売られすぎなので買うにはよさそうに見えますね。まあこの辺は当たるも八卦当たらぬも八卦。2024.06.24の終値で株を購入したとすると、次の日は上昇してましたね。まあ一日の成績というよりはもうちょっと中期的な実力を見る必要があると思います。
感想
四季報とかで何を見るかが決まっていれば、機械的によさそうな銘柄抽出ができるのではないかと思いますが、そのへんって結構あいまいなのかもしれません。時系列のグラフも「右肩上がりでなんかよさそう」というのを、線形近似の相関度とかで表現するといいのかも。