はじめに
機械学習の仕組みを使って、株の自動売買のシミュレーションをしてみます。モデルは線形回帰、指標は移動平均を用いることとします。
シミュレーション条件
株価のデータは既に取得済みとします。テストデータは直近(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 |
---|---|---|---|---|
INPEX | 8 | 10 | ¥263,296 | 0.5432835820895522 |
豊田通商 | 9 | 9 | ¥249,875 | 0.5348101265822784 |
シスメックス | 7 | 5 | ¥229,205 | 0.5577464788732395 |
日本ペHD | 14 | 6 | ¥217,925 | 0.5506607929515418 |
ルネサス | 9 | 15 | ¥209,214 | 0.5632911392405063 |
味の素 | 5 | 11 | ¥200,196 | 0.529113924050633 |
丸紅 | 32 | 13 | ¥196,421 | 0.5681818181818182 |
三菱重 | 30 | 7 | ¥189,229 | 0.64 |
三菱UFJ | 30 | 5 | ¥188,772 | 0.62 |
ヤクルト | 5 | 13 | ¥185,610 | 0.5367088607594936 |
日産自 | 17 | 15 | ¥184,283 | 0.6373626373626373 |
三井物 | 30 | 5 | ¥181,126 | 0.66 |
住友商 | 6 | 13 | ¥179,720 | 0.528 |
ユニチャーム | 24 | 6 | ¥178,880 | 0.6039603960396039 |
ソニーG | 9 | 9 | ¥177,385 | 0.5569620253164557 |
アドテスト | 9 | 15 | ¥173,110 | 0.5379746835443038 |
三菱商 | 31 | 13 | ¥172,233 | 0.5957446808510638 |
第一三共 | 19 | 11 | ¥171,349 | 0.4935897435897436 |
NTT | 5 | 9 | ¥168,686 | 0.5367088607594936 |
日本製鉄 | 32 | 5 | ¥168,634 | 0.6136363636363636 |
まとめ
今回は線形回帰と移動平均の組み合わせでシミュレーションしてみました。概ね利益を出すことができ、2年で元本の2倍を超える成果が出ているものもあります。翌日の価格の上昇、下降から正解率を算出しています。正解率と最終金額が同期していないことが見て取れます。つまり、値動きの幅や状況によって利益は左右されており今回は市場が良い状態だったため、利益が出ているのでは、とも考えられます。また、最大の成果を出すための移動平均や学習期間が企業によって異なっていることもわかりました。業種による値動きの様子や直近2年の経営状況の変化など、考慮すべきパラメータは多数存在していそうです。テスト期間の変更や最新日付でのテスト、組み合わせの変更など更なる分析を行うことで、ある程度期待ができる自動取引アルゴリズムを作ることができそうです。