0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【演習弐・実践編其の弐】量子コンピュータで株価予測に挑んだら、バックテストの罠にハマった話

Last updated at Posted at 2025-10-09

1. はじめに:RSA攻撃の次は株で一攫千金...のはずだった

前回の記事で、Shorのアルゴリズムの現実の厳しさを体験しました。

「暗号はダメだった...でも株価予測ならイケるんじゃね?」

という浅はかな考えのもと、**量子フーリエ変換(QFT)**を使った株価予測に挑戦しました。

結果は...


2. 前提:量子フーリエ変換で周期性を検出する

アイデア

株価には隠れた周期性があるのでは?

  • 景気循環
  • 決算サイクル
  • 季節性

量子フーリエ変換で周期を検出

その周期でトレードすれば儲かる!(はず)

実装方針

1. 過去データで周期を検出(QFT)
2. その周期でOOS期間(未来)を予測
3. Buy & Holdと比較

3. 実験結果:衝撃の数値たち

検証対象(日本株5銘柄)

銘柄 量子戦略 買い持ち 判定
トヨタ +433% +359% ✅ 勝ち
NTT +286% +418% ❌ 負け
藤田観光 +1293% -0.7% ✅ 勝ち?
丸善CHI +142% +57% ✅ 勝ち
イオン +208% +338% ❌ 負け

一見の印象

  • 😍 藤田観光すげええええ!+1293%!!
  • 🤔 トヨタと丸善も勝ってる
  • 😅 NTTとイオンは負けてる...

4. 現実:これ、完全にオーバーフィッティングです

4.1 藤田観光の異常値

+1293% って...おかしいだろ

理由:

  • テストデータでパラメータを最適化しすぎ
  • たまたまその期間だけ当たった
  • 再現性ゼロ

4.2 NTT・イオンで負けてる理由

買い持ちに負けるなら、戦略として意味がない

4.3 最大の問題:バックテストの罠

# こういうことをやってた
for window in rolling_windows:
    train_data = data[window-60:window]
    test_data = data[window:window+12]
    
    # 量子で周期検出
    period = quantum_detect_period(train_data)
    
    # その周期でテストデータをトレード
    strategy_return = trade_with_period(test_data, period)

問題点:

  • 12ヶ月後の未来データ(test_data)は実際には見えない
  • SMA(移動平均)も未来の情報を使ってる可能性
  • つまりタイムマシンを使ったズル

5. 実験コード(教育用・簡易版)

免責事項

⚠️ このコードは教育目的です。実際の投資に使用しないでください。

※日本語表示するにはjapanize_matplotlibを
使っているqBraidのPythonカーネルのPackageに追加してから下記を一度だけ実行してください。

# --- 日本語フォント自動設定ヘルパー(おまじない)--------------------------------
import japanize_matplotlib  # ← 日本語の呪いを解く魔法の呪文
import os
import matplotlib
from matplotlib import font_manager as fm
import matplotlib.pyplot as plt

def setup_japanese_font():
    """
    日本語が豆腐にならないようにフォントを自動設定。
    1) システムにある日本語フォントを探索
    2) プロジェクト同梱フォント(./fonts/*.ttf/otf)があれば登録
    """
    candidates = [
        "IPAexGothic", "IPAGothic",
        "Noto Sans CJK JP", "Noto Sans JP", "Noto Serif CJK JP",
        "TakaoGothic",
        "Yu Gothic", "YuGothic",
        "Hiragino Sans", "Hiragino Kaku Gothic ProN",
        "MS Gothic"
    ]
    available = {f.name for f in fm.fontManager.ttflist}
    for name in candidates:
        if name in available:
            plt.rcParams["font.family"] = name
            plt.rcParams["axes.unicode_minus"] = False
            print(f"日本語フォント: {name} を使用します")
            return name

    # ローカル同梱フォントのパス候補
    local_paths = [
        "./fonts/ipaexg.ttf",
        "./fonts/NotoSansJP-Regular.otf",
        "./NotoSansJP-Regular.otf"
    ]
    for p in local_paths:
        if os.path.exists(p):
            fm.fontManager.addfont(p)
            family = fm.FontProperties(fname=p).get_name()
            plt.rcParams["font.family"] = family
            plt.rcParams["axes.unicode_minus"] = False
            print(f"ローカル日本語フォント {p} を登録して使用します({family}")
            return family

    print("⚠ 日本語フォントが見つかりません。豆腐が出る場合は IPAex または Noto Sans JP を導入してください。")
    print("  例: Ubuntu系 → `sudo apt-get install fonts-noto-cjk`(No Tofu フォント)")
    return None

# アプリ起動時に一度だけ設定
setup_japanese_font()
# -------------------------------------------------------------------------
# ============================================================
# 量子フーリエ変換による株価周期性検出(超簡易版)
# ============================================================
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.circuit.library import QFT
from qiskit_aer import AerSimulator
from qiskit import transpile

print("="*70)
print("🔮 量子フーリエ変換で株価の周期を探る(教育デモ)")
print("="*70)

