モバイル&ワイヤレスブロードバンドでインターネットへ

gwaw.jp
 
AutoEncoder × dynamic rebalance

異常を検知したら、逃げるべきか

平穏な相場だけを学習した AutoEncoder は、危機が来ると再構成に失敗する——「いつもと違う」が誤差として現れる。 ではその異常度で、資産配分を自動的に守りに寄せたら、成績は良くなるのか。 ブラウザで実際に学習させ、取引コストも検知遅延も差し引いた上で、正直に確かめます。

TensorFlow.js AutoEncoder 2資産 backtest 擬似データ

[ § 0 ] 導入

「正常を学べば、異常が浮かぶ」を市場へ

アクセスログの異常検知でやったことは単純でした。正常なパターンだけを AutoEncoder に学習させる。 すると学習していないパターン——攻撃——が来たとき、再構成に失敗して誤差が跳ね上がる。 「正常を知れば、異常が浮かぶ」。

同じ発想を市場に当てます。平穏な相場だけを学習させ、危機が来たら誤差で気づく。 さらに一歩進めて、その異常度に応じて資産配分を自動で守りに寄せたらどうなるか。 これが「動的オート・リバランス」です。

はじめに、大事なこと。 この記事は技術デモであり、投資助言ではありません。使うのは実市場データではなく擬似データです。 ここで見えるのは「この手法がどう振る舞うか」であって、「儲かるか」ではありません。 そして先に白状すると——私はこの検証で、一度結論を間違えました。その経緯も含めて書きます。
[ § 1 ] 異常検知の再利用

平穏だけを、学習させる

擬似データは2資産です。リスク資産(平常時は緩やかに上昇、危機時は高ボラティリティで下落)と、 安全資産(低ボラティリティで微増)。ここに危機レジームを2回、意図的に埋め込みます。

入力は「直近10日のリターン」

リスク資産のリターンを10日ぶんの窓に切り、標準化して AutoEncoder に入れます。 構造は 10 → 4 → 10。真ん中で4次元に圧縮するので、よくあるパターンしか通れません。 学習は前半の平穏期だけ。危機のデータは一切見せません。

train.js — TensorFlow.js で AutoEncoder
const model = tf.sequential();
model.add(tf.layers.dense({ inputShape:[10], units:6, activation:'tanh' }));
model.add(tf.layers.dense({ units:4, activation:'tanh' }));   // ボトルネック
model.add(tf.layers.dense({ units:6, activation:'tanh' }));
model.add(tf.layers.dense({ units:10 }));                      // 再構成
model.compile({ optimizer: tf.train.adam(0.01), loss:'meanSquaredError' });

// 学習データは「平穏期の窓」だけ。危機は見せない
await model.fit(calmWindows, calmWindows, { epochs:60, batchSize:16 });

入力と出力が同じ(calmWindows, calmWindows)なのが AutoEncoder です。 「自分自身を復元する」訓練を平穏データだけで積むと、危機の形は復元できなくなる。 その失敗の大きさが、異常度です。

CPU backend で十分

この規模のネットワークなら GPU は不要です。以前のベンチでも「LSTMはCPU、モンテカルロはGPU」という住み分けが出ました。小さな全結合はカーネル起動のオーバーヘッドの方が大きく、CPUの方が速いことすらあります。

[ § 2 ] 異常度を、配分に写す

2つのルールを比べる

再構成誤差を、学習期間の平均と標準偏差で z スコアに直します。平常なら 0 付近、危機なら跳ね上がる。 これを配分に変換する方法を、2つ用意しました。

ルールA:連続シフト

異常度に比例して、リスク資産の比率をなめらかに下げます。少し変なら少しだけ守る。

rules.js — 連続シフト
target = clamp(base - k * max(0, z), floor, base);   // zが大きいほど守りへ

ルールB:閾値+ヒステリシス

閾値を超えたら守りに切り替え、別の(低い)閾値を下回るまで戻さない。 境界付近でのバタつきを防ぐ仕組みで、これが売買回数に効きます。

rules.js — 閾値+ヒステリシス
if (!defensive && z > thrOn)  defensive = true;   // 危機入り
if ( defensive && z < thrOff) defensive = false;  // 平常復帰(thrOff < thrOn)
target = defensive ? floor : base;

比較対象として、何もしない静的配分(リスク資産60% / 安全資産40%を維持)も並べます。

