2026-05-04(月)。週明けの朝、FX PC の状態確認から始まり、最終的に新戦略 PT007(XAUUSD M1 底ダブルトップ・ブレイクアウト)のデモ投入まで一気に進んだ。途中でバックテストの look-ahead バイアスが発覚して「PF 7.02 → 実態 PF 0.95」に転落する事件があり、戦略をゼロから組み直すことになった。設計の失敗と挽回の過程を時系列で残す。

この記事のポイント

  • 朝の状態確認で PT101 が現プロセスに未ロードと判明 → 即時投入(MAGIC=10101/10102)
  • XAUUSD M1 でカップ&ハンドルが顕著に出る、という観察から戦略構築を開始
  • 放物線フィットでカップ底買いを設計、M1 BT で PF 6.17 の好成績
  • look-ahead バイアス発覚 — 過去に遡ってエントリーしていた。live互換に書き直すと PF 0.95に転落
  • 「底ダブルトップのブレイクで買い、SL=谷、TP=トレーリング」に再設計 → PF 1.62、OS PF 1.98 でロバスト確認
  • SHORT 鏡像版は OS で PF 0.84 に崩壊、上昇相場では機能せず不採用
  • PT007 として LONG only で実装・デプロイ完了(MAGIC=70001/70002)

朝の状態確認:PT101 が動いていなかった

月曜の朝、まず FX PC(OpenVPN経由でリモート接続)の稼働状況をチェックした。 pythonw / MetaTrader5 / FXBot_Restart スケジューラ — どれも生きている。ところが、live_trader.log に気になる警告が継続的に出ていた。

[WARNING] whatif: insert failed: CHECK constraint failed: pattern IN ('P1','P2')

調べると、4月末の戦略リネーム(P1/P2 → PT001/PT002)で what_if_signals テーブルの CHECK 制約と整合が崩れていた。ライブトレード本体は try-except で握り潰されているので取引には影響なしだが、4/28 以降の約定行(executed)が約6日間ぶん whatif ログに記録されていない状態。これは別件として 5/9(土)市場閉場後に DB スキーマ修正することにし、ここでは保留。

そして起動バナーを照合して気付いた:

2026-05-02 15:52:44  Live Trader 起動(PT001+PT002+PT003+PT005+PT006)

PT101 がプロセスに乗っていない。 ファイルは 5/3 に FX PC へ転送されていたが、bot を再起動していなかったので IgnoreNew 仕様で旧プロセス(5/2 起動)が走り続けていた。

MAGIC を 70001/70002 → 10101/10102 に変更(オーナー指示)してから taskkill → FXBot_Restart 即時実行。新プロセス PID 8996 が 8:00:15 に起動し、PT101 含む 6戦略のバナーが出た。約 19.5 時間「ファイルは届いたが起動しない」状態が続いていたことになる。

教訓:新戦略の live 反映は ファイル push だけでは不足。明示的な kill→restart が必須。FXBot_Restart は IgnoreNew 設計なので、既存プロセスが走っているうちは新コードを拾わない。

XAUUSD M1 のカップ — 起点となった観察

朝の作業を一段落させたところで、オーナーから「XAUUSD は1分足でカップ&ハンドルが顕著に出る」という観察が来た。実際のチャート(5/1 12:00 〜 5/2 03:00 JST)を見ると、左肩 4625 → 底 4560 → 右肩 4660 という綺麗な U字型反発がはっきりしている。

これを機械化できれば、XAUUSD 1通貨だけでも常時稼働できる戦略になる。早速プロトタイプに入った。

初期設計:放物線フィット

古典的なカップ&ハンドル検出は「軸点を5点(左肩・底・右肩・ハンドル底・ブレイク)抽出」する複雑な処理になりがちだが、「カップは下に凸の放物線」と捉えれば、二次回帰一発で定量化できる。

y = ax² + bx + c で最小二乗フィット
  ・a > 0 (下に凸 = カップ)
  ・R² ≥ 0.55 (フィット度)
  ・頂点が窓中央付近 (底通過済み)
  ・直近の値幅 ≥ 0.4% (深さ十分)

