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?

Claudeが14本のプラグイン結果を統合してDiscordに届けるまで【投資分析システム設計記 #3】

0
Last updated at Posted at 2026-04-21

第1回ではプラグインアーキテクチャ、第2回ではデータ層とパイプラインについて書いた。

第1回の時点では9本だったプラグインも、第2回で触れた通り14本に増えた。プラグインが増えるほど、矛盾する判定が増え、統合分析の価値が上がる。

最終回は、14本のプラグイン出力が Claude の手で統合レポートになり、Discord 経由でスマホに届くまでの仕組み。このシリーズで一番面白い部分だと思う。

実際に毎日16時に届く Discord 通知がこれ。

RakuScan の Claude 統合分析レポート(Discord日次通知)

統合判断・目標株価・推奨アクションまで出力される
各プラグインの判定一覧に加えて、目標株価(計算値 + AI予測値)、矛盾の読み解き、推奨アクションまでが1つのレポートにまとまる

全体の流れ

14本のプラグイン実行
    ↓
各プラグインが PluginResult(signal / score / confidence / summary)を返す
    ↓
screener.py が consensus(BUY/SELL の数)と composite_score(加重平均)を計算
    ↓
上位銘柄の JSON を Claude Code CLI に渡す
    ↓
Claude が矛盾検出・推奨強度判定・レポート生成
    ↓
Discord Webhook で通知(毎日16時にスマホに届く)

Python が出すところまで:consensus と composite_score

Claude に渡す前に、Python 側で2つの指標を計算する。

consensus(合意)

@property
def consensus(self) -> dict:
    buy_count = sum(1 for r in self.results if r.signal in {"STRONG_BUY", "BUY"})
    sell_count = sum(1 for r in self.results if r.signal in {"STRONG_SELL", "SELL"})
    neutral_count = len(self.results) - buy_count - sell_count
    avg_score = sum(r.score for r in self.results) / len(self.results)
    return {
        "buy_count": buy_count,
        "neutral_count": neutral_count,
        "sell_count": sell_count,
        "avg_score": round(avg_score, 3),
    }

14本中何本が BUY を出して何本が SELL を出したか。シンプルだが、これが一番わかりやすい指標。

composite_score(加重平均スコア)

_SIGNAL_SCORE = {
    "STRONG_BUY": 1.0,
    "BUY": 0.75,
    "NEUTRAL": 0.5,
    "SELL": 0.25,
    "STRONG_SELL": 0.0,
}

@property
def composite_score(self) -> float:
    total = sum(
        _SIGNAL_SCORE.get(r.signal, 0.5) * r.confidence
        for r in self.results
    )
    return round(total / len(self.results), 4)

signal を数値化(STRONG_BUY=1.0 〜 STRONG_SELL=0.0)して confidence で重み付け。confidence が低いプラグイン(データ欠損など)の影響を自動的に抑える。

SELL フィルタ

composite_score でランキングする前に、SELL が過半数の銘柄は除外する。

# sell_count < len(results) // 2 + 1 の銘柄のみ通過

スコアが高くても半数以上のプラグインが SELL を出している銘柄は怪しい。一部のファクターだけ突出して高い「罠銘柄」を排除するためのフィルタ。

Claude に渡すもの:スリム化した JSON

スクリーニング上位の銘柄データを Claude に渡す。ただし、全フィールドを渡すとトークン消費が膨大になる。

_PLUGIN_FIELDS = {"plugin", "category", "signal", "score", "confidence", "summary", "target_price"}

# details フィールドを除いた軽量版を生成
slim_json = json.dumps(_slim(raw), ensure_ascii=False, indent=2)

各プラグインの details(手法固有の詳細データ:個別の財務指標やテクニカル値など)はカットして、判定結果(signal / score / confidence / summary)だけを渡す。1銘柄あたり約30行のコンパクトな JSON になる。

Claude に渡すのは「結論」であって「計算過程」ではない。この割り切りがトークン効率の鍵。

Claude Code CLI で統合分析

ここが RakuScan の一番ユニークな部分。Anthropic API(SDK)は使わず、Claude Code CLI を直接呼んでいる。

# src/notify/claude_analyzer.py

def analyze(screener_json: str) -> str:
    cmd = ["claude", "-p", skill_content, "--output-format", "text"]
    proc = subprocess.run(
        cmd,
        input=screener_json,
        capture_output=True,
        text=True,
        timeout=180,
    )
    return proc.stdout.strip()

