株価予測モデルの作成と投資シミュレーション

### はじめに
私はこれまで株式取引に興味を持ち、資産運用の一環として株価予測の分析を進めてきました。本プロジェクトでは、株価の予測モデルを作成し、ランダムフォレストを使用して株価の上昇・下落を予測することに挑戦します。また、この予測を活用した取引シミュレーションを行い、実際の投資における収益性を評価することを目指しています。

### 解決したい社会課題
株式市場では価格変動が大きく、投資家にとってリスク管理は非常に重要です。特に、短期的な予測が困難であり、個人投資家は適切な判断ができずに損失を被るケースも多々あります。このプロジェクトでは、株価予測モデルを用いて、短期的な株価変動を予測し、投資のリスクを軽減する手法を提案します。また、取引シミュレーションを通じて、予測モデルが投資判断にどのように寄与するかを検証します。

### 分析するデータ
yfinanceのライブラリを利用して、株価データなど分析に利用するデータを取得します。

### 実行環境
パソコン：MacBook Pro
開発環境：Spyder
言語：Python
ライブラリ：Pandas、Matplotlib、yfinance

### 分析の流れ
1. ライブラリのインポート
1. 株式および主要指数データの取得
1. 各指数の終値と変化率を追加
1. RSIおよびボリンジャーバンドの計算
1. 株価変化率の計算
1. 5日後の株価と将来フラグの計算
1. 欠損値の処理とデータ分割
1. 説明変数と目的変数の設定
1. ランダムフォレストの学習と予測
1. 取引シミュレーションと収益計算