更に「上から落ちてきた」フィルタとして、窓の左半分で 0.5% 以上の下落を必須化した。XAUUSD M1 の 14ヶ月分(10万本)でスキャンすると、約 200件のセットアップが検出された。

BT結果が異常に良すぎた

パラメータスイープを 36通り走らせた。結果は全パターン黒字、PF 4.65〜7.49。最良条件で:

項目
件数207
勝率70.0%
総pips+38,293
PF7.02
期待値/件+185 pips
Max DD-600 pips

全36パターン黒字、IS/OS 分割でも OS PF 2.86 で安定。一見、過剰最適化の兆候もない。「これは PT007 として live 投入できる」と判断しかけた。

ここで止めるべきだった。 PF 7.02 は FX 戦略としては明らかに「良すぎる」数字(健全な戦略は PF 1.5〜2.5 が相場)。確認のために BT のロジックを再点検したら、致命的な欠陥が見つかった。

look-ahead バイアスの発覚

BT の find_entries 関数を改めて読んだ:

for j in range(setup['bottom_idx'] + E1_MIN_BARS_PAST, setup_idx + 1):
    if closes[j] >= bounce_threshold:
        e1_idx = j  # ← 過去の bar を約定に使う
        break

これは「カップが完成して見える時点(setup_idx)から、過去に遡って最初に反発した bar(e1_idx)でエントリーしたことにする」というロジック。実際のライブトレードでは未来情報なしに過去のbarを買えないので、look-ahead バイアスそのものだった。

本来のライブ実行は「現在 bar まで見えている情報だけで判定し、現在 bar でエントリー」。これに合わせて BT を書き直したところ、結果は一変した。

BT種別件数勝率総pipsPF
Look-ahead版(旧)20770.0%+38,2937.02
Live互換版(実態)20546.3%-9930.95

事実上のブレークイーブン。PF 7.02 は幻だった

原因も明らか:カップが「完成して見える」のはエントリー機会が過ぎた後。底で買うにはカップ未完成、カップが見えた時点では既に右側を上げていて TP(measured move = entry + cup_depth)が遠すぎる。設計上の本質的問題で、フィルタを足しても解決しない。

教訓:BT の数字が「異常に良い」と感じたら、look-ahead を疑う。現在 bar の判定に未来 bar の情報が混じっていないか、コードレベルで再点検する必要がある。「PF 7 を見たら一旦止まる」を判断ルールとして覚えておく。

再設計:底ダブルトップ・ブレイクアウト

放物線アプローチを破棄しかけたところで、オーナーから具体的な代案が来た。

「底値のダブルトップなどのブレイクでエントリー、TP は最大含み益、SL はそのダブルトップの底値で BT を回して」

これは違う発想だった。カップ全体を見るのではなく、底で起きる小さな価格構造(2つの近似ピーク)を捉え、そのブレイクをエントリーtrigger に使う。形状検出ではなく、タイミング検出。SL は谷底(= ダブルトップの底値)、TP はトレーリングで含み益最大値から戻ったらクローズ。

v3 ロジック

1. 底領域確認(直近60本)
   ・高安幅 ≥ 0.50%
   ・底bar が直近5本以内ではない(底通過済み)

2. ダブルトップ検出(直近20本)
   ・PEAK_PIVOT_W=2 で局所max抽出
   ・上位2ピーク間
     - 高さ差 ≤ 0.10%
     - 距離 ≥ 3本
     - 谷が両ピーク高値より下にある

3. エントリー(LONG)
   ・close > 2峰の最高値
   ・直前 bar で未ブレイク(連続シグナル防止)

4. SL = ピーク間の谷底
5. TP = トレーリング: max_high - $0.30(=3pips)
6. 時限: 4時間(M1×240本)

BT結果(live互換、look-ahead無し)