[ § 3 ] 正直なバックテスト

4つの罠を、先に塞ぐ

この手の検証は、油断すると実際より良く見えてしまいます。塞いだ罠は4つです。

① ルックアヘッド・バイアス

未来を知った状態で学習・判定してしまう罠。ここでは AutoEncoder の学習も、z スコアの基準(平均・σ)も、 前半の平穏期だけから作ります。危機のデータは学習に一切入りません。

② 検知遅延

当日の異常を当日中に使えるのは非現実的です。配分の決定には前日までの情報だけを使います。 危機が始まってから守りに入るまで、必ず遅れが出る——それが現実です。

③ 取引コスト

リバランスのたびに手数料とスプレッドがかかります。無視すると動的戦略が過大評価されるので、 売買した分だけコストを引きます売買回転率も指標として表示します。

④ 後知恵の最適化

閾値や感度を「一番成績が良くなる値」に合わせ込めば、過去にだけ効く戦略が出来上がります。 なのでこのデモでは最適値を提示しません。スライダーはあなたが動かし、 条件を変えると結論がどう揺れるかを見てもらいます。

擬似データであることの利点。 危機の位置と強さをこちらで決められるので、「検知できたか」を答え合わせできます。 実データだと「何が異常だったか」の正解が曖昧ですが、擬似データなら検知性能そのものを測れます
[ § 4 ] 体感デモ

ブラウザで、実際に学習させる

「学習してシミュレーション」を押すと、TensorFlow.js がこの場で AutoEncoder を訓練します(数秒)。 学習が終わると、異常スコアと3戦略の資産曲線、配分推移が描かれます。

Interactive · AutoEncoder anomaly → dynamic rebalance
動的オート・リバランス シミュレーター
擬似データ・2資産。学習は平穏期のみ、判定は前日までの情報のみ、取引コスト控除つき。投資助言ではありません。
初期化中…
① 市場(擬似)と 異常スコア — 赤帯が仕込んだ危機、紫線が AutoEncoder の再構成誤差 z
リスク資産 安全資産 異常スコア z 危機(正解)
② 資産曲線 — 3戦略の比較(取引コスト控除後)
静的 60/40 動的・連続シフト 動的・閾値+ヒステリシス
③ リスク資産の配分推移 — 異常時にどれだけ守りへ寄ったか
静的 連続 閾値
学習前です。「学習してシミュレーション」を押してください。
感度 k(連続)0.35
閾値 ONz>2.0
閾値 OFFz<1.0
取引コスト0.10%
危機の強さ1.00×
「30相場で検証」を押すと、学習済みの AutoEncoder を使い回して30通りの相場をシミュレーションし、どの戦略が何回勝ったかを数えます。1回の結果と、分布は別物です。

スライダーを動かすと、結論が揺れます。閾値を下げれば早く逃げられますが誤検知で売買が増え、 コストを上げると動的戦略から順に不利になる。「どの設定でも動的が勝つ」わけではないのが見えるはずです。

[ § 5 ] 分かったこと

数回では、見誤った

下は、実際にブラウザで AutoEncoder を学習させて回した2つの相場です (どちらも危機2回・コスト0.1%・閾値 z>2.0・感度0.35。違うのは乱数だけ)。

相場A最終資産最大DD年率volSharpe売買回転
静的 60/401.502-9.7%0.1361.600.0
動的・連続シフト1.185-3.9%0.0721.2623.4
動的・閾値+ヒス1.212-4.6%0.0781.329.6
相場B最終資産最大DD年率volSharpe売買回転
静的 60/401.094-20.1%0.1530.390.0
動的・連続シフト1.053-6.4%0.0750.4130.3
動的・閾値+ヒス1.056-10.2%0.0880.3810.4

相場Aでは静的が最良、相場Bでは連続シフトが最良。 さらに別の乱数では閾値ルールが勝つ回もありました。何度押しても勝者が入れ替わる。 ——ここで私は「勝者は相場次第、リターンの優劣は運任せ」と結論づけました。

それが、間違いでした。 数回の観測から一般化してしまった。この記事自身が「1回の結果を信じるな」と書いておきながら、 数回で同じことをやっていたわけです。

30相場まわすと、構造が見えた

デモの「30相場で検証」は、学習済みの AutoEncoder を使い回して30通りの相場を回し、 勝者を数えます。取引コストを変えて2回押した結果が、これです。

