0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Agentic Trading】AIに毎秒SPYを判断させてみた

0
Last updated at Posted at 2026-10-24

「AIに株の売買を任せたらどうなるか」は、多くの人が一度は考えるテーマだと思います。そこで今回、TypeSafe の汎用判断モデル Jev に米国株ETFの SPY を 1秒ごと に判断させ、その判断どおりに 模擬売買(シャドートレード) する仕組みを Python で作って試しました。

3行まとめ

  • 約4時間で 14,285回 判断させ、模擬売買は 11往復。結果は 1勝1分け9敗、−1.68ドル(1株ずつの売買。実際の注文は出していません)。一晩でこれだけ負けたって理解でOKです。
  • 損失の約6割(0.97ドル)は買値と売値の差(スプレッド)。残る値動きの部分も「同じ時間だけ株を持っていた場合」とほぼ同じで、この設定では予測力の証拠は見つかりませんでした
  • 一番の収穫は、AIに数値を渡すときの 書き方 で判断が偏るとわかったこと。7.8e-05 を +0.78 bp と書き直すだけで、下落側への偏りが −12.7 → −1.7ポイント に縮みました

最初にお断り

  • 投資助言ではありません。 特定の銘柄や売買を勧めるものでもありません。
  • 今回の実験は 2セッション分だけ です。1秒モードでは実際の注文を一切出していません。模擬約定は IEX という1つの取引所の気配値で計算しており、実際の売買より有利な結果が出やすい条件です。
  • Jev は相場予測のために作られたモデルではなく、今回はあえて 本来の用途の外 で使っています。「Jev は役に立たない」という話ではなく、「この使い方・この設定では、腕前の証拠が出なかった」 という話として読んでください。

1. 何をやったか

項目 内容
銘柄 SPY(S&P 500 に連動するETF。米国株全体の値動きの代表格)
データ Alpaca の無料リアルタイムデータ(IEX 取引所の気配と約定のみ)
判断 TypeSafe の Jev に、毎秒「この先30〜120秒で上がりそうか、下がりそうか、どちらでもないか」を確率で答えてもらう(2回目の設定。1回目は10〜60秒)
売買 Jev の答えをコードの 固定ルール に通し、模擬約定(その瞬間の気配値で売買したことにして帳簿をつける)
お金 一切動かない。注文も出さない

実際に回したのは次の2セッションです。

1回目(v1) 2回目(v2)
日時(米東部) 2026/10/20 13:00〜15:50 2026/10/23 11:50〜15:50
日時(日本時間) 10/21 2:00〜4:50 10/24 0:50〜4:50
長さ 約169分 約240分
Jev のバージョン 1.13.0 1.14.0
判断回数 10,146回 14,285回
判断の内訳(下落/中立/上昇) 39% / 55% / 6% 40% / 35% / 25%
模擬売買 0回 11往復
模擬損益 0ドル −1.68ドル

1回目は1万回判断して取引ゼロ。そこから原因を調べて作り直したのが2回目です。以下、その経緯を順に書いていきます。

2. この記事に出てくる用語

金融の用語がいくつか出てくるので、先にまとめておきます。

用語 一言でいうと
bp(ベーシスポイント) 0.01%のこと。SPY が約760ドルなら、1bp は約7.6セント
bid / ask bid は買い手が出している最高値、ask は売り手が出している最安値。すぐ買うなら ask、すぐ売るなら bid で約定する
スプレッド ask と bid の差。すぐ買ってすぐ売ると、それだけで毎回この差の分だけ損をする
mid bid と ask のちょうど真ん中の値
IEX 米国に数多くある株式取引所の1つ。無料データでは IEX の分しか見えないため、市場全体のごく一部の気配と約定しか見ていないことになる
シャドートレード 実際には注文を出さず、その瞬間の気配値で売買したことにして帳簿をつける方式。今回の1秒モードはこれのみ
ペーパートレード 証券会社の模擬口座に、本物と同じ形式で注文を出す方式(お金は動かない)。今回は別の1分足ボットで使用
VWAP 出来高で重みをつけた平均約定価格
IC(情報係数) 「シグナルが強いときほど、その後の値動きも大きいか」を順位相関で表した数字。予測どおりならプラス、無関係なら0付近、逆ならマイナス
キャリブレーション 「70%と言ったとき、本当に70%の割合でそうなるか」という確率の正確さ
ミラーテスト 入力の上下を反転させて同じAIに聞き直し、判断に「好み」があるかを調べる方法(6章で説明)
ルックアヘッド 判断の時点ではまだ知り得ない未来のデータが、うっかり入力に混ざること。バックテストの成績が良く見えすぎる典型的な原因

