今回のゴール!
下記動画 のように、特定の銘柄 (シンボル) を送るだけで、明日の株価を予測してもらうLINE Bot を一緒に作っていきましょう!
この記事を最後まで読めば同じLINE Botを間違いなく実装できます!
一緒に頑張っていきましょう!
自己紹介
こんにちは!北海道の小売業でお惣菜担当として働いている社会人2年目のヒロです。
入社してから毎日忙しい日々を送っていますが、誰よりも早く出世したいという強い思いを持って、日々の業務に取り組んでいます!
最近は「デジタルツールを使って、今の自分にできることを広げたい!」という気持ちが強く、色々な技術を勉強しています。
そんな私が今特に興味を持っているのが株式投資です。
アメリカ株を中心に投資をしていますが、トランプ政権以降の値動きが激しすぎて、毎日肝を冷やしています。
「明日は上がるのか?下がるのか?」とチャートを眺めながら悩む日々……。
そんな中、「この時間をもっと有効活用できないか?」
という課題意識から、AIを活用して株価予測をLINEで手軽に確認できるツールを作ろうと思いました。
チャート分析って難しくね…?
毎日、株価チャートを眺めては「上がるかな?下がるかな?」と悩み続けていましたが、
正直、テクニカル分析の知識もあまりない自分がチャートを見ても、意味があるのか分からないのが本音です。
それでもつい気になってしまい、チャートを見る時間がどんどん長くなってしまう……。
特に今は昇格試験が迫っており、チャートを見ている場合ではないのに、気がつけば株価を確認してしまう日々が続いていました。
国もNISAを推進して投資のリテラシー向上を促していますが、投資初心者にとってテクニカル分析やチャート分析はハードルが高いです。
「これって、自分だけの悩みじゃないのでは?」
「難しいことは分からないけど、明日上がるのか下がるのかくらいは知りたい!」
という人は、私以外にもたくさんいるはずだと感じました。
そこで、LINEで銘柄のシンボルを送るだけで、明日の株価予測が簡単に確認できるBotを作ろう!
と思い立ったのが今回の企画のきっかけです。
今回作るBotについて
初めに説明した通り、今回作成するのは、LINEで簡単に株価予測が見られるBotです。
使い方はとてもシンプルで、LINEのトーク画面で銘柄のシンボル(例: AAPL, GOOG など)を送信するだけ。
するとBotが以下の情報を返信してくれます。
- 現在の終値
- 明日の予測終値(AIが予測した翌日の価格)
- 明日上がるか下がるか(方向性)
- 予測誤差(RMSE)
これらの予測は、決定木勾配ブースティング(Gradient Boosting) という手法を使って計算しています。
実はこの手法、大学時代の研究で使っていたので「これ、今回の予測モデルに使えるんじゃない?」と思って選びました。
決定木勾配ブースティングは、1本の決定木だけではなく、複数の弱い決定木を組み合わせて精度の高いモデルを作る手法です。
簡単に言えば、「1人の知恵じゃなく、みんなの知恵を合わせて予測する」 みたいなイメージです。
イラストで表すとこんな感じです
詳細についてはこちら
決定木勾配ブースティングはかなり奥が深く、専門的な内容なので詳細については割愛します。
興味がある方はこちらの記事がわかりやすいです。
使った技術
このBotを実現するために、以下の技術を組み合わせました。
- LINE Messaging API
- yfinance(株価データ取得)
- LightGBM(株価予測モデル)
- FastAPI(API構築)
- Render(APIのホスティング)
- Make(LINE Bot連携)
実際に作った流れ
-
「明日の株価予測APIがないかな?」と探し回る
まずは「明日の株価を予測してくれるAPIなんて都合のいいものがあるはず!」と思って探しましたが、そんなものは存在せず…。
「ないなら作るしかない!」 と腹をくくりました。
-
大学時代に触ったPythonを思い出しつつ、コードを書き始める
予測モデルの作成には、yfinance での株価データ取得、LightGBM での予測、そして FastAPI でAPI化する方法を選びました。
API化によって、どこからでも株価予測ができるようにするため、データ取得からモデル構築、APIへの組み込みまで一気通貫で実装しました。実際に作成したコードは以下の通りです。(コメント付きで解説しています)
コピペOKです!自由に使ってください!
from fastapi import FastAPI # FastAPIをインポートしてAPIアプリを作成する
import yfinance as yf # yfinanceを使って株価データを取得する
import pandas as pd # データ処理用のpandasをインポート
import lightgbm as lgb # LightGBMで株価予測モデルを作成
from sklearn.metrics import mean_squared_error # モデル評価指標(RMSE)を計算するために使用
import numpy as np # 数値計算用のnumpyをインポート
from sklearn.preprocessing import StandardScaler # データの標準化に使用
app = FastAPI() # FastAPIアプリのインスタンスを作成
@app.get("/predict/{symbol}") # GETリクエストで/predict/シンボル のエンドポイントを作成
def predict_stock(symbol: str): # リクエスト時にシンボルを受け取る関数
try:
df = yf.download(symbol, period="2y") # 2年間分の株価データをyfinanceで取得
if df.empty or len(df) < 60: # データが取得できない場合や少なすぎる場合はエラーを返す
return {"error": f"データが少なすぎるか取得できませんでした: {symbol}"}
if isinstance(df.columns, pd.MultiIndex): # 列名がMultiIndexの場合は1列にまとめる
df.columns = ['_'.join(col).strip() for col in df.columns.values]
close_cols = [col for col in df.columns if 'close' in col.lower()] # 'close'を含む列名を探す
volume_cols = [col for col in df.columns if 'volume' in col.lower()] # 'volume'を含む列名を探す
if not close_cols: # Close列が見つからない場合はエラー
return {"error": f"Close列が見つかりません: {symbol}"}
if not volume_cols: # Volume列が見つからない場合はエラー
return {"error": f"Volume列が見つかりません: {symbol}"}
close_col = close_cols[0] # Close列名を決定
volume_col = volume_cols[0] # Volume列名を決定
df = df.reset_index()[['Date', close_col, volume_col]] # 日付、Close、Volume列だけに絞って取得
df = df.rename(columns={close_col: 'close', volume_col: 'volume'}) # 列名を統一
df['log_volume'] = np.log1p(df['volume']) # 出来高を対数変換(0回避のためlog1p)
df['price_volume'] = df['close'] * df['volume'] # 株価×出来高の値を計算
df['log_price_volume'] = np.log1p(df['price_volume']) # 株価×出来高の対数変換
df['close_lag1'] = df['close'].shift(1) # 1日前の終値
df['close_lag2'] = df['close'].shift(2) # 2日前の終値
df['ma5'] = df['close'].rolling(5).mean() # 5日移動平均
df['ma10'] = df['close'].rolling(10).mean() # 10日移動平均
df['return'] = df['close'].pct_change() # 前日比(リターン)
df['volume_lag1'] = df['volume'].shift(1) # 1日前の出来高
df['volume_ma5'] = df['volume'].rolling(5).mean() # 5日移動平均の出来高
df['volatility'] = df['return'].rolling(5).std() # 5日間のリターンの標準偏差(ボラティリティ)
df['volatility_10'] = df['return'].rolling(10).std() # 10日間のリターンの標準偏差
df = df.dropna() # 欠損値が含まれる行を削除
scaler = StandardScaler() # 標準化用のscalerを作成
scaled_features = scaler.fit_transform(df[['return', 'volatility', 'volatility_10']]) # 特定の列を標準化
df[['return_scaled', 'volatility_scaled', 'volatility_10_scaled']] = scaled_features # 標準化後の値を新しい列に追加
feature_cols = [ # モデルに使う特徴量のリスト
'close_lag1', 'close_lag2', 'ma5', 'ma10', 'return_scaled',
'volume_lag1', 'volume_ma5', 'volatility_scaled', 'volatility_10_scaled',
'log_volume', 'log_price_volume'
]
X = df[feature_cols].copy() # 特徴量データをXとして作成
X.columns = [f"feature_{i}" for i in range(X.shape[1])] # 列名をシンプルな名前にリネーム
y = df['close'] # 目的変数(予測対象)は終値
split_idx = int(len(df) * 0.8) # データの8割を学習用、2割を検証用に分ける
X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:] # 学習データと検証データに分割
y_train, y_val = y.iloc[:split_idx], y.iloc[split_idx:] # ラベルも分割
if len(X_train) < 30 or len(X_val) < 30: # データ量が少なすぎる場合はエラー
return {"error": f"学習データが少なすぎます: {symbol}"}
model = lgb.LGBMRegressor( # LightGBM回帰モデルを作成
n_estimators=5000, # 決定木の最大数
learning_rate=0.01, # 学習率
random_state=42 # 乱数シード
)
model.fit( # モデルを学習
X_train, y_train,
eval_set=[(X_val, y_val)], # 検証データで評価
eval_metric='rmse', # 評価指標はRMSE
callbacks=[lgb.early_stopping(stopping_rounds=50, verbose=False)] # 50回改善しなければ早期終了
)
last_row = df.iloc[-1] # 最新のデータ(予測に使う)
X_pred = pd.DataFrame({X.columns[i]: [last_row[feature_cols[i]]] for i in range(len(feature_cols))}) # 最新データを特徴量形式に変換
pred_close = model.predict(X_pred)[0] # 翌日の株価を予測
val_pred = model.predict(X_val) # 検証データでの予測
rmse = np.sqrt(mean_squared_error(y_val, val_pred)) # RMSEを計算
trend = "↑" if pred_close > last_row['close'] else "↓" # 予測値が終値より高いか低いかで方向を決定
return { # 結果を返す
"symbol": symbol,
"現在の終値": round(last_row['close'], 2),
"予測終値": round(pred_close, 2),
"方向": trend,
"RMSE(誤差)": round(rmse, 2),
"学習回数": int(model.best_iteration_) if model.best_iteration_ is not None else model.n_estimators
}
except Exception as e: # エラー発生時はエラー内容を返す
return {"error": str(e)}
3. FastAPIで作ったAPIをRenderでデプロイし、常時稼働できるようにする
作成したFastAPIアプリは、無料で使えるRender というクラウドホスティングサービスにデプロイしました。
Renderを使うことで、作成したAPIを24時間稼働させ、外部からのリクエストに常に応答できる状態にしました。
Renderの詳細についてはこちら
こちらの動画がわかりやすくFastAPIで作ったAPIをRenderでデプロイするまで解説してくれています。
英語の動画ですが、プログラミングやITツールを使用する方は英語での情報収集は避けては通れないので翻訳等を駆使しながらチャレンジしてみてください
4. MakeでLINE Botと連携
MakeでLINE Botと連携させる部分では、HTTPモジュールを使ってAPIにリクエストを送るように設定しました。
LINEで送信されたメッセージ(シンボル)を受け取り、APIで株価予測を取得し、その結果をLINEに返信するという流れです。
実際のMakeシナリオはこんな感じです。
各モジュールの詳細についてはこちら
LINE (Watch Events)モジュール
THHP (Make a request)モジュール
LINE (Send a Reply Message)モジュール
画像のように設定してください。
LINE (Watch Events)モジュールについてはMessaging API設定が必要になります。わからなければこちらの記事がわかりやすいです。
LINEから送られたシンボルをAPIに渡し、取得した予測結果をLINEで受け取れるようにしています。
このシンプルなシナリオのおかげで、デジタルが苦手な叔父でも簡単に使えるBotが完成しました。
5. 実際にテストを繰り返し、デプロイ
テストで何度もデジタルが苦手な叔父に実際に使ってもらい、「これなら使える!」という状態までブラッシュアップを重ねました。
6. 完成!
Renderのサーバーを起動させて、MakeでもRun onceボタンを押してLINEでAPPLと送ってみましょう!
冒頭で紹介したように、明日の予測株価が送られてきたら成功です!
お疲れ様でした!
苦労したこと
初心者向けに記事を書いてますが、実は私もデジタルについては初心者です…
このBotの作成過程で苦労したことを紹介します
-
Pythonがそもそもよくわからなかった
作りたいものは頭の中にあるのに、それをどうやってコードに落とし込めばいいかが全く分からなかったです…。
何度もChatGPTに「こういうことがしたいんだけど、どう書けばいい?」と質問したり、インターネット検索で似た例を探したり、大学時代に使っていた参考書を引っ張り出してきたりして、少しずつ形にしていきました。
-
yfinanceでデータ取得エラー
一部の銘柄で「終値(Close)」が取得できず、エラーが頻発…。
終値がない場合は調整後終値(Adj Close)を扱う ことで対応し、無事に解決できました! -
かっこの閉じ忘れやインデントエラー
Pythonコードでのシンプルなミスで何度もつまずきましたが、その都度ChatGPTに相談しながらデバッグを繰り返し、問題を解決していきました。
「これ便利すぎるだろ」と思いながら、気づいたらエラーも解消していて本当に助かりました!
-
No further splits with positive gain 警告に悩まされた
LightGBMで学習を進めると、No further splits with positive gain
という警告が大量に出ることに悩まされました。
これは「これ以上データを分けても意味がないので、木を成長させません」という意味の警告で、分割条件が見つからないときに出ます。
特に株価データは連続的でノイズが多いため、特徴量の分散が小さいと分割できないことが多いです。
株価データはいろんな要因が複雑に絡む事や見えないデータが多いが多いことでノイズが多いと言われています!
そこで、特徴量エンジニアリング(データを加工して新しい特徴を作ること) を行い、分割しやすいデータにしました!
対策として行った特徴量エンジニアリング
問題 | 特徴量エンジニアリング |
---|---|
値が大きすぎて分けられない | 対数変換で値を小さくした |
値の範囲がバラバラ | 標準化(平均0、標準偏差1) で値のスケールを統一した |
分けられる情報が少ない | 移動平均、リターン、ボラティリティ、ラグ特徴量 を作った |
これだけ対策してもNo further splits with positive gain
の警告は消えませんでした…
しかし!特徴量を増やしたことでモデルの精度は上がったので、「まあこのままでいいか!」 と判断して、そのまま使っています!
天才にはなれなかった…
実は失敗したけどチャレンジしていたことがあります。
作成した株価予測データを使って、OpenAIのAPIを利用し、アナリスト風に「今は買い時です!」とか「最近のニュースではこういう動きがありました!」といった解説をしてくれるという機能も導入しようと思っていました。
しかし、OpenAIのAPIは有料プランが必要で、個人で試すにはハードルが高すぎると感じたため、今回は断念しました。
🟢追記!:Geminiを使うことで無料で実装できました!詳細は折り畳みご確認ください
OpenAIのAPIは有料ですが、GeminiのAPIはなんと完全無料で使用できます!
導入方法下記に記します
1.GeminiのAPIキーをこちらから取得
2.MakeでGoogle Gemini AIモジュールをHTTPモジュールとLINE(Send a Replay Message)の間に追加してAPIキーの入力後、下記のように設定(プロンプトは好みで変更してください)
3.完成!Run OnceしてAPPLと送ってみましょう!こちらの動画のようになれば成功です!
結構簡単でしたね!
また、APIがスリープしないようにするための仕組み作りにもチャレンジしました。
Renderの無料プランでは、15分間アクセスがないとサーバーがスリープ状態に入り、再起動に約5分かかるという仕様があります。
そのため、サーバーが寝ている状態でBotを起動すると、APIのアクセスがタイムアウトしてエラーになってしまう問題が発生しました。
これを解決するために、Makeで5分に1回APIにアクセスするシナリオを3個作り、それを15分に1回起動するように設定して、無限ループでAPIを叩き続ける仕組みを作成しました。
作ったシナリオはこちら
このアイデアが思いついたときは「これ天才じゃね!?」と思い、記事に書くのが楽しみだったんですが……
実はこれも失敗しました。
理由は、Makeの無料プランには月間のシナリオ実行回数に上限があり、1日と少しで上限に達してしまったからです。
「なんでこれに気づかなかったんだろう?」と思ったのですが、Makeのダッシュボードが全部英語で、無料プランの制限内容や実行状況が分かりにくかったことが原因でした。
上限に達した時の画面はこちら
これからは、英語の勉強もちゃんとしようと思います…
今後の可能性
今回作ったシステムは、株価をAIで予測し、LINEで毎日通知するところまででしたが、ここからさらに発展させていきたいと考えています。
まず、予測を作ったら終わりではなく、実際に当たっているのかを振り返る仕組みを作りたいです。過去の予測値と実際の株価を並べてグラフで確認し、予測の精度を可視化することで、AIの予測がどれくらい信頼できるのかを確かめたいと思っています。そのために、データベースとしてSQLiteやPostgreSQLを使い、可視化ツールとしてはStreamlitやGradioを活用しようと考えています。グラフの描画にはMatplotlibやPlotlyを使い、データ処理はPandasやNumpyで行う予定です。正解率や誤差(RMSE)、上昇・下降トレンドの的中率なども確認できるようにして、毎日の予測がどれくらい当たっているかを一目で把握できるダッシュボードにしたいと思っています!
さらに、株価チャートを自動で画像生成し、LINEに送信できるようにする機能も追加したいです。今はLINEで数字のメッセージを受け取るだけなので、そこにチャート画像を加えることで視覚的に株価の動きを確認できるようになります。これにはMatplotlibやPlotlyでチャートを作り、Pillowで画像ファイルを保存し、LINEで画像を送信する流れを考えています。毎朝「今日の株価予測」の通知と一緒に、直近の株価チャート画像がLINEで届くようにできれば、より直感的に株価の動きを把握できて便利だと思いませんか?
これらの機能を追加することで、ただの「株価予測Bot」から、
「予測の根拠を確認できて、視覚的にもわかりやすい投資サポートツール」
へと進化させていきたいと考えています。
さらに将来的には、決算情報やニュースをAIに分析させて予測に取り入れることで、より精度の高い予測ができるようになることを目指しています!
この記事を読んで株価予測Botを作った皆様も作って終わりではなく、何かオリジナルのアイディアを付け足して自分だけのツールに昇華させてください。
面白いものが作れたらぜひ教えてくださいね!
実際に制作・使用してわかったこと
今回は デジタル知識ほぼ0の私 が、APIを自作し明日の株価予測Botを作成 することができました。
様々な文献やエンジニア様方の記事や生成系AIを活用することで、知識がなくてもある程度はなんでも作れるものなんですね。
ちなみに今回作ったLINE Botツールを実際に使ってみると、精度は体感で 75% ってところです!(現時点では)
低くはないですが、信用して投資できるほどではないですね…。
まだまだ改善の余地はありますが、当初の課題である 毎日チャート見る時間を減らすという目標は達成 しました!
次は実用化に向けて頑張ってみます!
まだまだ努力!
まだまだデジタル人材とまでは言えませんが、社内のデジタルリーダー目指して頑張ります! 最後に参考文献や記事ご紹介します。ありがとうございました。