Agentic Trading(LLM Tradingとも呼ばれたりしてるね)におけるLLMエージェントの判断を研究するため、virattt/ai-hedge-fund を日本株で動かしてみました。これは投資家エージェントを含む教育・研究用の仮想ファンドのようで、OSSなので誰でもできるはず。ただ、米国株を対象に作られているので、米国株向けのデータ層をJ-Quants API V2の無料プランに差し替え、日本株の100株単位で動くフォークを作りました。
この記事では、実装で必要だった時点管理・TTM換算・分割対応を説明し、同じ財務データについて「企業を識別できる入力」と「匿名化した入力」を比べた予備実験を示します。実売買はしていません。投資助言や運用成績の検証ではありません。
| 項目 | 上流 | 日本株フォーク |
|---|---|---|
| 価格・財務 | Financial Datasets | J-Quants V2の日足、決算短信サマリー、銘柄マスタ |
| 発注単位 | 原則1株 | 100株単位に切り捨て |
| TTM | APIの指標 | 決算短信の累計から作成 |
| 開示情報の採用 | 判断日以前の提出情報 | 判断日より前に開示された短信 |
| 比較対象 | 指定したベンチマーク | 1306の調整後価格(売買しない場合のみ) |
J-Quants無料プランには過去2年、直近12週間の遅延、1分5回のAPI制限があります。財務諸表の詳細項目は含まれないため、粗利率・負債資本比率・流動比率は欠損値のまま扱います。
1. 決算短信の累計からTTMを作る
日本の四半期決算は期初からの累計です。前年同期と前期通期を使い、直近12か月のフローを次の式で作りました。
TTM = 今期累計 + 前期通期 − 前年同期累計
前期通期または前年同期がなければその期は省き、年換算しません。実装は jquants_fins.py の _ttm です。四半期の前年同期比は同じ累計期間同士を比較します。
EPSだけは分割前後の値を単純に加減できません。株式分割の前後で「1株」の基準が異なるためです。TTMの純利益を、最新の短信に載る期中平均株式数 AvgSh で割る計算を優先します。AvgSh が取れない場合、実装はEPSの累計からの換算値に戻るので、分割がある銘柄では注意が必要です。
時価総額、PER、PBRはその短信の株式数と開示日までの未調整終値から算出します。取得できる株価の範囲より古い短信では空欄です。事業分離などで前年を組み替えた会社は、当初開示の旧基準値と今期の新基準値が混ざる場合があります。無料サマリーだけでは補正できません。
2. 時点管理を日付単位で安全側に倒す
終値での仮想約定に対して当日引け後の開示を使わないよう、短信の DiscDate が判断日より前のものだけを採用します。訂正短信は、その訂正の開示後に置き換えます。業績予想の修正は取り込みません。
# jquants_fins.py の判定を簡略化
if row["DiscDate"] >= as_of:
continue
これは同日午前中の開示も除外する保守的な規則です。また、資料の開示日を守っていても、LLMの学習済み知識まで過去時点へ戻せるわけではありません。
3. 分割と100株単位を扱う
仮想注文は100株単位に切り捨てます。売買銘柄の評価には未調整終値を使い、対応済みの株数が増える分割では権利落ち日に仮想持株数を増やします。AdjFactor=0.2 の1株→5株なら持株数を 1 / 0.2 = 5 倍します。調整係数の意味はJ-Quants公式の説明にもあります。
一方、売買しない比較対象1306は分割で価格が飛ばないよう AdjC を使います。株式併合、100株単位が崩れる分割、momentum戦略での売買銘柄の分割にはまだ対応していません。1306の価格比較には分配金が含まれません。
4. 実データで判明した失敗を扱う
| 現象 | 対応 |
|---|---|
| 1306の2026年3月の分割で検証停止 | 売買しない比較対象だけ調整後価格へ |
| 分割をまたいだEPSの桁違い | TTM純利益 ÷ AvgShを優先 |
| 12秒おきの呼び出しでも429 | 13.2秒間隔に広げ、Retry-Afterで再試行 |
| 2年の契約範囲より前の価格取得で400 | エラー内の利用可能範囲を読み取り、開始日を直して取得 |
| AIが短いJSONの後に理由付きJSONを返す | 判断と確信度が一致すれば理由付きの方を採用。不一致なら判断を控える |
| 銘柄選定の基準日が早く、期が足りない会社が出る | 結果を見る前に2月9日から2月16日に変更 |
最後の変更はAPIの問題ではありません。2月9日時点では、期限内の2月10〜13日に発表した14社まで除外され、発表日の早い会社に寄った標本になっていました。
企業の識別情報を伏せる比較
通常の入力は、会社名の文字列をAIに渡していません。ただし証券コード・業種・時価総額・決算期末日・開示日を含み、会社を特定できてしまいます。以下では便宜上「識別情報あり」と呼びます。
LLMの学習の性質上、バックテストで期間を指定しても、すでにそれより先の未来を学習していると、その情報を踏まえた上でバックテストを行ってしまう恐れがあります。そのため企業を匿名化し、日付は latest / -3m のような相対表記に変更して行ってみました。PER、ROE、利益率、EPS、BPSなどの数字は残します。2つの設定の差は anonymize: true の有無です(japan-30.yaml、japan-30-anon.yaml)。同じスナップショットのハッシュで判断を組にする集計コードを使いました。
2026年2月16日時点のプライム市場から金融業を除き、TOPIX Core30・Large70・Mid400から各10社を種固定で選びました。選定に必要なTTMの期を4つ以上確保しています。週次判断は2月20日〜6月26日の19回。未変化のスナップショットは重複計上せず、Buffett役61組、Graham役61組、合計122組を比較しました。事前仮説と基準日の変更は手順書に記録しています。AIモデルはClaude Opus 5.5です。
| 判断役 | 中立:識別情報あり → 匿名化 | 判断一致 | 中立が「ありだけ」/「匿名化だけ」 |
|---|---|---|---|
| Buffett役 | 79% → 59% | 67% | 16/4組 |
| Graham役 | 36% → 41% | 92% | 1/4組 |
Buffett役では、13組が「識別情報ありで中立、匿名化で弱気」。Graham役ではほぼ同じ判断でした。Core30、Large70、Mid400におけるBuffett役の中立差は24、20、15ポイントで、会社への認知度の自己評価と会社ごとの差の順位相関は−0.05です。自己評価はMid400でも平均7.2点で、幅が狭い指標でした。
同じ会社が何度も登場するので、122組を独立した試行として有意差を主張できません。会社単位ではBuffett役で識別情報ありの中立が多い会社11社、少ない会社4社。符号検定の両側p値は約0.12です。会社が分かると判断が変わる可能性は示しましたが、記憶が原因だと特定したわけではありません。1株あたりの値からの再識別や、匿名化によって業種を伏せた影響も残ります。
同期間の仮想収益率は識別情報あり−6.8%、匿名化−3.7%、1306の価格が+5.1%。約4か月半で、空売りの費用、手数料、税、配当、実際の約定可能性などを含めていません。LLMの優劣や戦略の収益性は評価しません。
再現手順
Python 3.11以降、uv、J-Quants V2のAPIキーを用意します。AIを使う比較にはAnthropicのAPIキーも必要です。キーはリポジトリ直下の .env に JQUANTS_API_KEY=... と ANTHROPIC_API_KEY=... を各1行で設定し、Gitの管理対象外にしてください。
git clone https://github.com/TIshow/ai-hedge-fund-jp.git
cd ai-hedge-fund-jp
uv venv .venv
uv pip install --python .venv/bin/python -e . 'pytest>=7.4,<8'
.venv/bin/python japan_pilot_smoke.py > /dev/null
最後のコマンドは架空データの動作確認で、APIキー不要です。実データの30社比較は、JAPAN_PILOT.mdの「30銘柄の比較の実行手順」にある japan_universe.py → japan_knowledge_probe.py → 2つのバックテスト → japan_compare.py の順に実行します。J-Quantsの呼び出しがある作業は無料プランの制限上、順番に実行します。保存済み結果から画像を作る japan_media.py はAPIを呼びません。
コードと手順を再現できても、無料プランのデータ欠損、当初開示値の組み替え、LLMの学習済み知識、標本の小ささという制約は残ります。次は小型株への拡張と、モデルがまだ知りえない期間での前向きな比較を試します。
お疲れ様でした。
わからないところ、間違っているところ、もっといい方法がある場合は、コメントでもDMでも教えてください。