前回の記事では、RSIやMACD、出来高など複数のテクニカル指標を使って、株の売買判断をスコア化する仕組みを紹介しました。
→ 株の売買判断をPythonでスコア化してみた|RSI・MACD・出来高を点数化
この売買ルールが過去の相場でどの程度機能するのかを確認するため、開発ではPythonを使ったバックテストも進めています。
これまで東京エレクトロンを対象に約5年間の株価データで検証したところ、決済済みの取引は12回、勝率は58.3%でした。
ただ、結果を見て最初に思ったのは、
「12取引じゃサンプル数が少なすぎる」
ということでした。
1銘柄・12取引だけでは売買ルールが本当に有効なのか判断しにくいため、今回は売買条件を変更せず、検証対象を20銘柄へ拡大しました。
その結果、決済済みの取引は224件まで増え、勝率は75.0%。
一見するとかなり良さそうな結果ですが、その中に**最大損失-62.26%**という気になる数字がありました。
今回はこの取引の原因を調べるとともに、バックテスト自体にも問題がないか確認していきます。
売買条件を変えずに20銘柄へ拡大
今回は良い結果が出るように条件を調整するのではなく、これまで作ってきた売買条件をそのまま20銘柄に適用しました。
Codexを使いながら複数銘柄をまとめてバックテストできるように処理を追加し、約5年分の株価データを使って検証しています。
結果は以下のとおりです。
| 項目 | 結果 |
|---|---|
| 決済済み取引 | 224件 |
| 勝ち | 168件 |
| 負け | 56件 |
| 勝率 | 75.0% |
| 平均損益率 | +7.80% |
| 損益率中央値 | +6.89% |
| 利益取引平均損益率 | +12.96% |
| 損失取引平均損益率 | -7.67% |
| 最大利益率 | +84.13% |
| 最大損失率 | -62.26% |
| 平均保有期間 | 40.12営業日 |
| プラス銘柄 | 19銘柄 |
| マイナス銘柄 | 1銘柄 |
12取引から224取引まで増えたうえで、勝率75.0%。
想像していたよりも良い数字でした。
とはいえ、今後さらに銘柄や期間を広げたときにも75%前後を維持できるのかは分かりません。
それより先に確認したかったのが、最大損失の**-62.26%**です。
-62.26%になった取引を調べてみる
最大損失となった銘柄を調べると、レーザーテック(6920)でした。
2024年4月にBUYシグナルが発生してから、SELLとなったのは2025年5月。
256営業日も保有した結果、大幅な損失になっていました。
最初に疑ったのは、売買ロジックではなくデータです。
「そもそも本当に株価がこれだけ下がったのか?」
株式分割やyfinanceの価格調整によって、バックテスト上だけ-62%になっている可能性もあると思いました。
株式分割による価格のズレではなかった
そこでレーザーテックの株価データとStock Splitsを確認しました。
BUY前後からSELL後まで確認しても株式分割は0件。
さらに約5年分のデータ全体を確認しても、株式分割は記録されていませんでした。
使用しているyfinanceはauto_adjust=Trueで、BUY価格とSELL価格も同じ調整済み価格体系で計算されています。
つまり、今回の-62.26%は株式分割などによる単純なデータエラーではありませんでした。
現在のバックテスト条件・売買ルールで実際に発生した結果です。
実は一度+21%まで上昇していた
さらに値動きを追ってみると、BUY後にそのまま下落し続けたわけでもありませんでした。
保有中には最大で**+21.56%**の含み益になっています。
ところが、その時点ではSELL条件が成立しなかったため保有を継続。
その後株価は大きく下落し、一時は約**-72.30%**の含み損まで悪化しました。
最終的にSELLシグナルが出たところで決済され、-62.26%の損失になっています。
+20%以上まで利益が出ていたポジションを、なぜ-60%以上になるまで保有し続けたのか。
SELL条件を詳しく調べてみました。
256営業日のうちSELL条件が揃ったのは1日だけ
現在のSELL判定では、次の3つを使っています。
- RSI14 > 70
- MACD < MACD_SIGNAL
- 株価がボリンジャーバンド上限付近
このうち2条件が成立するとSELLです。
レーザーテックをBUYした翌営業日からSELLまで調べたところ、
| SELL条件成立数 | 営業日数 |
|---|---|
| 0条件 | 123日 |
| 1条件 | 132日 |
| 2条件 | 1日 |
| 3条件 | 0日 |
2つのSELL条件が揃ったのは、256営業日のうち最後の1日だけでした。
下落中にはMACDの弱気条件が何度も成立していました。
一方で、RSIが70を超えることやボリンジャーバンド上限付近まで上昇することは、下落局面ではなかなか起きません。
その結果、
「株価は下がっているのにSELL条件が揃わない」
という状態が長期間続いていました。
ここで現在のロジックには、
- 損切り
- 最大許容損失率
- 最大保有期間
- トレーリングストップ
といった仕組みがないことが大きな問題として見えてきました。
今度はバックテスト自体を疑ってみる
レーザーテックの損失を調べているうちに、売買ルールだけではなく、
「そもそもバックテストのやり方は正しいのか?」
という点も気になりました。
そこでコードを変更する前に、
- テクニカル指標に未来のデータが混ざっていないか
- 未来の行を参照していないか
- シグナルを出すタイミング
- 実際に売買したことにしているタイミング
- 株価の調整方法
- 株式分割
- 期末に残っている未決済ポジション
などを確認しました。
RSIやMACD、ボリンジャーバンドなどの計算に未来のデータが混入している問題は見つかりませんでした。
しかし、売買するタイミングに問題がありました。
終値を見てから、その日の終値で買っていた
これまでのバックテストでは、その日の終値を使ってテクニカル指標を計算し、BUYまたはSELLを判定していました。
ここまでは問題ありません。
問題は、そのあとです。
シグナルを確定したにもかかわらず、同じ日の終値で約定したことにしていました。
流れにすると、
当日の終値が確定
↓
テクニカル指標を計算
↓
BUY / SELLを判定
↓
同じ日の終値で約定
となります。
実際の取引では、終値を確認してから、その確定済みの終値に戻って売買することはできません。
いわゆるsame-close execution biasです。
そこで新しく、シグナルが出た翌営業日の始値で売買するrun_backtest_next_open()を実装しました。
翌営業日の始値で約定する方式へ変更
修正後は、
当日の終値でシグナルを判定
↓
翌営業日の始値で約定
という流れになります。
例えば4月17日の終値を使ってBUYシグナルが出た場合、実際のBUY価格として使うのは4月18日の始値です。
既存のsame-close方式も比較用として残し、20銘柄・224取引を同じ条件で再計算しました。

