機械学習(線形回帰)を使って株の売買シミュレーションを行う

  • URLをコピーしました!
目次

はじめに

機械学習の仕組みを使って、株の自動売買のシミュレーションをしてみます。モデルは線形回帰、指標は移動平均を用いることとします。

シミュレーション条件

株価のデータは既に取得済みとします。テストデータは直近(2023/05/01まで)の2年間とします。学習データの生成期間はそれ以前の5年から15年、移動平均の期間も5日から50日と変動させ、2年で10万円の資産が増えるかどうか、を記録することとします。

サンプルコード

以下にサンプルコードを記載します。

import sqlite3
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler

# SQLiteデータベースに接続
conn = sqlite3.connect('stock_data.db')

# tickerの一覧を取得
tickers = pd.read_sql_query("SELECT DISTINCT ticker FROM stock_price", conn)

# 結果を格納するための空のデータフレームを作成
results = pd.DataFrame()
counter =0

for ticker in tickers['ticker']:
    # tickerごとにデータを取得
    df = pd.read_sql_query(f"SELECT * FROM stock_price WHERE ticker='{ticker}'", conn)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date', inplace=True)

    # データフレームを営業日にリサンプリング(土日や祝日など、市場が開いていない日を除外)
    df = df.resample('B').mean(numeric_only=True)

    # テストデータの開始日と終了日を設定
    test_start_date = datetime(2021, 5, 1)
    test_end_date = datetime(2023, 5, 1)

    # 学習期間と移動平均期間の範囲を設定
    for training_period in range(5, 16):  # 5年から15年
        for ma_period in range(5, 51):  # 5日から50日
            try:
                # 学習データの開始日を設定
                train_start_date = test_start_date - timedelta(days=training_period*365)

                # 学習データとテストデータに分割
                train_df = df[train_start_date:test_start_date]
                test_df = df[test_start_date:test_end_date]

                # 移動平均を計算
                train_df = train_df.copy()
                train_df.loc[:, 'MA'] = train_df['close'].rolling(ma_period).mean()
                test_df = test_df.copy()
                test_df.loc[:, 'MA'] = test_df['close'].rolling(ma_period).mean()
                # 欠損値を削除
                train_df = train_df.dropna()
                test_df = test_df.dropna()

                # 線形回帰モデルを作成し、学習
                model = LinearRegression()
                model.fit(train_df[['MA']], train_df['close'])

                # テストデータで予測
                test_df = test_df.copy()
                test_df['prediction'] = model.predict(test_df[['MA']])

                # 正解・不正解のカウントを初期化
                correct = 0
                incorrect = 0

                # 売買シミュレーション
                initial_money = 100000  # 初期資金
                money = initial_money
                shares = 0
                for i in range(len(test_df) - 1):  # 最終日は次の日のデータがないため除外
                    # 予測価格が実際の価格より高い場合、購入
                    if test_df['prediction'].iloc[i] > test_df['close'].iloc[i]:
                      shares += money // test_df['close'].iloc[i]
                      money %= test_df['close'].iloc[i]
                      # 次の日の価格が上昇した場合、正解
                      if test_df['close'].iloc[i+1] > test_df['close'].iloc[i]:
                        correct += 1
                      else:
                        incorrect += 1
                    # 予測価格が実際の価格より低い場合、全て売却
                    elif test_df['prediction'].iloc[i] < test_df['close'].iloc[i]:
                      money += shares * test_df['close'].iloc[i]
                      shares = 0
                      # 次の日の価格が下降した場合、正解
                      if test_df['close'].iloc[i+1] < test_df['close'].iloc[i]:
                        correct += 1
                      else:
                        incorrect += 1

                # シミュレーション結果と正解率を計算
                result = money + shares * test_df['close'].iloc[-1]
                accuracy = correct / (correct + incorrect)

                # 結果をデータフレームに格納
                new_row = pd.DataFrame({'ticker': ticker,
                                        'training_period': training_period,
                                        'ma_period': ma_period,
                                        'final_money': result,
                                        'accuracy': accuracy}, index=[0])  # 正解率を追加
                results = pd.concat([results, new_row], ignore_index=True)

            except ValueError:
                # 移動平均期間が学習データの期間より長い場合、エラーが発生するため、スキップ
                continue

    counter+=1
    print(counter)


# 結果をデータベースに格納
results.to_sql('simulation_results', conn, if_exists='replace')

# データベース接続を閉じる
conn.close()

このコードは各パラメータの組み合わせに対するシミュレーション結果をデータベースに保存します。一つのシミュレーションごとに’Training period’, ‘Moving average period’, ‘Final balance’の列を持つ行を生成します。’Training period’と’Moving average period’はシミュレーションのパラメータを表し、’Final balance’はシミュレーション結果の最終的な資産の状態を表します。

実行結果

上記のシミュレーション結果です。上位20社を掲載します。

企業名移動平均対象
期間(日)
学習期間(年)最終金額accuracy
INPEX810¥263,2960.5432835820895522
豊田通商99¥249,8750.5348101265822784
シスメックス75¥229,2050.5577464788732395
日本ペHD146¥217,9250.5506607929515418
ルネサス915¥209,2140.5632911392405063
味の素511¥200,1960.529113924050633
丸紅3213¥196,4210.5681818181818182
三菱重307¥189,2290.64
三菱UFJ305¥188,7720.62
ヤクルト513¥185,6100.5367088607594936
日産自1715¥184,2830.6373626373626373
三井物305¥181,1260.66
住友商613¥179,7200.528
ユニチャーム246¥178,8800.6039603960396039
ソニーG99¥177,3850.5569620253164557
アドテスト915¥173,1100.5379746835443038
三菱商3113¥172,2330.5957446808510638
第一三共1911¥171,3490.4935897435897436
NTT59¥168,6860.5367088607594936
日本製鉄325¥168,6340.6136363636363636
シミュレーション結果

まとめ

今回は線形回帰と移動平均の組み合わせでシミュレーションしてみました。概ね利益を出すことができ、2年で元本の2倍を超える成果が出ているものもあります。翌日の価格の上昇、下降から正解率を算出しています。正解率と最終金額が同期していないことが見て取れます。つまり、値動きの幅や状況によって利益は左右されており今回は市場が良い状態だったため、利益が出ているのでは、とも考えられます。また、最大の成果を出すための移動平均や学習期間が企業によって異なっていることもわかりました。業種による値動きの様子や直近2年の経営状況の変化など、考慮すべきパラメータは多数存在していそうです。テスト期間の変更や最新日付でのテスト、組み合わせの変更など更なる分析を行うことで、ある程度期待ができる自動取引アルゴリズムを作ることができそうです。

バーマン
プロダクトマネージャー
ソフトウェア開発に長く従事しています。
・機械学習のサンプルコード作成
・生成型AIから調べたことのまとめ
・これまでのビジネスで経験したことのまとめ
を記事として作成させていただいています。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次