AIエージェントで作る銘柄評価基盤④—検証と継続改善

AIエージェントを業務パイプラインへ組み込むと、テストの考え方が変わります。通常のコードは同じ入力に同じ出力を期待できますが、AIの文章や採点案には揺れがあります。外部検索の結果も変わり、APIやCLIが一時的に失敗し、将来のモデル変更で採点水準が動くこともあります。
だからといって、「AIだからテストできない」と諦める必要はありません。AIの自由度を狭い境界へ閉じ込め、境界の前後を決定論的にすれば、多くの部分を通常のソフトウェアと同じように検証できます。
このプロジェクトでは、次の4層で品質を確認しています。
1. 純粋な計算とデータ変換を単体テストする
2. 利用者が期待する振る舞いを日本語のBDDシナリオで固定する
3. 合成データだけで日次パイプラインを端から端まで通す
4. 新しい指標や重みを本番へ影響させず、検証運転・shadow運転で事後比較する
この記事では、AI評価の検証、失敗注入、データリーク防止、オンライン重み学習、バックアップ、段階導入の仕組みを紹介します。個別銘柄、実スコア、実行ID、ローカルパスは掲載しません。
> 本記事はソフトウェア検証とAIエージェント運用の解説です。事後成績の測定は、将来の利益を保証するものではありません。統計的な優位性の判断には十分な期間と独立標本が必要です。
AIのどこをテストし、どこを検証するか
AIの自然言語出力を一字一句固定するテストは、実用的ではありません。代わりに、システムが必要とする契約を固定します。
| 対象 | 固定するもの | 固定しないもの |
|—|—|—|
| 評価入力 | 必須データ、対象日、前回評価、プロンプト版 | 検索で見つかる文章の順序 |
| AI出力 | JSON構造、カテゴリ、型、点数範囲 | 要約の完全一致 |
| 最終点 | 合計式、リスク減点、グレード境界 | AIが各カテゴリへ付ける妥当な範囲内の点 |
| 保存 | 銘柄単位のコミット、評価日、モデル、版 | 処理時間の完全一致 |
| 失敗 | 再試行回数、連続失敗時の打ち切り | 外部サービスのエラー文面 |
AIが返す文章そのものではなく、「構造化された評価案を返し、コードが契約を検証できること」をテストします。評価内容の妥当性は、根拠確認、複数例のキャリブレーション、後日の成績比較で検証します。
“`mermaid
flowchart LR
I[固定された評価パッケージ] –> M[AIバックエンド]
M –> J{JSON契約を満たすか}
J — いいえ –> R[修正要求を付けて再試行]
R –> M
J — はい –> C[コードで合計・グレード]
C –> S[版情報とともに保存]
S –> O[後日の事後評価]
“`
テストピラミッド
実装には、単体、振る舞い、統合、E2Eのテストがあります。外部APIや実AIを使わず、短時間で再現できるテストを土台にします。
| 層 | 主な対象 | 外部通信 | 目的 |
|—|—|—:|—|
| 単体テスト | 計算式、変換、検証、選定、保存 | なし | 小さな規則を高速に確認 |
| BDD | 評価、Tier、冪等性、通知などの利用者要件 | なし | 仕様を日本語で共有 |
| 統合テスト | SQLiteと複数モジュールの連携 | なし | 境界の受け渡しを確認 |
| E2Eスモーク | 特徴量→選定→Tier→レポート | なし | 日次の主経路を通す |
| 本番検証 | API・AI・通知・実行時間 | あり | 接続と運用状態を確認 |
外部通信を含むテストは、通信状態、費用、提供データで結果が変わります。日常の回帰テストから分け、通常のテストは合成データと偽バックエンドで完結させます。
単体テストで固定する計算
定量評価では、次のような小さな規則を個別に確認します。
- 中央値とMADによるロバストzスコア
- 高いほど良い指標と低いほど良い指標の符号
- z値のクリップと0〜100への写像
- 市場×業種で正規化すること
- ハードフィルターの境界値
- 欠損ピラーを中立50で補うこと
- ハードフィルター不通過へpercentileを付けないこと
- 調整後価格からリターンとドローダウンを計算すること
- 財務の累計値を単独四半期へ直し、直近4四半期をTTMにすること
たとえば、売買代金が下限をわずかに下回る合成銘柄を作り、`hard_pass`が0になることを確認します。PBRが同じ業種で最も低い合成銘柄は、割安度スコアが業種内で最も高くなることを確認します。
ここでは実在企業の履歴をテストデータに使いません。架空コードと生成した価格系列を使うため、テストを公開しても個別評価が漏れません。
AI評価は偽バックエンドで失敗を注入する
AI評価のテストでは、実際のモデルを呼びません。順番に決められた応答や例外を返す偽バックエンドを使います。
代表的なシナリオは次の通りです。
- APIキーやCLIが利用できない時は、評価だけをスキップする
- 2件目だけ例外になっても、1件目と3件目の成功を保存する
- 一度目に範囲外スコアを返し、二度目に正常JSONを返したら回復する
- 当日評価済みの対象はAIを再呼び出ししない
- 連続失敗が上限に達したら、その日のAI評価を打ち切る
- JSONの必須項目が欠けていたら保存しない
- カテゴリ点の型が文字列なら拒否する
- リスク点が許容範囲外なら拒否する
この方法なら、認証情報や費用を使わず、異常系を毎回同じ条件で再現できます。外部サービスが実際に何回失敗するかを待つ必要もありません。
BDDで「仕様の意味」を残す
テストコードだけでは、なぜその条件が必要なのかが読み取りにくくなります。このプロジェクトでは、日本語のGherkin形式で主要な振る舞いを記述しています。
記事用に匿名化した例です。
“`gherkin
機能: エージェント深掘り評価
固定ルーブリックで事業の質を採点し、価格水準は別の定量層が担当する
シナリオ: 範囲外スコアは拒否され再試行で回復する
前提 評価エージェントは1回目に範囲外の点を返す
かつ 2回目に正常な構造化出力を返す
もし 1件の評価を実行する
ならば 評価は成功する
かつ 再試行回数が保存される
“`
BDDでは、次の機能を扱っています。
- 定量スコアリングとハードフィルター
- AI評価とルーブリック検証
- 統合スコアとTier更新
- イベント、発掘、期限超過、敗者復活のローテーション
- 同一営業日の冪等性
- 通知の成功・片側失敗・未設定
- オントロジー関係と波及イベント
- 一目均衡表の補助シグナル
- 重み学習の成果確定とshadow比較
- バックアップと世代管理
日本語で仕様を残すと、「テストが通る」だけでなく、AIエージェントへ何を許し、どの境界をコードが守るかを説明できます。
E2Eは合成データで数日回す
E2Eスモークテストでは、架空の銘柄集合、130営業日分の価格、財務データを作り、次の流れを通します。
“`text
合成データ投入
→ 特徴量計算
→ 定量スコア
→ イベント検知
→ 当日枠の選定
→ Tier更新
→ Markdown・HTMLレポート
→ 翌日の選定
→ 数日後に全件が一周することを確認
“`
一部の架空銘柄だけ流動性を低くし、全件通過でも全件不通過でもない状態を作ります。初日の選定件数、翌日との重複の少なさ、数日後の一周、レポートファイルの生成を確認します。
このE2Eは外部データAPIも実AIも使いません。目的は、現実の評価が当たることではなく、モジュール間の列名、日付、保存、枠数、状態遷移が壊れていないことを確認することです。
冪等性をテストする
定期ジョブでは、同じ営業日を二度処理しないことが重要です。BDDと単体テストで、次の3ケースを確認します。
1. 処理済みの営業日はスキップし、スキップ通知を送る
2. 未処理の営業日は通常どおり進み、スキップ通知を送らない
3. 明示的な強制実行ではガードを通過する
さらに、学習処理では、成熟した過去日を一度消化したら、同じ日を強制再実行しても二重に重み更新しないことを確認します。日次処理の冪等性と、学習イベントの冪等性は別々に必要です。
プロンプトをバージョン管理する
AI評価では、モデル名だけでなくプロンプト版を保存します。ルーブリック、採点水準、出力形式、市場別キャリブレーションを変えたら版を上げます。
評価履歴には次の情報を持たせます。
- 評価日と有効期限
- 対象市場
- AIバックエンドとモデル識別情報
- プロンプト版
- カテゴリ別点数とリスク減点
- 事業スコアとグレード
- 要約、カタリスト、リスク
- 確信度と再試行回数
これにより、プロンプト変更前後の点を無条件に同一視せず、どの条件で作られた評価か追跡できます。市場追加時も、既存市場の文面を不必要に変えず、比較可能性を守る設計にしています。
新機能は「表示専用」から始める
一目均衡表シグナルを追加した時は、最初からランキングへ組み込みませんでした。まず計算して保存し、専用の検証レポートへ次を出します。
- 必要な履歴が揃っている割合
- 日足・週足・月足の値域
- 前日からの遷移整合性
- 変化イベントの件数
- 匿名化した代表ケースの手計算照合
設定フラグが無効な間は、通常レポートや通知へ表示しません。数営業日の検証で問題がなければ表示だけを有効にします。スコアへ加えるかどうかは、さらに事後成績を見て判断します。
“`mermaid
flowchart LR
A[実装] –> B[単体・BDD・E2E]
B –> C[保存のみ]
C –> D[検証レポート]
D –> E[通常画面へ表示]
E –> F[shadow比較]
F –> G{十分な期間で改善したか}
G — いいえ –> H[既定動作を維持]
G — はい –> I[明示フラグで本番適用]
“`
段階を分けると、「計算できた」と「役に立つ」を混同しません。
重み学習も本番と並走させる
統合スコアの初期重みは、事業60%、割安度25%、モメンタム15%です。しかし、この配分が将来も最適とは限りません。そこで、事後リターンを使って重みを少しずつ更新するオンライン学習を追加しています。
重要なのは、学習重みをすぐ本番へ使わないことです。
- `total_fixed`:現行の固定重みで計算したスコア
- `total_learned`:その時点の学習重みで計算したshadowスコア
同じ銘柄、同じ成分スコアを二つの重みで並走記録します。初期状態では学習重みも固定重みと同じなので、両者は一致します。差が出た日から、学習が実際に動いたと分かります。
本番スコアへ学習重みを使うには、全体機能フラグとは別の適用フラグが必要です。さらに、一定回数のwarmupを終えて状態がactiveでなければ、適用フラグを有効にしてもコード側が固定重みへ戻します。
事後成績は将来日が来てから確定する
今日のスコアを評価するには、将来の価格が必要です。未来の情報を今日の計算へ混ぜないよう、5、20、60営業日後の価格が実際に存在するようになってから、別テーブルへ事後成績を確定します。
“`text
評価日 t のスコアを保存
↓ まだ20営業日後ではない
事後成績の行を作らない
↓ 20営業日後の価格が確定
銘柄リターンと同業中央値を計算
↓
業種相対超過リターンを確定保存
“`
報酬には、生の株価リターンではなく、同じ市場・業種の中央値との差を使います。
“`text
excess_return
= 銘柄の期間リターン
− 同じ市場・業種の期間リターン中央値
“`
相場全体や業種全体が上がっただけで「スコアが当たった」と誤認するのを減らし、銘柄選別の相対的な成績を見ます。業種内の標本が不足する場合は、市場中央値、全体中央値の順にフォールバックします。
将来価格が欠ける場合は`missing_price`として記録し、学習母集団から外します。未成熟の日付には行自体を作らないため、学習処理が誤って参照できない構造です。
比較指標は順位相関と上位成績
固定重みと学習重みは、次の指標で比較します。
| 指標 | 見ていること |
|—|—|
| 成分別IC | 事業、割安度、モメンタムの順位と事後超過リターン順位の一致 |
| 固定IC | 固定統合スコア順位の予測力 |
| 学習IC | 学習統合スコア順位の予測力 |
| 上位N件の平均超過リターン | 上位候補が同業中央値を上回った幅 |
| 上位N件のヒット率 | 超過リターンが正だった割合 |
| 重み軌跡 | 学習がどの成分を増減したか |
ICにはSpearman順位相関を使います。スコアの点差そのものではなく、並び順と将来の並び順が一致したかを見ます。定数列など相関が定義できない場合は中立の0として扱います。
一日だけのICやヒット率で判断しません。20営業日ホライズンは隣接する評価日の期間が大きく重なるため、営業日数ほど独立標本は増えません。数週間の差より、数か月以上の累積と安定性を見ます。
重み更新は意図的に遅くする
学習アルゴリズムは、事業、割安度、モメンタムの3人の専門家へ発言権を配るように考えます。ある成熟日に順位相関が良かった成分の重みを少し増やし、悪かった成分を少し減らします。
概念式は次の通りです。
“`text
w_k ← w_k × exp(学習率 × 成分kのIC)
w ← 合計1になるよう正規化
w ← 固定割合だけ均等配分を混ぜる
w_k ← 下限を割らないよう再配分
“`
均等配分を少し混ぜるFixed-Shareは、ある成分が長期間不調でも重みを完全に失わず、相場環境が変わった時に復帰できるようにします。各成分には下限を設け、小さな日次ノイズで重みが急変しない学習率を使います。
また、成熟日の対象件数が少なすぎる場合は、その日のICを学習に使いません。更新済みの最終成熟日を保存し、再実行で同じ報酬を二度消化しないようにします。
本番適用のゲート
学習重みを本番へ使う前に、少なくとも次を確認します。
- warmupを終えている
- 一定期間の成熟データがある
- 学習ICが固定ICを一時的ではなく累積で上回る
- 上位候補の超過リターンとヒット率でも悪化していない
- 重みが下限や一成分へ不自然に張り付いていない
- プロンプト版や市場構成の変更による断層を確認した
- 欠損率と対象件数が安定している
- ダッシュボード、レポート、監査ログが一致する
- ロールバック方法を確認した
条件を満たしても、設定フラグを明示的に変更するまで固定重みを使い続けます。適用後に問題があれば、フラグを戻すだけで固定重みへ復帰できます。
テーマ・競合グラフも補助線として扱う
AI評価後の公開情報から、テーマや競合関係を抽出する補助処理があります。高評価企業の競合として見つかった未評価候補を発掘枠へ回したり、決算イベントを関係先へ波及させたりできます。
ただし、関係抽出は誤りを含む可能性があります。そのため次の制限を置きます。
- 関係の確信度を保存する
- 低確信度を自動の発掘・波及へ使わない
- 名寄せできた関係だけを自動処理に使う
- 直接イベントを波及イベントより優先する
- 一日の波及件数に上限を置く
- 関連評価スコアは表示専用とし、統合スコアへ入れない
AIが作った知識グラフを、すぐ本番の点数へ直結させないことが重要です。まず候補発見と説明の補助へ使い、誤差が主順位を汚染しないようにします。
監査可能な保存設計
検証には、今日の結果だけでなく「その時点で何を知っていたか」が必要です。主な保存先を役割で分けています。
| 保存対象 | 役割 |
|—|—|
| 日次価格・財務 | 特徴量の再計算 |
| 定量日次履歴 | 当時のスコアと順位の再現 |
| AI評価履歴 | プロンプト版、カテゴリ点、要約、確信度 |
| 日次選定 | その日に何を、なぜ選んだか |
| Tier状態 | 前回評価、次回期限、遷移候補 |
| shadowスコア | 固定重みと学習重みの並走 |
| 事後成績 | 成熟後の超過リターン |
| 学習状態 | 重み、消化日、報酬、warmup状態 |
| レポート | 人が読んだ日次成果 |
短期参照はSQLite、長期の定量履歴はParquetへ分けます。AI評価や関係グラフのように再取得コストが高いテーブルは、小さなSQLiteダンプとして日次バックアップし、世代数を制限して保存します。価格や財務は外部から再取得可能ですが、過去時点のAI評価や人が確認した関係は同じ形で再現できないため、保護優先度を上げます。
変更を本番へ入れる標準手順
このプロジェクトから得た、AIエージェント機能を段階導入する手順は次の通りです。
1. 期待する振る舞いをBDDへ書く
2. 計算と境界条件の単体テストを作る
3. 外部通信を偽物へ置き換え、異常系を注入する
4. 合成データでE2Eを通す
5. 機能フラグを既定無効または表示専用にする
6. 本番データで保存だけ行い、検証レポートを見る
7. shadowで現行方式と同じ対象を並走比較する
8. 十分な成熟期間と標本数を待つ
9. 適用条件、停止条件、ロールバックを決める
10. 明示フラグで本番適用し、監視を続ける
この順番の利点は、新機能を実装した人の期待ではなく、現行方式との事後比較で判断できることです。
公開前の品質・プライバシーチェック
- [ ] テストデータは架空コード・合成価格・架空財務である
- [ ] 日次レポートの実銘柄名、実順位、実要約を掲載していない
- [ ] ダッシュボード画像に個別銘柄、日付、件数の実績が残っていない
- [ ] ジョブID、実行ID、PID、チャンネルIDを隠した
- [ ] 絶対パスを`/path/to/project`へ置き換えた
- [ ] APIキー、Webhook、環境変数値を検索した
- [ ] AIの検索結果に未公開資料や個人メモが含まれていない
- [ ] スコアを投資推奨として読める表現にしていない
- [ ] 将来計画と現在稼働中の機能を分けた
- [ ] 画像メタデータと周辺UIも確認した
現時点の制約
この検証設計にも限界があります。
- 事後リターンは市場環境に依存し、短期間では統計的に不安定
- 20営業日リターンは隣接日の期間が重なり、独立標本が少ない
- 業種内企業数が少ない場合、正規化と同業中央値が不安定
- AIのWeb検索結果は時点により変わり、完全再現できない
- データ提供時刻の境界で速報と確報が異なる可能性がある
- 気配値がないため、流動性は代理指標を含む
- 評価スコアは因果推論ではなく候補順位の補助
- バックテストが良くても、将来の運用成績を保証しない
制約を消すのではなく、レポートと設計書へ残し、判断に使える範囲を明確にします。
まとめ
AIエージェントを含むシステムでも、テスト可能な部分は多くあります。入力パッケージ、JSON契約、点数範囲、合計式、保存、再試行、打ち切り、冪等性をコードで固定すれば、AIの揺れを狭い境界へ閉じ込められます。
新しい指標や学習重みは、実装後すぐ本番へ入れません。保存のみ、検証レポート、表示専用、shadow比較、warmup、明示適用という段階を踏みます。未来の価格が実際に確定してから事後成績を作り、固定方式と同じ対象で比較します。
AIエージェント活用で大切なのは、最も賢い回答を一度得ることではありません。失敗を再現でき、変更の影響を測れ、昨日の判断条件を追跡でき、必要なら安全に元へ戻せることです。この仕組みがあって初めて、AIを継続的な業務部品として育てられます。
実装確認に使った主なファイル
- `tests/unit/`
- `tests/features/`
- `tests/e2e/test_smoke.py`
- `stock_brand_eval_agent/evaluator/agent.py`
- `stock_brand_eval_agent/evaluator/rubric.py`
- `stock_brand_eval_agent/learning/outcomes.py`
- `stock_brand_eval_agent/learning/policy.py`
- `stock_brand_eval_agent/learning/shadow.py`
- `stock_brand_eval_agent/storage/backup.py`
- `stock_brand_eval_agent/screener/ichimoku_verify.py`
- `dashboard/data.py`
- `dashboard/app.py`