3. 構成:「判断はAI、決定はコード」

全体は4つの層に分かれています。

③のポリシーとリスク管理は、次の固定ルールです。

ルール 内容
買い 上昇の確率 68%以上 かつ 買い場スコア 2以上 かつ 不安定さ 35%以下
売り 下落の確率 68%以上(または不安定・引け前)
最低保有時間 30秒
売買の間隔 10秒以上
売買回数の上限 60回
損失の上限 25ドルで停止
引け 引けの10分前に手仕舞い
資金の上限 1,000ドル(SPY は約760ドルなので、毎回1株)

Jev に聞いていること

Jev は、状態(JSON)と「型の決まった質問」を渡すと、選択肢ごとの確率やスコアを返してくれる汎用の判断モデルです。毎秒、次の4つを聞いています(実際の質問はもちろん英語です。Jevって日本語確か怪しかった記憶)。

質問 答えの形
この状態に一番合う、次の30〜120秒の値動きの方向は? 上昇 / 下落 / 中立 それぞれの確率
今の相場の状態は? 通常 / 高ボラティリティ / レンジ / トレンド
慎重な30〜120秒の戦略にとって、この状態は不安定か? 「はい」の確率
慎重な買いエントリーにとって、この状態はどのくらい好ましいか? 0〜3のスコア(避ける/弱い/普通/好ましい)

方向の質問の原文は次のとおりです。6章の実験で、この文言が効いてきます。

Which next-30-to-120-second price direction is most consistent with this compact state?
Do not infer certain profits.
  up: "Up bias" / down: "Down bias" / neutral: "No directional edge"

Jev に「任せない」こと

ここが設計の肝です。何株買うか、いつ止めるか、何回まで売買するか、損失がいくらになったら止めるか は、すべてコードの固定ルールが決めます。Jev が担当するのは「上がりそうか、下がりそうか」という狭い判断だけで、注文の大きさを決めたり、緊急停止を覆したりすることはできません。

資金の上限を1,000ドルにしているので、売買は毎回1株です。この記事の損益の金額が小さいのはそのためです。

参考にした設計

「状態の計算 → Jev → ポリシー → 執行」という分け方は、Roan さんが公開の場で紹介していた構成[リンク: Roan さんの投稿]を参考にしました。また、Jarrod Watts さんの jev-trader からは、モック(ルールだけの偽AI)と Jev の切り替え、遅れて届いた判断を捨てる仕組み、ドライラン、イベントログといった設計のアイデアをもらっています。

なお、今回のコードは jev-trader のフォークでも移植でもありません。jev-trader は TypeScript/Bun 製で、Monad 上の Kuru の MON-USDC を対象にしたものです。こちらは Alpaca と米国株向けに、Python で独自に書いています。

4. 安全設計:お金が動かない仕組みを先に作る

AIに売買をさせる実験で一番怖いのは、「バグで本物のお金が動くこと」です。そこで、戦略を考えるより先に、次の仕組みを作りました。

お金が動かない

  • 注文先は、Alpaca のペーパー(模擬)口座のURLだけをコードに固定しています。 本番口座へ注文を出す経路は、コードのどこにも存在しません。
  • 既定は観察モード(注文ゼロ)です。 ペーパー注文モードは、明示的なフラグを付け、しかも中身が空の専用ペーパー口座でなければ起動しません。
  • 今回の1秒モードは、さらに厳しくしています。 注文の送信・取り消し・照会という機能自体を持たず、模擬約定だけを行います。

未来のデータを混ぜない(ルックアヘッド対策)

  • 判断時刻より前のタイムスタンプを持つデータだけで状態を作ります。
  • 「今」の時刻も、PCの時計ではなくデータのタイムスタンプから決めます。Mac の時計がずれていても影響を受けません。
  • 模擬約定には、判断が終わった 後 の気配を使います。