Sharpe が最良だった回数静的 60/40連続シフト閾値+ヒス
取引コスト 0.1%4 / 306 / 3020 / 30
取引コスト 0.5%8 / 300 / 3022 / 30

個別に数回見たときは勝者がばらばらだったのに、30回まとめると 閾値+ヒステリシスが両方の条件で最良(20勝・22勝)。 「運任せ」ではなく、はっきりした順序がありました。数回の観測では、それがノイズに埋もれて見えなかった。

これが、この記事でいちばんの教訓です。 少数の観測は、構造を隠します。「毎回違う結果が出る」ように見えても、 回数を増やせば分布が現れる。バックテストで本当に必要なのは、良い1回ではなく、たくさんの回。 デモのボタンを押して、自分の目で分布を確かめてみてください。

コストを上げると、何が起きたか

さらに面白いのは、コストを 0.1% → 0.5% に上げたときの動き方です。

  • 連続シフトは 6勝 → 0勝に消滅。回転が閾値の3.4倍あるため、コストに真っ先に殺される
  • 静的は 4勝 → 8勝に倍増。何も売買しない=回転ゼロの強みが、高コスト環境で効いてくる
  • 閾値+ヒステリシスは 20勝 → 22勝。両方の環境で安定して最良

同じ異常スコアを使っていても、動き方の設計だけで「一度も勝てない」ところまで落ちる。 連続シフトの敗因は検知の精度ではなく、じりじり動き続ける設計そのものでした。 ヒステリシスで往復を止めるという一手が、コストを通じてこれだけの差を生みます。

30相場で、変わらなかったこと

勝敗の分布が動く一方で、リスク側の効果はどちらのコスト水準でも安定していました。

  • DD圧縮 平均41〜49% — リスク縮小は例外なく起きる
  • vol削減 平均47% — 両条件でまったく同じ。値動きは確実に穏やかになる
  • 回転比 平均3.4倍(連続÷閾値)— これも両条件で不動

検知と、利益は別

AutoEncoder 自体の性能は良好です。危機期の再構成誤差は平常の数十倍に跳ね、 グラフの紫線は赤帯にきれいに重なります。それでも検知できることと、得することは別でした。 利益を決めたのは検知精度ではなく、検知したあとの動き方——配分ルールの設計です。

この結果の射程。 ここで見た「閾値ルールが優位」は、この擬似データの生成過程における性質です。 危機の深さも頻度もこちらで決めており、実市場は違う形をしています。 言えるのは、リスクは確実に縮むこと、回転差は必ず出ること、 そして——少数の観測から結論を出してはいけないことです。最後のひとつは、私自身がここで学びました。
[ § 6 ] まとめ

検知は、判断を置き換えない

アクセスログでも市場でも、AutoEncoder がやることは同じでした。正常を学び、外れたら気づく。 その先で「気づいたあと、どう動くか」を決めるのは、検知器ではなく設計です。 ログなら遮断ルール、資産なら配分ルール。そこの巧拙で、結果は逆転します

この記事のまとめ

  • 平穏期だけで学習した AutoEncoder は、危機で再構成誤差が数十倍に跳ねる。
  • 動的リバランスはDDを41〜49%圧縮・volを47%削減。コスト水準によらず安定。
  • 連続シフトの売買回転は閾値ルールの約3.4倍。この差も不動。
  • 30相場では閾値+ヒステリシスが20勝/22勝で両条件とも最良。数回の観測では見えなかった。
  • コストを 0.1%→0.5% に上げると連続シフトは6勝→0勝に消滅、静的は4勝→8勝に倍増。回転の重さが命取りになる。
  • 少数の観測は構造を隠す。バックテストに必要なのは良い1回ではなく、たくさんの回。
  • 正直なバックテストの4条件:ルックアヘッド回避・検知遅延・取引コスト・後知恵最適化の禁止
  • 利益を決めたのは検知精度ではなく検知したあとの配分ルール

擬似データで構造を確かめたら、次は実データで同じことをやる番です。ただしそのときこそ、 この4つの罠が牙をむきます。正直に組めば、たいてい期待値は下がる。 それでも下がった後に残るものがあるなら、それは本物かもしれません。

『異常を検知したら、逃げるべきか — AutoEncoder × 動的オート・リバランス』を公開しました。