Claude に「この銘柄どう思う?」と聞けば、それっぽい分析は返ってくる。
ただ、毎回プロンプトを投げるたびに微妙に違う判断が出てくるし、200銘柄をスクリーニングしようとすればトークン消費が膨大になる。再現性がなく、費用対効果も厳しい。
そこで考え方を変えた。定量的な分析は Python で確定的に回し、Claude には「複数の分析結果を統合して人間に伝える」部分だけを任せる。RakuScan はその設計思想で作った投資分析システムで、現在9本のプラグインが動いている。
この記事はシリーズ第1回として、プラグインアーキテクチャの設計思想と全体構成を書く。
RakuScan の全体像
RakuScan は、日本株のスクリーニングと銘柄分析を自動化するシステム。技術スタックは Python 3.13 + uv。
やっていることはシンプルで、毎日16時に以下の流れが自動で走る。
- 投資対象の銘柄群(ユニバース)を取得する
- 各銘柄に対して、複数の投資手法(プラグイン)を実行する
- 結果を Claude が統合分析して、Discord に通知する
ポイントは、2 の「投資手法」がすべて独立した Python スクリプトになっていること。ファイルを1個追加して設定に1行書けば、新しい手法がそのまま翌日のスクリーニングに組み込まれる。
なぜプラグイン型にしたのか
投資分析は手法の入れ替えが頻繁に発生する。
「モメンタムだけで見てたけど、やっぱり財務健全性も確認したい」「この手法は成績が悪いから一旦外したい」みたいなことが日常的に起きる。さらに、手法ごとに参照する学術論文も違えば、使うデータソースも異なる。
こういう性質のシステムを1枚岩で書くと、手法を追加するたびに既存コードに手を入れることになって、すぐに保守が厳しくなる。
だからこう決めた。
- 1つの投資手法 = 1つの Python ファイル
- すべてのプラグインは同じフォーマットで結果を返す
- プラグイン同士は互いの存在を知らない
この設計にすることで、「手法を追加しても他の手法に影響しない」状態を維持している。
現在のプラグイン構成
9本のプラグインが動いていて、それぞれ学術論文ベースの投資手法を実装している。
| プラグイン | カテゴリ | やっていること | 根拠 |
|---|---|---|---|
| regime | 市場環境 | TOPIX の200日移動平均で攻め/守りを判定 | Faber (2007) |
| piotroski | 財務 | 9項目の財務チェックで健全性をスコア化 | Piotroski (2000) |
| factor_momentum | ファクター | 12ヶ月リターンでモメンタムを評価 | Jegadeesh & Titman (1993) |
| factor_value | ファクター | PBR・PER・PCFRでバリューを評価 | Fama & French (1992) |
| factor_quality | ファクター | ROE・ROA・FCFで収益の質を評価 | Novy-Marx & Velikov (2016) |
| altman_z | 財務 | Z-Score で倒産リスクを判定 | Altman (1968) |
| sepa | テクニカル | トレンドテンプレートの条件を判定 | Minervini VCP |
| exit_rules | リスク | 5つの売却条件をチェック | ATR ベース |
| factor_composite | ファクター | モメンタム×バリュー×クオリティの複合スコア | Asness et al. (2013) |
カテゴリは5種類ある。市場環境(regime)、ファクター(factor)、財務(fundamental)、テクニカル(technical)、リスク管理(risk)。カテゴリが違えば見ている観点がまったく異なるので、複数のプラグインが同時に BUY と言っているときほど信頼度が高くなる。
さらに7本の未実装プラグイン(CANSLIM、デュアルモメンタム、タートルブレイクアウトなど)が設計段階で控えている。実装したらファイルを置いて設定を1行足すだけで組み込める。
共通インターフェース:PluginResult
プラグインアーキテクチャの核は、すべてのプラグインが返す共通のデータ構造にある。
PluginResult という dataclass を定義していて、どのプラグインもこの形式で結果を返す約束になっている。
主なフィールドはこう。
| フィールド | 型 | 意味 |
|---|---|---|
| signal | str | 5段階の判定(STRONG_BUY / BUY / NEUTRAL / SELL / STRONG_SELL) |
| score | float | 0.0〜1.0 に正規化されたスコア |
| confidence | float | 0.0〜1.0 の確信度 |
| summary | str | 1行の日本語要約 |
| details | dict | 手法固有の詳細データ |
| category | str | 5種類のカテゴリ |
ここで重要なのは、score と confidence を分離していること。
たとえば Piotroski F-Score が 7/9 なら score は 0.78 だが、財務データに欠損があれば confidence は 0.6 に下がる。「高スコアだけどデータが不完全」という状態を表現できる。
バリデーションも入っていて、score や confidence が 0.0〜1.0 の範囲外だったり、signal が定義済みの5段階に合致しなかったりすると、インスタンス生成時にエラーになる。型で壊れたデータが後段に流れるのを防いでいる。
プラグインの規約
プラグインを書く側のルールは最小限にしている。
-
scripts/ディレクトリに Python ファイルを置く -
run()関数を公開する -
PluginResultを返す
これだけ。銘柄コードを受け取って分析するプラグインなら run(symbol: str) -> PluginResult、市場全体を見る regime のようなプラグインなら run() -> PluginResult になる。
各プラグインは if __name__ == "__main__" を付けてあるので、コマンドラインから単体実行もできる。開発中に個別の手法だけ試したいときに便利。
動的ロードの仕組み
プラグインの有効/無効は plugins.yaml で管理している。
plugins:
- name: regime
display_name: マーケットレジーム
enabled: true
category: regime
- name: piotroski
display_name: ピオトロスキー F スコア
enabled: true
category: fundamental
# 成績が悪ければ enabled: false にするだけ
# - name: canslim
# enabled: false
スクリーニング実行時、ローダーがこの YAML を読んで enabled: true のプラグインだけを importlib.import_module() で動的にロードし、run() 関数を呼ぶ。
新しいプラグインを追加するときの手順はこう。
-
scripts/new_strategy.pyを作る -
run()関数でPluginResultを返すように実装する -
plugins.yamlに1行追加する
既存のプラグインには一切手を入れない。この「追加だけで拡張できる」性質が、プラグインアーキテクチャの一番の恩恵だと感じている。
プラグインを束ねるスクリーナー
個別のプラグインを統合して動かすのが screener.py の役割。処理の流れはこう。
-
plugins.yamlから有効なプラグインの一覧を取得する - ユニバース(分析対象の銘柄群、150〜200銘柄)を取得する
- まず regime プラグインで市場環境を判定する。守りレジーム(DEFENSIVE)なら新規推奨は出さない
- 各銘柄に対して、有効な全プラグインを実行する
- 銘柄ごとに consensus(BUY/NEUTRAL/SELL の数)と composite_score(加重平均スコア)を計算する
- スコア上位の銘柄を抽出する
スクリーナー自体はプラグインの中身を知らない。PluginResult という共通形式を信頼して、signal と score と confidence だけを見て集計している。だから新しいプラグインが増えても、スクリーナーのコードは変わらない。
Claude の立ち位置:統合分析だけを任せる
ここまでの処理はすべて Python で確定的に動く。同じデータを入れれば同じ結果が出る。
Claude が登場するのはこの後。スクリーナーが出力した JSON(プラグイン名・シグナル・スコア・サマリー)を Claude Code CLI に渡して、統合レポートを生成させる。
つまり役割分担はこう。
| 役割 | 担当 | 理由 |
|---|---|---|
| 個別手法の定量判定 | Python(各プラグイン) | 再現性が必要。同じデータなら同じ結果 |
| 結果の統合・言語化 | Claude | 複数の矛盾する判定を読み解いて自然言語にまとめるのは LLM の得意領域 |
この設計にした背景には、最初にすべてを Claude にやらせようとして失敗した経験がある。
「TOPIX の200日移動平均を計算して、この銘柄の F-Score を出して、モメンタムも計算して…」と1つのプロンプトに詰め込むと、トークン消費が膨大になるし、計算の再現性もない。10回聞けば10回違う数字が返ってくることもあった。
今の設計では、Claude に渡すのはプラグインの出力 JSON だけ。各手法の生データや計算過程は渡さない。トークン消費を最小限に抑えつつ、Claude には「判断の統合」という最も得意な仕事だけをさせている。
最終的にこのレポートは Discord に通知される。スマホから毎日のスクリーニング結果を確認できる。この通知の仕組みと、Discord Bot による双方向のやりとりについてはシリーズ第3回で詳しく書く。
3つのインターフェースで同じエンジンを共有する
スクリーナーを動かす方法は3つある。
-
CLI:
python -m scripts.daily_runで日次パイプラインを実行 -
Claude Code スキル:
/scanでスクリーニング、/analyze 7203で個別分析 -
Discord Bot:
!scanで結果確認、!researchで Claude を使った深掘り
どのインターフェースも内部では同じ screener.py を呼んでいる。入り口が違うだけで、走る分析は同じ。
平日16時の cron で daily_run.py が自動実行され、結果が Discord に飛んでくるのが基本の運用フロー。気になる銘柄があれば、Discord Bot に !analyze 7203 と打てばその場で全プラグインが走る。
段階的に育てられる設計
RakuScan は最初から9本のプラグインを揃えたわけではない。
最初に動いたのは3本だけ。regime(市場環境)、piotroski(財務健全性)、factor_momentum(モメンタム)。この3本でスクリーニングの基本形ができた段階で日次運用を開始し、そこから手法を1つずつ追加していった。
この「ミニマムで動き始めて、あとから足していく」ができるのは、プラグインアーキテクチャの構造的なメリットだと思っている。
現状でもまだ7本の未実装プラグイン(CANSLIM、デュアルモメンタム、タートルブレイクアウトなど)が設計段階で控えていて、優先度の高いものから順に実装を進めている。
まとめと次回予告
RakuScan のプラグインアーキテクチャをまとめるとこう。
- 1ファイル = 1手法。プラグイン同士は互いを知らない
- 共通インターフェース(PluginResult)で結果の形式を統一
- YAML + 動的ロードでプラグインの追加・削除がコード変更なしで可能
- Python で定量、Claude で統合。再現性とトークン効率を両立
シリーズ第2回では、3つの外部 API(yfinance / EDINET DB / J-Quants)のデータ層設計と、API 無料枠でやりくりするキャッシュ戦略について書く。
第3回では、プラグインの出力が Claude の手で統合レポートになり、Discord でスマホに届くまでの仕組みを書く。9本のプラグインが出したバラバラの判定を、Claude がどう1つのレポートにまとめるのか。このシリーズで一番面白い部分になると思う。
実際の Discord 通知はこんな感じで届く。

Claude が9本のプラグインの結果を統合して、スマホに毎日届く
この連載の内容をさらに深掘りした有料本を公開中。各プラグインの戦略設計の詳細、設計判断の裏側にある思想、失敗と修正の過程、実コードの解説など、連載では書ききれなかった内容を15章・約5万文字にまとめた。
https://zenn.dev/sktt_panda/books/rakuscan-design-philosophy
この記事は Zenn にも同じ内容を投稿しています。