# ============================================================
# 1. 模擬データ生成(実際の株価の代わり)
# ============================================================
def generate_mock_stock_data(length=100, period=12):
    """周期性のある模擬株価データを生成"""
    t = np.arange(length)
    trend = 0.01 * t  # 緩やかな上昇トレンド
    cycle = 0.1 * np.sin(2 * np.pi * t / period)  # 周期成分
    noise = 0.05 * np.random.randn(length)  # ノイズ
    return trend + cycle + noise

# サンプルデータ生成
stock_data = generate_mock_stock_data(length=100, period=12)
print(f"\n📊 模擬株価データ生成完了: {len(stock_data)}日分")
print(f"   実際は周期{12}日の正弦波 + トレンド + ノイズです")

# ============================================================
# 2. 量子回路で周期検出
# ============================================================
def detect_period_with_qft(data, n_qubits=4):
    """QFTで周期を検出(超簡易版)"""
    # データを正規化
    normalized = (data - np.mean(data)) / np.std(data)
    
    # 量子回路作成
    qc = QuantumCircuit(n_qubits, n_qubits)
    
    # 重ね合わせ状態
    for i in range(n_qubits):
        qc.h(i)
    
    # データエンコード(位相回転)
    for i, val in enumerate(normalized[:2**n_qubits]):
        angle = np.pi * val
        qc.rz(angle, i % n_qubits)
    
    # QFT適用
    qft = QFT(n_qubits)
    qc.append(qft.inverse(), range(n_qubits))
    
    # 測定
    qc.measure(range(n_qubits), range(n_qubits))
    
    # シミュレータで実行
    backend = AerSimulator()
    job = backend.run(transpile(qc, backend), shots=1024)
    counts = job.result().get_counts()
    
    # 周期推定
    periods = []
    total = sum(counts.values())
    for bitstring, count in counts.items():
        if count > total * 0.05:  # 5%以上の確率
            measured = int(bitstring, 2)
            if measured > 0:
                period = len(data) / measured
                if 2 <= period <= len(data) // 2:
                    periods.append((period, count / total))
    
    return sorted(periods, key=lambda x: x[1], reverse=True)

print("\n🔮 量子回路で周期検出中...")
detected_periods = detect_period_with_qft(stock_data)

print("\n✨ 検出された周期:")
for period, confidence in detected_periods[:5]:
    print(f"   周期 {period:.1f}日 (信頼度: {confidence:.3f})")

# ============================================================
# 3. 可視化
# ============================================================
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))

# 株価データ
ax1.plot(stock_data, label='模擬株価', linewidth=2)
ax1.set_title('📈 模擬株価データ(実際は12日周期)', fontsize=14, weight='bold')
ax1.set_xlabel('日数')
ax1.set_ylabel('価格')
ax1.legend()
ax1.grid(alpha=0.3)

# 検出された周期
if detected_periods:
    periods, confidences = zip(*detected_periods[:8])
    ax2.bar(range(len(periods)), confidences, color='blue', alpha=0.7)
    ax2.set_title('🔮 量子フーリエ変換で検出された周期', fontsize=14, weight='bold')
    ax2.set_xlabel('周期ランキング')
    ax2.set_ylabel('検出信頼度')
    ax2.set_xticks(range(len(periods)))
    ax2.set_xticklabels([f'{p:.1f}' for p in periods], rotation=45)
    ax2.grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()

print("\n" + "="*70)
print("🎯 結論:")
print("  ・量子フーリエ変換は周期を検出できる")
print("  ・しかし、それが「未来も続く」保証はない")
print("  ・バックテストで良くても、実運用では...")
print("="*70)

6. 何が問題だったのか:5つの教訓

教訓1: バックテストは未来を保証しない

過去に有効だった戦略が、未来も有効とは限らない。

教訓2: オーバーフィッティングは簡単に起きる

複雑な手法ほど、データに過剰適合しやすい。

教訓3: 量子は万能じゃない

量子コンピュータは計算の仕方が違うだけ。
魔法じゃない。

教訓4: 情報リーク(Look-Ahead Bias)

未来の情報を使っていないか、常に疑え。

教訓5: 単純な手法の方が頑健

  • Buy & Hold
  • インデックス投資
  • ドルコスト平均法

↑こういうシンプルな戦略の方が、長期的には強い。


7. それでも得られたもの

技術的知見

  • QFTの実装経験
  • Qiskit 2.1の使い方
  • バックテストフレームワークの設計

教訓

「失敗から学ぶことは、成功から学ぶことより多い」


8. 次回予告:量子乱数の奇妙な世界

第三弾(完結編)では、量子乱数の不思議な性質に迫ります。

  • 真の乱数とは?
  • 古典乱数との違い
  • 奇妙な偏りの正体

お楽しみに! 🎲✨


参考文献


おわりに

量子コンピュータで株価予測...夢がありますよね。

でも、現実は甘くなかった。

失敗を公開する勇気こそが、技術コミュニティの財産だと信じています。

この記事が、同じ道を歩もうとしている誰かの助けになれば幸いです。

次回の量子乱数編で、ようやく「面白い発見」をお届けできると思います!


この記事が役に立ったら、いいね👍とフォローをお願いします!

ご質問やコメントもお気軽にどうぞ。一緒に量子の世界を探求しましょう!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?