有用なバックテストのテストは、より良いパラメーターを探すこととは無関係です。プロセスを途中で止めて復元し、同じリプレイを最後まで実行します。入力が同一で、実行シミュレーターが制御されていれば、判断、注文、資産推移は中断なしの実行と一致するはずです。
一致しなければ、状態管理に問題があると分かります。保存していない、または再構築できない何かに戦略が依存しています。この依存関係は、リサーチの実行を再開するとき、ワーカーを入れ替えるとき、ペーパートレードのサービスに新しいコードをデプロイするときに影響します。
このテストが気に入っているのは、期待される答えがとりわけ明確だからです。相場が変わったかどうかを議論する余地はありません。どちらの実行にも同じ相場データが渡されます。
再起動でやりがちな誤りを3つ紹介します。数値は説明用ですが、どの失敗も、ほかの部分が決定論的なシステムで起こり得ます。
1. 数本のバーを読み込み、指標のウォームアップが済んだと思い込む
戦略が期間100の指数移動平均を使うとします。更新式は次のとおりです。
alpha = 2 / 101
ema_next = alpha * close + (1 - alpha) * ema_previous
中断なしのプロセスでは、蓄積されたEMAの値が引き継がれます。再起動したプロセスは100本のバーを取得し、最初の終値を初期値としてEMAを計算し、期間100の指標には100件の観測値があればよいと思い込みます。
この思い込みは、指標の平滑化パラメーターと有限の記憶ウィンドウを混同しています。EMAは初期状態の影響を、時間とともに減衰させながら保持します。2つのバージョンのEMAに最初10価格単位の差があり、その後の価格が同一なら、その差は次のように縮まります。
| 初期化後の更新回数 | 残る差 | 初期誤差に対する割合 |
|---|---|---|
| 100 | 1.353 | 13.53% |
| 250 | 0.0674 | 0.674% |
| 500 | 0.000454 | 0.00454% |
計算式は10 * (99 / 101)^kです。最初の観測値を初期値に使う場合、100本のバーを取得しても更新回数は99回です。
問題はたいてい、判断の境界付近で表面化します。片方の実行では価格がEMAを上回り、もう片方では下回ります。わずかな数値差が、取引を丸ごと1回増やすことがあります。そうなると、クールダウン、利用可能な現金、その後の判断まで食い違い始めます。
再帰的な指標の状態、初期化済みかどうか、最後に処理したイベントを保存しましょう。あるいは、既知の初期状態から再生します。ウォームアップ期間を長くすれば許容できる近似値が得られる場合もありますが、明示した誤差許容値に基づいて期間を決め、その誤差で判断が変わり得るか確認してください。「期間の5倍」は慣例であって、保証ではありません。
履歴が必要なのは指標だけではありません。ローリング・パーセンタイルにはウィンドウが必要です。オンラインモデルにはオプティマイザーの状態が必要な場合があります。損失後に3本待つルールなら、損失とカウンターを覚えておく必要があります。
2. ポジションを保存して、処理中の注文を忘れる
目標ポジションは10単位です。10単位の買い注文のうち4単位が約定し、残り6単位が未約定です。ポジションを4と記録して再起動し、不足分の6単位を買い注文として出し直します。
元の残りと出し直した注文の両方が約定すると、保有量は16になります。
このバグはバックテストでは見つからないことがあります。再起動時に約定エンジンが稼働中の注文をひそかに消してしまうためです。ペーパートレードでは、シミュレーターや外部サービスが注文を保持している場合があります。同じ復旧コードでも、どのコンポーネントが稼働し続けたかによって、結果のエクスポージャーが変わります。
| 再起動時 | 実際の状態 | ポジションだけから復旧した場合 |
|---|---|---|
| 目標ポジション | 10 | 10 |
| 約定済みポジション | 4 | 4 |
| 未約定の買い数量 | 6 | 0 |
| 追加で必要な数量 | 0 | 6 |
問題は、復旧直後に注文が説明なく急増する形で現れます。エクスポージャーが2倍になることもあります。保護注文がまだ有効なのにポジションを決済し、その注文が後から新たなポジションを作ってしまうこともあります。
チェックポイントには、ポジションとともに注文の識別情報とライフサイクル状態も保存する必要があります。新たな注文を生成する前に、復旧処理でそれらの記録と執行システムを照合してください。結果が不明な注文は調査が必要です。「確認応答を保存していない」を「注文を送信していない」と扱うと、重複注文が生まれます。
一意なクライアント注文IDがあれば、何が起きたか照会しやすくなります。ただし、受信側システムが必要な一意性や冪等性のルールを実際に適用している場合に限り、重複を防げます。処理済み約定IDも永続化して、リプレイされた約定でポジションが2度増えないようにしましょう。
私は、地味な注文状況画面に愛着があります。再起動の日には、小さな一覧の1行1行が、建物の中で突然いちばん興味深い画面になります。
3. ポジションを復元して損益台帳を新しく始める
手数料なし、レバレッジなしの現物取引を考えます。現金$10,000で始め、$100で10単位を買い、評価価格が$110になった時点でチェックポイントを記録します。
正しい状態は、現金$9,000と評価額$1,100のポジションで、資産は合計$10,100です。復旧時に10単位のポジションを戻しながら、現金を元の$10,000にリセットすると、資産を$11,100と報告してしまいます。プロセスの再起動で$1,000を生み出したことになります。
もっと目立たない形で現れることもあります。資産は正しく保たれていても、取得価格が$110にリセットされます。総資産が正しいまま、実現損益と含み損益の内訳が変わることがあります。損切りや決済条件が取得価格を参照する場合、この会計上の近道が取引の動作まで変えてしまいます。
あるいは、過去の資産最高値を忘れてしまいます。資産が$10,600まで上がった後、$10,100まで下がったとします。ドローダウンは約4.72%です。復旧時に最高値をリセットすると、戦略は突然、ドローダウンがゼロだと思い込みます。ドローダウンに基づくリスク管理が、許可なくリセットされたことになります。
この場合、問題は資産の不連続な変化、不自然に改善したドローダウン、またはデプロイ後に作動しなくなるリスクルールとして現れます。台帳と、現金の移動、ポジション、該当する取得原価、発生済み費用、リスク管理の記憶といった、会計に依存する戦略の状態を保存しましょう。同じ評価時刻で、復元した資産と台帳を照合してください。
チェックポイントには一貫した記録境界が必要です。約定後の現金と、約定前のポジション数量を保存すると、実際には存在しなかった状態ができてしまいます。関連する状態をまとめて確定するか、そこから再構築できる永続的なイベント列を記録してください。復旧時に約定を飛ばしたり二重適用したりしないよう、状態とともにイベントのカーソル位置も保存します。
リサーチ用ハーネスには、まず中断なしの基準リプレイを1回実行し、次に意図的に扱いにくいタイミング、つまり指標の初期化中、部分約定の直後、リスク制限の作動中に、別の実行を再起動するテストを組み込みます。同じイベント順を使い、シミュレーターの乱数状態も保持してください。復旧後最初の判断、注文と約定の記録、資産推移を比較します。最終残高だけが一致していても、互いに打ち消し合う誤りが隠れていることがあります。
注文を送信した後、確認応答を受け取る前にクラッシュするケースでは、戦略プロセスとは独立して執行サービスの状態も保持する必要があります。そうしなければ、テスト対象であるはずの不確実性を、ハーネスが消してしまいます。
戦略の仕様には、何を記憶するかも含まれます。リプレイの途中でプロセスを停止しても、どうやって作業に戻るのかを正確に示せるよう、その記憶を明示しましょう。
← 記事一覧