claude -p でプロンプトを渡し、stdin で JSON を流し込む。出力は Markdown のテキスト。API キーの管理が不要で、Claude Code のサブスクリプションで動くのがメリット。

なぜ API ではなく CLI か

  • API キー管理が不要。個人開発のスクリプトにAPIキーを埋め込みたくなかった
  • Claude Code のスキル機能が使える。プロンプトを .claude/skills/ に Markdown ファイルとして管理できる
  • モデル選択が自動。Claude Code が最適なモデルを選んでくれる

デメリットはレスポンス速度(CLI起動のオーバーヘッド)と、Claude Code がインストールされた環境でしか動かないこと。個人開発の自分用ツールなので許容範囲。

プロンプト設計:矛盾検出がキモ

Claude に渡すプロンプト(スキルファイル)には、単なる「分析してください」ではなく、具体的な矛盾パターンと判断ルールを埋め込んでいる。

レジームチェック(最優先)

市場全体が下落トレンド(DEFENSIVE レジーム)のときは、どの銘柄も新規推奨しない。これは Claude に判断を任せるのではなく、プロンプト内で明示的にルール化している。

矛盾検出パターン

14本のプラグインが全部 BUY を出すことはまれで、むしろ矛盾する判定が出るのが普通。この矛盾をどう読み解くかが分析の価値。

プロンプトには5つの矛盾パターンを定義している。

パターン 解釈
モメンタムBUY × 財務SELL 勢いはあるが財務に不安。仕手株の可能性。見送り推奨
バリューBUY × モメンタムSELL 割安だが市場に無視されている。バリュートラップの可能性
ファクター系BUY × ファンダ系BUY 両方強い。信頼度が高い
テクニカルBUY × リスクSELL トレンドは良いが損切り条件に近い。タイミング注意
全プラグインNEUTRAL 明確なシグナルなし。様子見推奨

これを Claude にテンプレートとして渡すことで、「なんとなくそれっぽい分析」ではなく「定型パターンに基づく構造化された分析」を出力させている。

推奨強度の4段階判定

buy_count と avg_score の組み合わせで、推奨の強さを4段階に分ける。

  • 強い推奨: buy_count が多く avg_score が高い
  • 推奨: buy_count が過半数
  • 弱い推奨: buy_count はあるが avg_score が低い
  • 見送り: buy_count が少ない、または矛盾が多い

Claude にはこの4段階を使ってレポートを書くよう指示している。

さらに、目標株価も2種類算出させている。プラグインの数値から機械的に計算した「計算値」と、Claude がセクター特性やリスクを織り込んで調整した「AI予測値」。先ほどのスクリーンショットでは、計算値4,503円(+78.8%)に対してAI予測値が3,800円(+51%)と出ている。資源株のボラティリティを考慮して下方修正した、という根拠も添えられている。

Discord への通知

Webhook(一方通行の自動通知)

日次スクリーニングの結果は Discord Webhook で送信する。

# src/notify/discord.py

def send_claude_analysis(analysis: str, total_screened: int, as_of: str = "") -> None:
    payload = {
        "embeds": [{
            "title": "🤖 Claude 統合分析レポート",
            "description": analysis,  # Markdown テキスト(最大4000文字)
            "color": 0x2ECC71,        # 緑
            "footer": {
                "text": f"RakuScan • {as_of} • {total_screened}銘柄スクリーニング済"
            },
        }]
    }
    requests.post(webhook_url, json=payload)

Claude の分析が取得できなかった場合(タイムアウト等)は、従来形式のスクリーニング結果をフォールバック通知する。Claude が落ちてもパイプライン自体は止まらない。

シグナルの視覚化

Discord Embed のカラーコードとシグナル絵文字で、パッと見で判断できるようにしている。

_SIGNAL_EMOJI = {
    "STRONG_BUY": "🟢🟢",
    "BUY": "🟢",
    "NEUTRAL": "⚪",
    "SELL": "🔴",
    "STRONG_SELL": "🔴🔴",
}

_COLOR_BUY = 0x2ECC71       # 緑
_COLOR_SELL = 0xE74C3C      # 赤
_COLOR_DEFENSIVE = 0xF39C12 # オレンジ(守りレジーム)

スマホの通知を開いた瞬間に「今日は買い推奨が出てるな」「守りモードだな」がわかる。

双方向 Discord Bot

Webhook は一方通行。気になる銘柄をその場で深掘りしたいときのために、双方向の Discord Bot も動かしている。

コマンド体系

実際に !analyze 2760(東京エレクトロン デバイス)を実行した画面がこちら。

