構築記

AI自動売買の作り方#3: 過去データで戦略を検証する

#2 GitHub編では、コードをGitHubに保管するところまでをやりました。今回はいよいよ売買戦略の中身に入ります。ただし、いきなりお金は使いません。まず「過去のデータで試したらどうなっていたか」を確認する、バックテストという工程からです。

  • #1 準備編: Claude Code導入と取引ルールブックづくり
  • #2 GitHub編: コードの保管庫「GitHub」とは何か
  • #3 バックテスト前編(この記事): 戦略の中身と、過去データで検証する方法
  • #4 バックテスト後編: 損切り・ポジションサイズという安全装置と、結果の読み方
  • #5 朝レポート編: GitHub Actionsで、毎朝6時に売買シグナルを自動チェックする
  • #6 ダッシュボード編: Cloudflareで、スマホから見られる管理画面を作る
  • #7 証券口座接続編: MCPで証券口座をAIにつなぎ、提案→承認→発注で運用する

この記事のゴール: 読み終わる頃には、①バックテストが何のためのものかわかり、②実際に作った2本の戦略の中身が理解でき、③自分のPCで同じ検証を再現できる状態になります。所要時間は30分〜1時間です。

バックテストとは何か

一言でいうと、「この売買ルールを過去に使っていたら、資産はどう増減していたか」を、実際の株価データで再現する作業です。

たとえば「株価が上向きに転換したら買う」というルールがあるとします。これを頭の中だけで「良さそうだ」と判断するのではなく、2022年〜2025年のAAPL(アップル)の実際の値動きに当てはめて、本当に儲かっていたか・どのくらい負けが込む局面があったかを数字で確認する。それがバックテストです。

なぜお金を入れる前にやるか

理由はシンプルで、「なんとなく良さそう」は、たいてい当たらないからです。#1で書いた「取引ルールブック」は暴走を防ぐ檻ですが、そもそも戦略そのものが弱ければ、ルールを守っていても静かに資産が減っていきます。バックテストは、その戦略の弱さを実弾を使わずに発見できる、唯一といっていい手段です。

手順1: 戦略のアイデアを2本、Claudeに相談する

私は最初からアイデアを持っていたわけではありません。Claudeに、こう相談しました。

株の売買戦略を何本か作って、バックテストできるようにしたい。
初心者でも理屈が分かるシンプルなものから試したい。

返ってきた提案の中から、まず理屈が直感的に分かる2本を選びました。

  1. 移動平均クロス(SMAクロス): 短期の平均株価が長期の平均株価を上に抜けたら「上昇トレンドの始まり」とみなして買う、下に抜けたら手仕舞う
  2. RSI逆張り: 「売られすぎ」を示す指標(RSI)が一定より下がったら「そろそろ反発するはず」とみなして買う、「買われすぎ」まで戻ったら手仕舞う

この2本を選んだ理由は、考え方が正反対だからです。SMAクロスは「流れに乗る(順張り)」、RSI逆張りは「行き過ぎたら戻ることに賭ける(逆張り)」。どちらが自分のスタイルに合うかを、感覚ではなく数字で比較したいと思いました。

手順2: 過去の株価データを用意する

戦略を試すには、まず材料になる株価データが必要です。Claudeが作ったのがdata_loader.pyという小さな部品で、役割はこれだけです。

def load_prices(ticker: str, start: str, end: str) -> pd.DataFrame:
    # すでにダウンロード済みなら、保存済みのCSVをそのまま使う
    # 無ければ yfinance(株価データを無料で取ってこれるライブラリ)から取得して保存

ポイントはキャッシュです。 同じ銘柄・同じ期間のデータを何度もダウンロードし直すと時間がかかりますし、無料サービスに毎回負荷をかけることになります。一度取ってきたデータはdata/フォルダにCSVとして保存しておき、2回目以降はそれを読むだけにする。地味ですが、この後何十回と戦略を試すことになるので、最初にここを整えておいたのは効きました。

手順3: 戦略をコードにする

「戦略のコード化」と聞くと難しそうですが、実際にできあがったコードはかなり短いものでした。SMAクロス戦略の心臓部だけ抜粋します。

