• #モンテカルロ
  • #シミュレーション
  • #投資
  • #Vue
  • #Chart.js
  • #統計
開発未分類メモ

テンバガー銘柄のモンテカルロシミュレーション — 長期保有 vs 短期売買の検証

結論

AI関連銘柄(NVDA, TSLA, AMD, AVGO, TSM, GOOGL)を対象に、短期売買(利確/損切りルール)が長期保有に勝てるかをシミュレーションで検証した。

ほとんどの銘柄・パラメータで短期売買は長期保有に劣る。利確+20%、損切り-10%の設定では、NVDAで長期保有を上回る確率は約5〜15%にとどまる。取引コスト・税金を考慮するとさらに不利になる。

シミュレーションは以下のページで実際に試せる。

テンバガーMCシミュレーション

前提条件

項目内容
データ期間2020年1月〜2025年6月(銘柄により若干異なる)
頻度週次
価格終値(株式分割調整済み、配当は含まない)
対象銘柄NVDA, TSLA, AMD, AVGO, TSM, GOOGL
取引コスト0(手数料・税金・スリッページは考慮しない)
リターン計算単純リターン(対数リターンではない)
試行回数デフォルト1,000回(UIで変更可能)
乱数Mulberry32(シード値固定で再現可能)

注意: 取引コスト0%の前提であるため、短期売買の実際の成績はここに示す結果よりさらに悪くなる。

背景 — 検証したかったこと

投資において「良い銘柄を見つける」のと「良い銘柄を持ち続ける」のは別の問題だ。

AI関連というテーマに確信がありNVDAやAVGOを保有していたとしても、+20%上がれば利確したくなるし、-10%下がれば損切りしたくなる。その売買行動がリターンを毀損しているのではないかというのが検証したい仮説だ。

分割調整後の終値ベースで、NVDAは2020年1月から2025年6月にかけて約25倍になった。この期間に利確と損切りを繰り返すと、買い直しのタイミング次第でリターンが大幅に変わる。この「タイミング次第」の部分を定量化した。

シミュレーションの仕組み

2つのモード

1. ヒストリカル・シミュレーション(推奨)

実際の株価データをそのまま使用する。試行ごとにランダムな開始日を選び、売買ルールを適用する。

  • 開始日の範囲: データ全体の先頭20%(十分な売買期間を確保するため)
  • 長所: コロナショック→回復→AI相場という実際の市場局面がそのまま反映される
  • 短所: 過去データ1本に依存するため、バラつきの要因は開始日の違いのみ

厳密にはランダム開始バックテストに近い。「モンテカルロ法」と呼ぶ場合、不確実性の源泉が開始日と待機期間の乱数である点に注意が必要だ。

2. ブロック・ブートストラップ

過去の週次単純リターンを8週間のブロック単位で重複可能にランダム抽出し、連結して合成価格パスを生成する。端のブロックがデータ長を超える場合は切り捨てる。

  • 長所: 「ありえたかもしれない別の展開」を大量に生成できる
  • 短所: 長期トレンド(6ヶ月以上の連続上昇など)がブロック境界で切断される

売買ルール

各試行で以下のルールを適用する(デフォルト値を括弧内に示す)。

  1. ランダムな開始日に全額で購入
  2. 購入価格から+X%上昇したら利確・全額売却(X=20%)
  3. 購入価格から-Y%下落したら損切り・全額売却(Y=10%)
  4. 売却後、ランダムな待機期間を経て再購入(1〜20週の一様分布)
  5. データの最終日まで繰り返す
  6. 最終日時点の資産額でリターンを計算

同じ開始日で買って持ち続けた場合のリターン(長期保有)と比較する。

技術的な実装

React JSXからVue 3への移植

元のシミュレーションはReact + rechartsで作られていた。これをVue 3 Composition API + Chart.jsに移植した。

主な変更点:

  • useStateref()
  • useMemocomputed()
  • useEffectwatch() + nextTick()
  • recharts → Chart.js(canvas描画)
  • 株価データ(6銘柄分、約91KB)を public/data/tenbagger-stock-data.json に分離

乱数生成器

シード付き疑似乱数生成器としてMulberry32を採用した。Math.random()と異なり再現性があるため、同じシード値なら同じ結果が得られる。

function seededRandom(s) {
  let state = s | 0
  return () => {
    state = (state + 0x6d2b79f5) | 0
    let t = Math.imul(state ^ (state >>> 15), 1 | state)
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296
  }
}

統計関数

  • 標本分散(n-1除算)
  • 線形補間による分位点(10パーセンタイル、90パーセンタイル)

Codexレビューで改善した点

GPT-5.2(Codex CLI)に3回レビューを依頼し、以下を改善した。

  1. PRNGの品質: 単純な線形合同法からMulberry32に変更
  2. ブートストラップの実装バグ: ブロック開始位置とオフセットが混同されていたのを分離
  3. フェア比較: buyAndHoldを試行ごとの開始日に合わせて計算(全期間の固定値ではなく)
  4. ヒストリカルモード追加: レジーム変化を反映するため、実データ上での直接シミュレーションを追加

Codex(GPT-5.2)が指摘した限界

非定常性・レジーム変化

株の動き方は時期で異なる。同じNVDAでも、2022年の利上げ局面と2024年のAI相場ではリターン分布がまったく違う。ブートストラップは「どの時期のリターンも同じ確率で起きる」と仮定するため、この局面の違いを区別できない。

ヒストリカルモードでは実データの局面変化がそのまま反映されるため、この問題を一部緩和できる。ただし単一の実現パスに依存するため、「もしコロナショックがなかったら」のような反事実的な分析はできない。

ブロック境界での切断

ブートストラップはblockSize=8(8週間)でリターンをシャッフルする。NVDAが6ヶ月連続で上昇した局面があっても、8週ごとにランダムな別の時期へ切り替わるため、長期トレンドは再現されない。

「良い銘柄を持ち続ければ勝てる」という仮説の検証には、長期トレンドの存在が前提なので、これを切断するのは問題だ。ヒストリカルモードではこの問題は発生しない。

まとめ

  • 短期売買は長期保有に勝ちにくい。テンバガー銘柄であればなおさら
  • 取引コスト0%の前提でも勝てないため、実際にはさらに不利
  • ヒストリカル・シミュレーションは、銘柄選定が終わっている前提での検証に適している
  • ブートストラップは統計的な性質を探るのに有用だが、実際の投資判断にはヒストリカルの方が説得力がある
  • Codex(GPT-5.2)によるレビューは、統計的妥当性の検証に有効だった

シミュレーションを試す