当日終値約定(same-close)と翌営業日始値約定(next-open)の比較。全224取引について、シグナル日の次のデータ行で約定していることも確認した。
今回のデータでは、next-open方式へ変更しても成績は大きく崩れませんでした。
むしろ勝率は75.0%から76.3%、平均損益率は+7.80%から**+8.23%**になっています。
ただし、これはnext-open方式にしたから成績が良くなったという意味ではありません。
今回の224取引では、翌営業日の価格ギャップが結果的に有利に作用しただけです。
ここで重要なのは勝率が上がったことではなく、実際に約定可能なタイミングを使ったバックテストへ修正できたことだと考えています。
レーザーテックは修正後も約-61%
次に、問題となったレーザーテックもnext-open方式で確認しました。

最大損失となったレーザーテックの取引を比較。翌営業日始値で約定する方式へ変更しても損失率は-61.30%となった。
従来方式では2024年4月17日のBUYシグナルと同じ日の終値で買っていましたが、修正後は翌18日の始値でBUY。
SELLも5月8日のシグナルに対して、翌9日の始値で約定しています。
それでも損失率は**-61.30%**でした。
つまり、約定タイミングを修正しても大幅損失はほとんど解消されていません。
レーザーテックの問題はバックテストの約定方法だけではなく、大きな損失を途中で止める仕組みがない現在の売買ロジックそのものにあることが分かりました。
もう一つ見つかった「未決済ポジション」の問題
バックテストの確認では、もう一つ気になる点が見つかりました。
20銘柄のうち11銘柄に、検証期間終了時点でまだSELLされていないポジションが残っていました。
内訳は、
- 含み益:4件
- 含み損:7件
- 未決済11件の損益率合計:-31.89%
です。
現在の「224取引・勝率75.0%」という数字は、決済まで完了した取引だけを集計しています。
参考として未決済11件を期末価格で評価対象に加えると、235件相当で勝率73.19%、平均損益率+7.30%となりました。
もちろん、これは期末に強制決済するルールを実装した結果ではないため、正式なバックテスト結果ではありません。
ただ、含み損のポジションが未決済のまま集計から外れていると、成績が実態より良く見える可能性があります。
この扱いも今後決める必要があります。
勝率76.3%でも、まだ実運用はできない

約定方法を修正した現在のバックテストでは、20銘柄・224取引で勝率76.3%、平均損益率+8.23%となりました。
数字だけを見ると方向性は悪くなさそうです。
ただ、今回調べただけでも、
- 最大損失が約-61%
- 損切りルールがない
- 最大保有期間がない
- 期末未決済ポジションが残る
- 手数料を考慮していない
- スリッページを考慮していない
といった課題が残っています。
そのため、現時点で「勝率76.3%の売買戦略が完成した」とは考えていません。
むしろ今回の検証で、良い数字が出たときほど中身を確認する必要があると感じました。
次は出口戦略を検証する
次に取り組みたいのは、今回レーザーテックで問題になった出口戦略です。
例えば、
- 一定の損失率で損切りする
- 最大保有日数を設定する
- 含み益が出たあとにトレーリングストップを使う
- 利確条件を追加する
- 現在のSELL条件そのものを見直す
といった方法が考えられます。
ただ、単純に「-10%で損切りすればいい」と条件を追加するだけでは、本来その後上昇する取引まで途中で切ってしまう可能性があります。
複数の条件を実際のデータで比較して、勝率だけでなく最大損失や平均損益がどう変化するのかを確認していく予定です。
まとめ
今回は、1銘柄・12取引だったバックテストを20銘柄へ広げ、224件までサンプルを増やしました。
そこで勝率75.0%という結果が出た一方、最大損失-62.26%という問題も発見。
さらに調べていくと、売買ロジックの出口戦略だけでなく、バックテストの約定タイミングや未決済ポジションの扱いにも改善点が見つかりました。
現在は翌営業日の始値で約定する方式へ修正し、224取引・勝率76.3%。
それでも最大損失は約-61%残っています。
今回の開発で感じたのは、バックテストは良い成績を確認するためだけではなく、売買ルールの弱点を見つけるためにも使えるということです。
次は損切りや最大保有期間などの出口戦略を検証し、さらに実際の取引に近いバックテストへ改善していきます。



コメント