遅れたら・壊れたら、売買しない

  • Jev の答えが600ミリ秒以内に届かなければ捨てます。データが2秒以上古ければ判断しません。
  • エラー時は止まる側に倒します(fail closed)。 1分足のペーパー注文モードでは、注文の前に識別子つきの「注文意図」を記録し、結果がはっきりしない注文は再送せずに照会したうえで、そのセッションの新規注文を止めます。

その他

  • APIキーや口座情報はログに出しません。
  • 戦略はセッション中に変えません。変えるのは次に実行する前だけで、同じデータでルールだけの基準と比べてから変えます。

地味な話ですが、実際に役に立った場面もありました。10/20 に別の1分足ボットをペーパー口座で動かしたところ、ネットワークエラーが断続的に発生しました。ボットはエラーの間は判断を止めて待ち、1件も注文を出さないままセッションを終えています。わからないときは何もしない が、設計どおりに働いた例です。

5. 1回目(v1):1万回判断して、取引ゼロ

v1 の入力

最初のバージョンでは、IEX の 気配(bid/ask)を中心に状態を作っていました。mid の1秒/5秒/30秒/60秒のリターン、板の偏り(買い注文と売り注文の量の差)、スプレッド、約定件数などです。数値は生の小数のまま渡し、時刻や株価そのものも状態に含めていました。

結果

  • 判断は10,146回。応答時間は中央値0.15秒、90パーセンタイルで0.20秒で、1秒ごとのループには十分な速さでした。
  • 判断の内訳は 下落39%、中立55%、上昇6%。
  • 「上昇の確率68%以上」は約2時間50分でわずか 3回(「下落68%以上」は1,138回)。買いの条件を一度も満たさず、取引はゼロ でした。

この時間帯の SPY はほぼ横ばい(IEX の mid で −0.04%)です。それなのに、判断の多くが下落寄りでした。この時点で「何かおかしい」と思い始めます。

評価そのものが壊れていた

事後に、判断と10秒後の値動きの関係(IC)を測ると −0.18(95%区間 −0.24〜−0.12)でした。これだけ見ると「Jev の逆をやれば当たる」ように思えます。

ところが、Jev を使わない単純な「直近5秒のリターン」も同じく −0.18、板の偏りも −0.17 でした。原因は IEX の気配のチラつき です。1秒ごとの mid の変化について「前の1秒の変化」と「次の1秒の変化」の相関(1次の自己相関)をとると −0.32。上がったら次は下がる、の繰り返しになっていました。正解ラベル自体が跳ね返るので、直近の動きに沿うシグナルは何でも「逆に当たる」ように見えてしまいます。

実際、正解ラベルを mid ではなく 実際の約定価格 にすると、Jev の10秒IC は +0.08(+0.01〜+0.14)と符号まで変わりました。物差しの選び方で結論が逆になる ほど、1秒単位の IEX データはノイズが大きかったわけです。v1 からは何も結論を出せません。

6. 寄り道:AIの「下落びいき」を測る(ミラーテスト)

方法

横ばいの相場で下落判断が多い。これは相場を読んだ結果なのか、それとも Jev の「好み」なのか。これを切り分けるために使ったのが ミラーテスト です。

  1. 実際に記録した状態を200個取り出す
  2. 方向の意味を持つ入力(リターン、VWAP乖離、売買フローなど)の 符号をすべて反転 させた「鏡像」を作る
  3. 元の状態と鏡像の両方を Jev に聞く

偏りのない判定者なら、元の状態で「上昇70%、下落10%」と答えたものは、鏡像では「上昇10%、下落70%」になるはずです。そこで「上昇確率 − 下落確率」をスコアとし、元のスコアと鏡像のスコアを足して2で割った値 を偏りとしました。好みがなければ0になります。単位は、確率の差を100倍した「ポイント」です。

考え方をコードにすると、次のようになります(実際のコードではなく、説明用に簡略化したものです)。