def next(self):
    if crossover(self.fast_line, self.slow_line):
        # 短期線が長期線を上抜けた → 買う
        self.buy(sl=sl, size=size)
    elif crossover(self.slow_line, self.fast_line):
        # 長期線が短期線を上抜けた(=下降転換) → 手仕舞う
        self.position.close()

日本語のコメントを追えば、やっていることは「上抜けたら買う、下抜けたら売る」という単純な条件分岐だとわかると思います。裏側の平均株価の計算や注文処理はbacktestingという既製のライブラリに任せているので、自分で書く部分は「いつ買っていつ売るか」という判断ロジックだけで済みました。

RSI逆張り戦略も考え方は同じで、「RSIが30を下回ったら買う、70を上回ったら売る」という条件を書くだけです。

手順4: 実行して結果を見る

戦略ができたら、ターミナルで1行打つだけで検証が走ります。

python run_backtest.py AAPL --strategy sma --start 2022-01-01 --end 2025-12-31 --cash 10000

AAPL(アップル)を対象に、2022年〜2025年の4年間、10,000ドルの元手でSMAクロス戦略を動かしたら、というシミュレーションです。実際に出てきた結果がこちらです(数値は簡略化せず、実際の出力をそのまま載せています)。

指標SMAクロスRSI逆張り
最終資産(元手1万ドル)10,942ドル10,676ドル
リターン+9.4%+6.8%
同じ期間ずっと持ちっぱなしだった場合(Buy&Hold)+64.9%+72.5%
取引回数18回19回
勝率44.4%57.9%
最大ドローダウン(一番資産が目減りした幅)-7.1%-6.6%
プロフィットファクター1.871.48

正直に書きます。どちらの戦略も、AAPLを最初から最後まで持ちっぱなしにしていた場合(Buy&Hold)の成績に、大きく負けました。 2022年〜2025年のAAPLは右肩上がりが強かった期間なので、細かく売り買いを繰り返す戦略は、その上昇を全部は取りきれなかった、ということです。

がっかりする結果に見えるかもしれませんが、これはバックテストの正しい使い方そのものです。「この戦略で本当にやって大丈夫か」を、お金を失う前に確認できた。これが目的です。ちなみにプロフィットファクター(儲けた金額÷損した金額)は両方とも1を超えているので、「勝ってはいるが、ただ持っているより効率が悪い」という状態でした。この後どちらを採用するか、どう改善するかは#4で書きます。

つまずきポイント

① yfinanceが返すデータの形が複雑だった。 株価データを取得するライブラリyfinanceは、銘柄によって列の構造が微妙に変わる(複数階層になっている場合がある)ことがあり、そのままでは扱えずエラーになりました。Claudeが原因を切り分けて、データの形をそろえる1行(isinstance(df.columns, pd.MultiIndex)の判定)を足して解決しています。私はエラーメッセージを見せて「なんかおかしい」と伝えただけでした。

② 取引が1回も発生しないことがある。 銘柄や期間によっては、条件(平均線のクロスやRSIのしきい値)に一度も当てはまらず、「取引回数0回」でバックテストが終わることがあります。これ自体はバグではなく「その期間・その銘柄には向かない戦略だった」というだけの結果ですが、最初は「壊れた?」と焦りました。run_backtest.pyには取引0回のときに警告を出す処理を後から足しています。

ここまでのチェックリスト

  • data_loader.pyで株価データを取得・キャッシュできる
  • SMAクロス・RSI逆張りの2戦略がコードになっている
  • python run_backtest.py <銘柄> --strategy sma でHTMLレポートが出力される
  • 出てきた結果(リターン・Buy&Hold比較・勝率・ドローダウン)の意味がだいたい説明できる

ここまでで、戦略を思いつきではなく数字で検証する土台ができました。次回の#4 バックテスト後編では、この記事のコードに実はもう仕込まれていた「損切り-8%」「1トレードの損失を資金の2%に抑えるポジションサイズ計算」という安全装置について、なぜ最初からこの2つを入れたのかを書きます。

※投資は自己責任でお願いします。このシリーズは私の作業記録で、特定の手法や銘柄をおすすめするものではありません(免責事項)。