RakuScan Discord Bot の !analyze コマンド実行結果
14本のプラグインが一斉に走り、シグナル(🟢🟢/🟢/⚪/🔴)・スコア・サマリーが一覧で返ってくる。レジーム・合意・composite_score まで一目でわかる

即レス系(Claude呼び出しなし):

コマンド 機能
!scan スクリーニング実行
!analyze 7203 指定銘柄を全プラグインで分析
!portfolio ポートフォリオ表示
!performance 週次パフォーマンス
!regime 市場環境判定

Claude経由系(レート制限あり):

コマンド 機能
!research 半導体セクターの見通し Claude にリサーチ質問
!why 7203 判断根拠の説明
!compare 7203 6758 2銘柄の比較分析

レート制限

Claude を呼ぶコマンドにはレート制限をかけている。

class RateLimiter:
    def __init__(self, per_hour: int = 10, per_day: int = 30, cooldown_seconds: int = 30):
        self._per_hour = per_hour
        self._per_day = per_day
        self._cooldown = cooldown_seconds

1時間あたり10回、1日あたり30回、コマンド間隔30秒。Claude Code CLI の呼び出しコスト(時間・トークン)を考慮した設定。

Claude の非同期呼び出し

Discord Bot は discord.py の非同期フレームワークで動いているので、Claude Code CLI の呼び出しも非同期で行う。

async def _call_claude(prompt: str, stdin_data: str = "") -> str | None:
    cmd = ["claude", "-p", prompt, "--output-format", "text"]
    proc = await asyncio.create_subprocess_exec(
        *cmd,
        stdin=asyncio.subprocess.PIPE,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    stdout, stderr = await asyncio.wait_for(
        proc.communicate(stdin_data.encode()),
        timeout=180,
    )
    return stdout.decode().strip()

タイムアウトは180秒。Claude が考え込んでも Bot が固まらない。

セキュリティ

Bot にはチャンネル制限とユーザー制限をかけている。

class RakuScanBot(discord.Client):
    def __init__(self, bot_config: dict):
        self._allowed_channels: set[int] = {int(c) for c in bot_config.get("allowed_channels", [])}
        self._allowed_users: set[int] = {int(u) for u in bot_config.get("allowed_users", [])}

指定チャンネル・指定ユーザー以外からのコマンドは無視する。投資判断に関わるシステムなので、第三者がコマンドを打てない状態にしている。

運用の全体像

平日の運用フローをまとめるとこう。

16:00  cron → daily_run.py 起動
16:01  ユニバース取得 + データプリフェッチ(キャッシュ活用で10秒以内)
16:02  14本のプラグインが200銘柄を分析(キャッシュヒットで高速)
16:03  上位銘柄の JSON → Claude Code CLI → 統合レポート生成
16:05  Discord Webhook でスマホに通知

随時  気になったら Discord Bot に !analyze 7203 を打つ
      → 全プラグイン実行 → 結果を Embed で返答

土曜  weekly_run.py → 事後検証 + パフォーマンスレポート
月初  monthly_run.py → ユニバース再構築

スマホの通知を見て、気になる銘柄があれば Bot で深掘りする。この「自動通知 + 対話的な深掘り」の組み合わせが、使っていて一番便利なポイント。

シリーズまとめ

3回にわたって RakuScan の設計を書いてきた。

回 テーマ キーワード
第1回 プラグインアーキテクチャ PluginResult / YAML動的ロード / 1ファイル=1手法
第2回 データ層とパイプライン 3つの無料API / ABC抽象化 / 2層キャッシュ / 月次・週次・日次
第3回(本記事) Claude統合とDiscord Claude Code CLI / 矛盾検出プロンプト / Webhook + Bot

設計思想をひとことでまとめると「Python で定量、Claude で統合、Discord で通知」。

確定的に計算できるものは Python で回し、複数の矛盾する判定を統合して人間に伝える部分だけを Claude に任せる。この役割分担がうまくハマると、LLM のコストを抑えつつ、再現性のある分析が回せる。

個人開発でも、API の無料枠とキャッシュ戦略を工夫すれば、200銘柄の日次スクリーニングシステムは十分に運用できる。


このシリーズの内容をさらに深掘りした有料本を公開中。各プラグインの戦略設計の詳細、設計判断の裏側にある思想、失敗と修正の過程、実コードの解説など、連載では書ききれなかった内容を15章・約5万文字にまとめた。
https://zenn.dev/sktt_panda/books/rakuscan-design-philosophy


この記事は Zenn にも同じ内容を投稿しています。

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?