### 分析の過程
```
# ライブラリのインポート
import yfinance as yf
import mplfinance as mpf
import warnings
import datetime as dt
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
warnings.simplefilter('ignore')


# 複数の株コードリストを作成
stock_codes = ["6758.T", "7203.T", "4004.T", "4203.T", "4215.T", "4005.T", "6753.T", "6752.T", "7011.T", "4755.T", "4063.T"]  # 必要に応じて追加可能
start = '2010-01-01'
end = dt.date.today()

# 6758:Sony 7203:Toyota 4004:Resonac 4203:住友ベークライト
# 4215:タキロン 4005:住友化学 6753:SHARP 6752:panasonic 7011:三菱重
# 4755:楽天 9434:SB 4063:信越化学 6752: 7011:

# 結果を保存するデータフレームを初期化
results = pd.DataFrame(columns=['Stock_Code', 'Accuracy', 'Final_Profit'])


# データフレームの作成
for stock_code in stock_codes:
    # Yahoo Financeから株式データを取得
    df = yf.download(stock_code, start=start, end=end)

    # 主要な指数のコードを設定
    dow_data = yf.download("^DJI", start=start, end=end)    # ダウ平均
    nasdaq_data = yf.download("^IXIC", start=start, end=end)  # NASDAQ
    sp500_data = yf.download("^GSPC", start=start, end=end)  # S&P 500
    nikkei_data = yf.download("^N225", start=start, end=end) # 日経平均
    usd_jpy_data = yf.download("JPY=X", start=start, end=end)  # USD/JPY 為替レート
    
    # 各指数の終値をメインのデータフレームに追加
    df['Dow_Close'] = dow_data['Close']
    df['Nasdaq_Close'] = nasdaq_data['Close']
    df['SP500_Close'] = sp500_data['Close']
    df['Nikkei_Close'] = nikkei_data['Close']
    df['USD_JPY_Close'] = usd_jpy_data['Close']

    # 各指数の変化率を計算してデータフレームに追加
    df['Nasdaq_Change'] = df['Nasdaq_Close'].pct_change() * 100  # NASDAQの変化率
    df['SP500_Change'] = df['SP500_Close'].pct_change() * 100  # S&P500の変化率
    df['Dow_Change'] = df['Dow_Close'].pct_change() * 100  # ダウ平均の変化率
    df['Nikkei_Change'] = df['Nikkei_Close'].pct_change() * 100  # 日経平均の変化率
    df['USD_JPY_Change'] = df['USD_JPY_Close'].pct_change() * 100  # ドル円の変化率

    # RSIの計算
    def calculate_RSI(data, window):
        delta = data['Close'].diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
        RS = gain / loss
        RSI = 100 - (100 / (1 + RS))
        return RSI
    
    df['RSI'] = calculate_RSI(df, 14)


    # ボリンジャーバンドの計算
    def calculate_bollinger_bands(data, window=20):
        rolling_mean = data['Close'].rolling(window=window).mean()  # 移動平均
        rolling_std = data['Close'].rolling(window=window).std()  # 標準偏差
        upper_band = rolling_mean + (rolling_std * 2)  # 上限バンド
        lower_band = rolling_mean - (rolling_std * 2)  # 下限バンド
        return rolling_mean, upper_band, lower_band
    
    # 20日間のボリンジャーバンドを計算
    df['BB_Mid'], df['BB_Upper'], df['BB_Lower'] = calculate_bollinger_bands(df)
    
    # %Bの計算を追加 #BBの中のどこにいるか
    def calculate_bollinger_percent_b(data, upper_band, lower_band):
        percent_b = (data['Close'] - lower_band) / (upper_band - lower_band)
        return percent_b
    # \text{%B} = \frac{(Close - BB\_Lower)}{(BB\_Upper - BB\_Lower)}
        
    # %Bの計算を追加
    df['BB_%B'] = calculate_bollinger_percent_b(df, df['BB_Upper'], df['BB_Lower'])

    # 株価の変化率を追加
    df['Close_Change'] = df['Close'].pct_change() * 100
    # 5日後の終値を追加する列を作成
    df['Close_5d_Future'] = df['Close'].shift(-5)
    df['Future_Change_Flag'] = df.apply(lambda row: 1 if row['Close_5d_Future'] < row['Close'] else 0, axis=1)

    df.dropna(inplace=True)

    # 学習データとテストデータに分割
    train_df = df.iloc[:-365]
    test_df = df.iloc[-365:]
    
    # 特徴量と目的変数の設定
    X_train = train_df[['Close_Change', 'Dow_Change', 'Nasdaq_Change', 'SP500_Change', 'RSI', 'BB_%B', 'Nikkei_Change', 'USD_JPY_Change']]
    y_train = train_df.loc[X_train.index, 'Future_Change_Flag']  # X_trainと同じインデックスを持つ行を使用
    
    X_test = test_df[['Close_Change', 'Dow_Change', 'Nasdaq_Change', 'SP500_Change', 'RSI', 'BB_%B', 'Nikkei_Change', 'USD_JPY_Change']]
    y_test = test_df.loc[X_test.index, 'Future_Change_Flag']  # X_testと同じインデックスを持つ行を使用
    
    # ランダムフォレストモデルのインスタンス化
    rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
    # ランダムフォレストモデルの学習
    rf_model.fit(X_train, y_train)

    
    # 予測と精度の計算
    y_pred = rf_model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)

    # 取引シミュレーションの実行
    y_pred_prob = rf_model.predict_proba(X_test)
    shares_owned = 0
    purchase_price_total = 0
    cash = 0

    for i in range(len(test_df)):
        prob_up = y_pred_prob[i][1]
        if prob_up >= 0.7:
            shares_owned += 100
            purchase_price_total += test_df.iloc[i]['Close'] * 100
        elif prob_up <= 0.2 and shares_owned > 0:
            cash += shares_owned * test_df.iloc[i]['Close']
            shares_owned = 0
            purchase_price_total = 0

    final_stock_value = shares_owned * test_df.iloc[-1]['Close']
    total_value = cash + final_stock_value
    profit = total_value - purchase_price_total

    # 結果をデータフレームに追加 (appendではなくconcatを使用)
    new_row = pd.DataFrame({'Stock_Code': [stock_code], 'Accuracy': [accuracy], 'Final_Profit': [profit]})
    results = pd.concat([results, new_row], ignore_index=True)

# 結果を表示
print(results)

import matplotlib.pyplot as plt

# 各株式の終値をプロット
plt.figure(figsize=(12, 8))

for stock_code in stock_codes:
    # Yahoo Financeから株式データを取得
    df = yf.download(stock_code, start=start, end=end)

    # 日付を横軸、終値を縦軸とする折れ線グラフを作成
    plt.plot(df.index, df['Close'], label=stock_code)

plt.xlabel('Date')
plt.ylabel('Closing Price (JPY)')
plt.title('Stock Price Movements Over Time')
plt.legend()
plt.grid(True)
plt.show()
```
#### 可視化したデータ
0              Accuracy  Final_Profit
1      6758.T  0.550685  5.763400e+06
2      7203.T  0.517808  4.443750e+06
3      4004.T  0.523288  2.209900e+06
4      4203.T  0.567123  2.057500e+06
5      4215.T  0.498630  6.319000e+05
6      4005.T  0.506849  1.777000e+05
7      6753.T  0.547945  1.821790e+06
8      6752.T  0.528767 -2.673000e+05
9      7011.T  0.523288  5.564100e+05
10     4755.T  0.542466  8.502600e+05
11     4063.T  0.495890  2.378000e+06

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864247/695fdeb1-9b22-2e85-f60d-7bdc47e6088b.png)

### 分析したデータから得られた情報
大部分の株式で利益が得られたが、一部で損失が確認されました。特に6758や4203などは高い利益率を示していますが、6752はマイナスの結果となりました。ランダムフォレストによる予測は概ね良好な精度を示しましたが、全ての銘柄に対して常に利益が得られるわけではないことが示唆されます。

### まとめ
今回の分析を通じて、ランダムフォレストを用いた株価予測モデルがある程度の精度で短期的な価格変動を予測できることが確認できました。また、取引シミュレーションの結果から、予測に基づく売買が一定の収益をもたらす可能性があることが示されました。しかし、市場の変動は予測が難しく、常に正確な予測ができるわけではないため、引き続きモデルの改善や他の手法との比較を進めることが重要です。