mirror_test_sketch.py
# 方向の意味を持つ特徴量(符号を反転させる対象)
DIRECTIONAL = {
    "ret_tr_30s", "ret_tr_60s", "ret_tr_300s",
    "vwap_dev_30s_bps", "vwap_dev_300s_bps",
    "flow_60s", "flow_300s",
}

def mirror(state: dict) -> dict:
    """方向の意味を持つ特徴量だけ符号を反転した「鏡像」を作る"""
    return {k: (-v if k in DIRECTIONAL else v) for k, v in state.items()}

def score(probs: dict) -> float:
    """上昇確率 − 下落確率(−1〜+1)"""
    return probs["up"] - probs["down"]

def mirror_bias(judge, states: list[dict]) -> float:
    """偏り(ポイント)。偏りのない判定者なら 0 になる"""
    pair_bias = [(score(judge(s)) + score(judge(mirror(s)))) / 2 for s in states]
    return 100 * sum(pair_bias) / len(pair_bias)

おまけとして、方向の入力をすべて0にした状態も20個聞きました。こちらはどの条件でも 20個すべて「中立」 で、材料がないときに下落を選ぶ「事前の好み」はありませんでした。偏りは、数値が入ったときに生まれていたことになります。

結果:書き方を変えるだけで偏りがほぼ消えた

1回目に保存したデータから、v2 で使う特徴量の一部を計算し直した状態を作り、条件ごとに200ペアずつ測りました。表は、方向の質問に絞って条件を比べたものです。

条件(変えたのは1か所だけ) 偏り(ポイント) 偏りがマイナスだったペアの割合 中立以外を答えた割合
元の設定(生の小数、元の文言) −12.8 97.5% 25%
A:文言から “Do not infer certain profits.” を削除 −9.0 90.5% 35%
B:数値を符号付きbpで表記(+0.78 bp) −1.7 62% 69%
C:選択肢を「高くなりそう/低くなりそう」に変更 −0.8 58% 0.5%
A+B+C をすべて適用 +5.9 18.5% 59%

本番と同じ4問の構成で測っても、生の小数では −12.7、符号付きbpでは −1.7 でした。v1 の気配ベースの状態(生の小数)では −19.2 で、200ペアすべてがマイナスでした。

1回目の記録から10秒おきに取り出した961個の状態を、表記だけ変えて聞き直した結果にも、違いがはっきり出ています。

表記 上昇 中立 下落
生の小数 0 729 232
符号付きbp 304 324 333

生の小数のままでは、961回聞いて 「上昇」が一度も出ませんでした。

なぜ?(仮説)

確かめたわけではありませんが、有力な仮説が1つあります。

Python の float は、絶対値が 1e-4(=1bp)未満になると、repr() や json.dumps() で 2.6e-05 のような指数表記になります。つまり 1bp 未満の小さな正の数は、文字列の中にマイナス記号を含む ことになります。一方、負の数は -0.0001366 のように先頭にマイナスが付きます。結果として、生の小数では正でも負でも、ほとんどの数値に「-」が含まれていた わけです。

実際の v1 の入力の一部はこうでした。

ret_1s: 2.6e-05    ret_5s: -0.0001366    ret_30s: -0.0002277    ret_60s: -0.0002537

符号付きbpにすると、こうなります。

ret_1s: +0.26 bp   ret_5s: -1.37 bp      ret_30s: -2.28 bp      ret_60s: -2.54 bp

変換はこれだけです(こちらも説明用の簡略版です)。

signed_bp.py
def to_signed_bp(x: float) -> str:
    """リターン(小数)を符号付きbpの文字列にする。例: 7.8e-05 -> '+0.78 bp'"""
    return f"{x * 1e4:+.2f} bp"

落とし穴:C案は「偏りが消えた」のではなく「答えなくなった」

C案の偏りは −0.8 で、一見いちばん良い結果です。ところが、中立以外を答えた割合はわずか0.5%でした。ほぼ常に「どちらでもない」と答える ようになっただけで、判定者としての役目を果たしていません。偏りだけを見ていると、この罠にはまります。

