ChatGPTに教わりながら株式投資の指標を作ってみた

# 目的
株式投資もいろんな解析手法があり、証券会社が用意しているツールもありますが、もっと網羅的に解析したいなと思い、ChatGPTに教わりながらPythonでいろいろやってみました。

# 財務指標フィルタリング条件
- PBRが0.6〜1.0くらいでいい感じに成長余地があるもの
- 増収増益している
- 営業キャッシュフローがプラス

ひとまずこの辺で銘柄を絞ってから時系列のグラフを作成して
- 半年間の株価が右肩上がり
- RSIが0.3近傍

つまり、成長余地があって調子がよさそうなのを抽出する。まあこの辺はいろんなやり方あると思うので、これが正解というのはないと思いますが、スクリプト調整すればいかようにでもできると思います。

``` Python:使用ツール
pip install yfinance
pip install pandas
pip install ta numpy
pip install arch
```

``` Python:財務指標抽出
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（銘柄リスト）が取得できるので、以下でテクニカル指標を含んだ株価時系列グラフを作成。日付の期間は適宜変える必要があるが、現時点までの半年をプロットする。

``` Python:株価時系列グラフ
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()
```

プロットの出力は以下の通り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/c84e2554-6613-2562-103b-aae7388795e9.png)

うーん、まあ8316.Tとか5801.TとかはRSI的に売られすぎなので買うにはよさそうに見えますね。まあこの辺は当たるも八卦当たらぬも八卦。2024.06.24の終値で株を購入したとすると、次の日は上昇してましたね。まあ一日の成績というよりはもうちょっと中期的な実力を見る必要があると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/7accf875-703c-997d-9ddd-321719799f83.png)

# 感想
四季報とかで何を見るかが決まっていれば、機械的によさそうな銘柄抽出ができるのではないかと思いますが、そのへんって結構あいまいなのかもしれません。時系列のグラフも「右肩上がりでなんかよさそう」というのを、線形近似の相関度とかで表現するといいのかも。