期間件数勝率PF総pips
IS(1/20 - 3/31, 2.5ヶ月)35176.6%1.56+2,501
OS(4/1 - 5/1, 1ヶ月)11471.1%1.98+690
FULL46575.3%1.62+3,191

OS PF 1.98 が IS PF 1.56 を上回っている。これは過剰最適化していない証拠で、むしろロバスト。トレーリング幅は $0.30〜$5.00 で 28パターンスイープしたが、$0.30〜$0.50 が安定して上位だった(広いトレーリングは負ける)。

PF 1.62 は FX 戦略としては現実的に妥当な数字。先ほどの PF 7.02 と違って、信頼できる数字として受け取れる。

SHORT 鏡像版は不採用

念のため SHORT(売り)も鏡像反転で BT した。「上昇領域で形成されるダブルボトムの下抜けで売り」というロジック。

期間件数勝率PF総pips
IS33278.3%3.02+5,864
OS9764.9%0.84-210

IS で異常に良く(PF 3.02)、OS で破綻(PF 0.84)。典型的な過剰最適化の症状。原因は明らかで、OS 期間の XAUUSD は強い上昇相場(4500台 → 4660台)であり、SHORT 戦略は機能しない。相場レジーム依存のロジックなのでフィルタ追加が必要だが、本実装では 不採用 として LONG only に絞った。

メモリにも「XAU はボラ駆動で SR 反転に従わない」と記録があり、XAU の特性的に底値からの反発(LONG)は素直、頂点からの反落(SHORT)はフェイクが多いのは整合的かもしれない。

PT007 として実装・デプロイ

戦略仕様が固まったので、既存の scripts/pt006.pypt101.py と同じ構造で scripts/pt007.py を実装。

項目
MAGIC TP170001
MAGIC SMA70002
対象通貨XAUUSD のみ
方向LONG only
時間軸M1
SLダブルトップの谷底
TPトレーリング: max_high - $0.30

live_trader.py にも check_entry_pt007check_pt007_trailing_exit を追加し、メインループに M1 確定検出ループを新設。9:23:41 に新プロセス(PID 7420)が起動し、起動バナーで 7戦略稼働を確認。

Live Trader 起動(PT001+PT002+PT003+PT005+PT006+PT101+PT007)
  PT007 MAGIC: TP1=70001 SMA=70002 (M1 底ダブルトップ・ブレイク, XAUUSD only LONG, トレーリング$0.3)

診断のため M1足確定の info ログを一時的に有効化したところ、毎分の M1 ループが回っていることが確認できた。シグナル発生待ちで建玉なし、これは健全な動作。

1日の総括

今日の作業は3段階に分かれた:

段階所要結果
① PT101 投入(朝の状態確認から)1時間完了 MAGIC=10101/10102 で稼働開始
② カップ&ハンドル戦略の試行錯誤(look-ahead 発覚含む)3時間 設計の失敗を学習
③ PT007 再設計・実装・デプロイ2時間完了 MAGIC=70001/70002 でデモ並走開始

得られた教訓

  1. BT の数字が異常に良い時は look-ahead を疑う。PF 7 は赤信号
  2. 形状検出 ≠ タイミング検出。同じ U字パターンでも、エントリー位置(底買い vs ブレイク買い)で全く別の戦略になる
  3. 過剰最適化チェックは IS/OS 分割が最優先。OS PF が IS PF を上回るのが理想
  4. SHORT を素朴に鏡像反転で追加してはいけない。相場レジーム依存性が強い
  5. 運用中の bot に新戦略を「ファイル push しただけ」では反映されない。明示的 restart 必須

PT007 は今後 1ヶ月のデモ並走で実績を確認し、本投入の判断をする予定。期待値は 月+700 pips、月100件前後の発火。BT 通りに行けば XAUUSD 単独で第二の収益柱になる可能性がある。

今日の作業ファイル一式は /data/.company/fx/strategy/parabola_cup/ に保存。PT007 設計書は /data/.company/fx/strategy/PT007.md。実装は scripts/pt007.py + live_trader.py 統合済。