また、A・B・C をすべて入れると、今度は 上昇側に +5.9 と行き過ぎました。変更は1つずつ試すべき、という教訓です。本番には A・B・C のうち B(符号付きbp)だけを採用しました。2回目のセッション当日の状態で測り直した偏りは −2.8(95%区間 −3.3〜−2.3)です。ゼロではありませんが、−12.7 と比べればかなり小さくなっています。

万能薬ではない

同じ処方を1分足ボットの状態(株価そのものや移動平均の水準を含むもの)に試したところ(77ペア)、偏りは生の小数で −15.2、符号付きbpで −15.4 と、まったく改善しませんでした。株価の水準を消して相対値だけにすると −10.8 まで減りましたが、それでも残っています。状態の設計が違えば効く処方も違う ので、毎回測るしかありません。

AIに数値を渡すすべての人へ

この章の教訓は、相場に限らず、LLM や判断モデルに数値を渡すあらゆる場面に当てはまると思います(別記事で詳しく書く予定です)。

  1. ミラーテストで偏りを測る。 200ペアなら、ゼロ状態20件を足しても420回の呼び出しで済む、安い健康診断です。
  2. 符号を明示し、単位をそろえる。 指数表記の小数をそのまま渡さない。
  3. 偏りと同時に「ちゃんと答えているか」も見る。 中立ばかりになっていないか(C案の罠)。
  4. 変更は1つずつ。 まとめて変えると、逆方向に行き過ぎることがある。
  5. 状態の設計を変えたら測り直す。 前に効いた処方が次も効くとは限らない。

7. v1 → v2 で変えたこと

項目 v1 v2
状態の材料 IEX の 気配(mid、板の偏り) 実際の 約定(約定価格のリターン30秒/1分/5分、VWAP乖離、売買フロー、約定件数、5分の値幅)+スプレッド
Jev に見せる数値 生の小数(2.6e-05) 符号付きbp(+0.26 bp)
時刻・株価そのもの 含む 含まない(日付や価格水準から何かを「思い出す」余地をなくす)
質問の時間軸 次の10〜60秒 次の30〜120秒
記録 判断のログ 生のストリームも保存し、あとから完全に再生できる
Jev のバージョン 1.13.0 1.14.0(※比較を難しくする要因。12章参照)

「売買フロー」は、各約定が買い手主導(ask で約定)か売り手主導(bid で約定)かを分類し、出来高で重み付けした −1〜+1 の値です。+1 に近いほど、買い手が積極的に買っている状態を表します。

2回目のセッションで最初に「買い」になった瞬間、Jev にはこう見えていました。方向の意味を持つ値には符号を付け、値幅やスプレッドのように向きのない値はそのままにしています。

ret_tr_30s: +0.66 bp      ret_tr_60s: +2.62 bp     ret_tr_300s: +4.98 bp
vwap_dev_30s_bps: -0.13 bp   vwap_dev_300s_bps: +3.16 bp
flow_60s: +0.889   flow_300s: +0.044
trades_30s: 10   trades_60s: 18   trades_300s: 117   volume_30s: 223
range_300s_bps: 6.95   trade_age_s: 3.0   spread_bps: 1.048

Jev の答えは「上昇79%、中立17%、下落4%」、相場の状態は「トレンド」、買い場スコアは2.0、不安定さは26%。すべての条件を満たしたので、模擬で1株買いました(この取引が次章の表の #1 で、結果は −0.27ドルでした)。

8. 2回目(v2):4時間・1.4万回の判断と11往復の模擬売買

実行の数字

項目 値
時間 米東部 10/23 11:50〜15:50(日本時間 10/24 0:50〜4:50)、約240分
Jev の判断 14,285回
応答時間 中央値0.15秒、90パーセンタイル0.20秒、99パーセンタイル0.36秒
締め切り超過などで使えなかった判断 85回(約0.6%)
判断の内訳 下落40%、中立35%、上昇25%
強い判断(確率68%以上) 下落1,757回、上昇979回
受信データ 気配の更新 約100万件、約定 7,494件(約0.5件/秒)
IEX のスプレッド 中央値0.66bp(約5セント)、90パーセンタイル2.49bp
IEX の mid の自己相関(1次) −0.35(v1 と同じく、mid のチラつきは残っていた)
SPY の動き(IEX の mid) 762.61 → 760.26ドル(−0.31%)

全取引

時刻は米東部時間です(日本時間は+13時間)。買いは ask、売りは bid での模擬約定です。「mid基準」は、スプレッドを払わずに mid で売買できたと仮定した場合の損益です。

# 買い 売り 保有 買値 売値 損益 mid基準 売った理由
1 11:57:38 12:01:28 3.8分 763.02 762.75 −0.27 −0.235 Jev が下落判断
2 12:30:37 12:39:22 8.7分 761.79 761.91 +0.12 +0.165 Jev が下落判断
3 12:45:51 12:48:35 2.7分 762.63 762.43 −0.20 −0.105 Jev が下落判断
4 13:30:11 13:35:19 5.1分 760.39 760.36 −0.03 +0.075 Jev が下落判断
5 13:48:06 13:52:28 4.4分 760.99 760.79 −0.20 +0.005 Jev が下落判断
6 13:57:56 14:00:44 2.8分 760.94 760.79 −0.15 +0.040 Jev が下落判断
7 14:15:45 14:28:25 12.7分 760.87 760.78 −0.09 −0.005 Jev が下落判断
8 14:53:00 14:59:21 6.3分 760.72 760.28 −0.44 −0.400 Jev が下落判断
9 15:02:56 15:11:07 8.2分 760.65 760.65 ±0.00 +0.050 Jev が下落判断
10 15:29:58 15:31:30 1.5分 760.52 760.32 −0.20 −0.145 Jev が下落判断
11 15:45:02 15:50:00 5.0分 760.29 760.07 −0.22 −0.160 引け前の手仕舞い
計 61.3分 −1.68 −0.715

(単位:ドル、すべて1株)

  • 1勝1分け9敗、−1.68ドル。
  • mid基準では −0.715ドル。差額の 0.965ドル(損失の約57%)がスプレッドの負担 です。1往復あたりに直すと、損益は −2.0bp、そのうちスプレッドが約1.15bpです。
  • mid基準ではプラスが5回ありましたが、スプレッドを払うと、そのうち4回がマイナスか±0になりました(#4・#5・#6・#9)。
  • 平均保有時間は5.6分。株を持っていた時間は、セッション全体の約26%でした。

9. 同じデータで比べる

1回の損益だけでは、良いのか悪いのか判断できません。そこで、同じ日の同じデータで基準と比べました。

方式 往復 損益(スプレッド込み) mid基準 保有時間の割合
Jev(実際のシャドー運用) 11 −1.68 −0.715 26%
Jev(記録から再生したオフライン再現) 11 −1.56 −0.94 26%
ルールだけの基準(60秒の約定モメンタム+売買フロー、Jev なし) 30 −7.31 −2.38 5%
1株を最初に買って最後まで保有 1 −2.42 −2.355 100%
保有時間を Jev にそろえた買い持ち(概算) — 約−0.62 約−0.60 26%

(単位:ドル、すべて1株)

  • ルールだけの基準 は30往復もして、スプレッドで大きく負けました。Jev 版は売買回数が少なかった分、損失も小さく済んでいます。
  • 買い持ち はこの日 −2.42ドル。SPY が −0.31% 下げた日なので、買いしかしない戦略にはもともと逆風でした。
  • 最後の行は、買い持ちの損益を Jev の保有時間の割合(26%)で単純に割り引いた概算です。下げた日に26%の時間だけ1株持っていれば、それだけで約0.6ドルは負ける計算 になります。Jev の mid基準の損益(−0.715ドル)はこれとほぼ同じで、値動きを当てて稼いだ形跡も、大きく外した形跡もありません。
  • オフライン再現(−1.56ドル)が実際の運用(−1.68ドル)とほぼ一致したのは、仕組みの検証として良いニュースです。差は、約定に使った気配が数ミリ秒ずれていることによるものです。

10. Jev は何を見ていたか

判断は「直近1分の値動き」とほぼ連動していた

Jev のスコア(上昇確率 − 下落確率)と、入力した各特徴量との順位相関を測りました。

特徴量 Jev スコアとの相関
直近60秒の約定価格リターン +0.81
5分VWAPからの乖離 +0.79
直近30秒のリターン +0.67
直近5分のリターン +0.58
売買フロー(60秒/5分) +0.33 / +0.34
スプレッド ±0.00

つまり Jev は、「最近上がっていれば上昇、下がっていれば下落」 という素直なモメンタム(勢い)型の判断をしていました。判断として不自然なものではありません。

ところが、この日の相場は逆向きだった

判断とその後の値動きとの IC は、次のとおりです(重ならない区間ごとに数えたサンプル。[ ]は95%区間)。

何秒後を予測 IC(約定価格で評価) IC(IEX の mid で評価)
10秒 −0.10[−0.16, −0.05] −0.05[−0.11, +0.00]
30秒 −0.14[−0.23, −0.05] −0.10[−0.19, −0.01]
60秒 −0.14[−0.27, −0.01] −0.08[−0.21, +0.05]
120秒 −0.20[−0.37, −0.02] −0.17[−0.34, +0.01]

どの時間軸でもマイナスで、セッションを前半と後半に分けても同じ傾向でした。Jev を使わない単純な「60秒モメンタム」も、約定価格で評価すると −0.07〜−0.12 とマイナスです。反対に「30秒VWAPより下なら買い(平均回帰)」は、30〜120秒で +0.07〜+0.12 とプラス寄りでした(ただし多くは区間が0をまたいでいます)。

この日は「上がったら戻る」平均回帰寄りの相場で、勢いに乗る判断とは相性が悪かった というのが、一番素直な解釈です。とはいえ1日分のデータでは、Jev の性質なのか、その日の相場の性質なのかは区別できません。

jev_IC.png

確率は「当たる確率」ではない

Jev が「上がりそう」と言ったとき、実際にはどうだったのか。上昇と下落の確率だけで比べた値(上昇 ÷(上昇+下落))を5段階に分け、10秒後に mid が上がっていた割合を見ました。

Jev の上昇確率(上昇 ÷(上昇+下落)、各段階の平均) 実際に上がった割合 件数
11% 53% 350
29% 54% 300
50% 47% 211
70% 47% 229
90% 47% 215

90%と言ったときも、実際に上がったのは半分以下でした。予測の良し悪しを測るブライアスコア(小さいほど良い)は0.35で、毎回「50%」と答え続けた場合の0.25より悪い結果です。Jev の確率をそのまま売買の自信度として使うのは危険で、使うなら別の日のデータで較正(キャリブレーション)する必要があります。

jev_キャリブレーション.png

「じゃあ逆をやれば?」も勝てない

IC がマイナスなら、Jev の逆を張れば勝てるのでしょうか。「Jev が下落68%以上と言ったら買う」というルールも検証しましたが、スプレッドを払うと30秒で1取引あたり −0.78bp で、どの時間軸でもマイナスでした。実は、試したルールは(Jev に従うもの、逆を張るもの、Jev を使わない単純なものも含めて)すべて、スプレッドを払うとマイナス でした。1秒〜2分の時間軸で約1bpのスプレッドを毎回超えるのは、それだけ難しいということです。

11. なぜ負けたのか

  1. スプレッド負け。 損失1.68ドルのうち0.97ドルは、買値と売値の差を払った分です。数分で売り買いする設計では、値動きの予測が当たっても、このコストを超えるのは簡単ではありません。
  2. 下げた日に、買いだけで勝負した。 SPY は −0.31%。持っている時間が長いほど不利な日でした。
  3. 判断の向きと相場の性質が逆だった。 Jev は勢いに乗る判断をしましたが、この日は平均回帰寄りでした。
  4. 確率が過信気味だった。 「90%」が実際には47%。確率のしきい値(68%)に、見た目ほどの意味はありませんでした。
  5. そもそもサンプルが小さい。 11往復は運の範囲に収まる数です。良くも悪くも、ここから実力は語れません。

12. 反省点

  1. データのノイズを先に調べなかった。 v1 で IEX の mid を1秒単位で使い、チラつき(自己相関 −0.32)に振り回されました。セッションの前に、記録データで自己相関を見ておくべきでした。
  2. AIの偏りを本番前に検査しなかった。 v1 は「下落びいきのAI」のまま約3時間回していました。ミラーテストは数百回の呼び出しで済むので、本番前の必須チェックにすべきでした。
  3. 評価の物差しを事前に決めていなかった。 mid と約定価格のどちらで評価するかで、IC の符号まで変わりました。
  4. v1 と v2 の間で、Jev のバージョンも変わっていた(1.13.0 → 1.14.0)。 そのため、v1 と v2 の違いをすべて設計変更の効果とは言えません。
  5. 模擬約定は楽観的。 注文の順番待ち(キュー)、通信の遅れ、市場全体の価格を考慮していません。実際に売買していれば、結果はもっと悪かった可能性があります。
  6. 2セッションだけ、しかも同じデータでの事後診断。 9〜10章の比較はすべてインサンプル(答えを知っているデータでの検証)です。
  7. 1分足ボットは、ネットワークエラーで実験として不完全に終わった。 安全側に止まったのは良かったものの、データとしてはほとんど使えません。

13. 収穫

  1. 安全に回る土台ができた。 2回のセッションで実際の注文はゼロ。遅れた判断は捨て(約0.6%)、エラー時には止まる。この土台があったので、安心して中身の検証に集中できました。
  2. Jev は十分に速い。 応答は中央値0.15秒、90パーセンタイル0.20秒。1秒ループの判断役として、工学的には問題なく使えます。
  3. ミラーテストという安い検査方法を手に入れた。 数値の書き方ひとつで −12.7 → −1.7。この知見は相場以外にも使えます。
  4. 損益を分解して「どこで負けたか」を言えるようになった。 スプレッド・値動き・保有時間に分けると、「予測が外れた」ではなく「コストと相場環境で負けた。予測力の証拠はない」と正確に言えます。
  5. 再現できる。 生データを保存しておいたので、オフラインでほぼ同じ結果(−1.56ドル対−1.68ドル)を再現できました。次の改善も、同じデータで公平に比べられます。
  6. 汎用AIの「性格」が見えた。 書式に敏感で、素直に勢いに乗るタイプ。こういう性質は、実際に動かして測ってみないとわかりません。

14. 次にやること

研究として、次の順で進める予定です(売買の推奨ではありません)。

  • 同じ設定を固定したまま、セッションを重ねる。 しきい値などは事前に決めて記録し、途中で動かさない。
  • Jev のバージョンが変わるたびにミラーテストを実行する。 偏りと「答えているか」の両方を測る健康診断として。
  • 約定モデルを現実に近づける。 注文の順番待ちや通信遅延を考慮し、可能なら IEX だけでなく市場全体(SIP)のデータで検証する。
  • スプレッドに対して値動きが十分大きい時間軸を検証する。 秒単位にこだわらず、取引回数を減らす設計も含めて比べる。
  • 確率を較正する。 別の日のデータで、「Jev の70%」が実際には何%に当たるかを測る。
  • 1分足ペーパーモードで、約定通知と注文の突き合わせを検証する。
  • Jev が本来得意そうなタスクを試す。 数値の羅列ではなく、ニュースや適時開示の文章を読んで判断させる実験を予定しています。

おわりに

AIに毎秒相場を判断させると聞くと、派手な結果を期待したくなります。けれども、実際に手元に残ったのは「スプレッドは重い」「数値の書き方でAIは偏る」「1日の結果からは何も言えない」という、地味で確かな学びでした。個人的には、それで十分に面白い実験だったと思っています。

免責事項

  • 本記事は個人の技術実験の記録であり、投資助言ではありません。特定の金融商品の売買を勧めるものではありません。
  • 1秒モードは実際の注文を一切出さないシャドートレードで、損益は IEX の気配値にもとづく模擬計算です。手数料、注文の順番待ち、通信遅延、市場全体の価格は考慮していません。
  • 結果は2セッション分だけのもので、統計的には「仮説を立てる段階」です。将来の成績を示すものではありません。
  • Jev は汎用の判断モデルであり、今回は本来の用途の外で使っています。結果は Jev 1.13.0 / 1.14.0 と、この記事に書いた設定でのものです。

お疲れ様でした。

わからないところ、間違っているところ、もっといい方法がある場合は、コメントでもDMでも教えてください。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?