0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Amplify x AgentCore】日本株分析エージェントを作ってみた

0
Posted at

はじめに

Amplify Gen 2Amazon Bedrock AgentCore Runtime で日本株の分析エージェントを構築してみました。

私自身、趣味程度で時々日本株を売り買いしたりするんですが、ちょっと気になる銘柄が出てきた時にサクッと調べられるツールがほしいなぁと思い、作ってみました。

↓↓こんな感じです↓↓

アプリ開発のベースとなるテンプレートには、以前作成した Kiro x Amplify Gen 2 テンプレート を活用しています。

コーディングは全てKiroちゃんにお任せで、第1版でほぼ完成形を作ってくれました!
その後、細かいレイアウト調整やレスポンス改善などを進めてこの形になりました。

アプリの全体像

こんなアプリ

銘柄コードまたは銘柄名(例:7203 / トヨタ自動車)を入力するだけで、AIエージェントが以下の流れで分析レポートを生成します。

  1. 株価チャートが2〜3秒で先に描画される ※銘柄名で検索する場合はコード特定後に実行
  2. その裏でエージェントが複数ツールを順次実行
  3. 分析テキストがストリーミングで表示される(約60秒)

アーキテクチャ

アーキテクチャ図はこんな感じです。
jp-stock-analysis.png

フロントエンドはAmplify Hostingで受け付けて、分析リクエストを受け取ると大きく2つの経路で処理が流れます。

①チャート描画

AppSyncを介してGraphQLでチャートの描画に必要な過去1年分の日次終値を取得するLambdaを起動します。
LambdaはYahoo Financeからデータを取得しています。

jp-stock-analysis-1.png

②株価分析エージェント

Bedrock AgentCore Runtime上に展開したAIエージェントを呼び出します。
AIエージェントにはToolsを登録しており、Yahoo Financeから必要な情報を収集してレポートにまとめます。オプションとしてTavily APIを登録すると、直近のニュースをWebからも収集します。
分析結果はSSEストリーミングで順次画面上に配信されます。

jp-stock-analysis-2.png

フロントエンドはGraphQLクエリとSSE通信を並行実行しています。
チャートだけでも先に見せることで、体感速度を少しでも上げられるようにしています。

最初はAgentCore側でチャート生成まで実施するようにしていましたが、チャート描画用データを生成するのに必要な時間が約1分と長かったので、今回のような並行実行する構成に変更しました。

③認証の仕組み

AppSyncとAgentCore Runtimeで、同じCognito User Poolを共有しています。
Amplifyが払い出したUser Poolをバックエンド処理でも参照することで、ユーザーは1回のログインで両方のAPIへアクセスできます。

jp-stock-analysis-3.png

ただし同じCognitoを使っていても、認証の仕組みはそれぞれ別物です。

観点 ①AppSync ②AgentCore Runtime
認証モード Cognito User Pool Custom JWT Authorizer
検証の仕組み AWS managed OIDC Discovery
トークン送信 Amplifyライブラリが隠蔽 Authorization: Bearer

①AppSync:Cognito User Pool 認証(ネイティブ統合)

AppSyncはCognitoとネイティブ統合しているので、AWS側で裏側の検証をまるごと面倒見てくれます。今回構成のAmplify Dataでは、認可ルールを宣言するだけでOK。

amplify/data/resource.ts (抜粋)
getStockPrices: a
  .query()
  .arguments({ tickerCode: a.string().required() })
  .returns(a.string())
  .authorization((allow) => [allow.authenticated()])
  .handler(a.handler.function(stockPrice)),

②AgentCore Runtime:Custom JWT Authorizer(汎用JWT検証)

一方のAgentCoreは、Cognitoとのネイティブ統合はありません。
OIDC Discoveryで公開鍵を取得してJWTを自前で検証する、汎用的な仕組みになっています。

agentcoreデプロイ時のコンフィグにて設定します。

  • discoveryUrl:Cognitoの .well-known/openid-configuration のURL
  • allowedClients:許可するApp Client IDのリスト

この2つをcustomJWTAuthorizerのパラメータとして渡すと、AgentCore側で署名・発行元・クライアントIDを検証してくれる形です。

agentcore configure \
  --entrypoint jp_stock_agent/app.py \
  --name jp_stock_agent \
  --authorizer-config '{"customJWTAuthorizer":{"discoveryUrl":"https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration","allowedClients":["<app-client-id>"]}}' \
  --region us-west-2

クライアント側は Authorization: Bearer <JWT> を明示的に付けてHTTPSで叩きます。

AgentCore側がOIDC準拠の汎用JWT検証なので、Cognito以外のIdPでも接続可能
今回はCognitoを共有することで「1回のログインで両サービスにアクセス」を実現。

技術スタック

レイヤー 技術
フロントエンド Next.js 15 + TypeScript
バックエンド AWS Amplify Gen 2(AppSync, Cognito, Lambda)
AI エージェント Python / Strands Agents SDK
エージェント実行基盤 Amazon Bedrock AgentCore Runtime
株価データ Yahoo Finance API(yfinance)
ホスティング Amplify Hosting
IDE 支援 Kiro

ディレクトリ構成

japan-stock-analysis/
├── src/                         # フロントエンド(Next.js App Router)
│   ├── app/                     # ページとレイアウト
│   ├── components/stock/        # 株価チャート・分析レポート UI
│   ├── hooks/                   # カスタムフック(useStockAnalysis)
│   ├── lib/                     # Amplify 設定、AgentCore 通信
│   └── types/                   # 型定義
├── amplify/                     # Amplify Gen 2 バックエンド定義
│   └── functions/stock-price/   # 株価データ取得 Lambda
├── agents/                      # Strands エージェント
│   ├── jp_stock_agent/          # 日本株分析エージェント
│   ├── common/                  # 共通処理(設定、ログ)
│   └── scripts/                 # ローカル実行・環境変数設定スクリプト
├── docs/                        # ドキュメント
└── .kiro/                       # Kiro ワークスペース設定

技術的なポイント

ここからは実装面で工夫した点を掘り下げます。

株価データ取得 (Lambda)

やっていること

  • 過去1年分の日次OHLCVを取得
  • 終値ベースの**単純移動平均線(5日・25日・75日・200日)**を算出
  • StockDataPayload 形式のJSON文字列を返却

エラーハンドリング

エラー 対応
銘柄コードが4桁数字でない バリデーションエラー
銘柄が見つからない 空データエラー
API タイムアウト(10秒) AbortController でタイムアウト
HTTP エラー ステータスコード付きエラー
JSON パース失敗 パースエラー

株価分析エージェント (AgentCore Runtime)

分析ツール一覧

日本株分析エージェントには、5つのツールを用意しています。エージェントはユーザーの入力内容に応じてこれらを順次実行し、最終的な分析レポートをまとめます。

ツール 機能
search_stock 銘柄コードまたは銘柄名から企業を特定
get_stock_prices 過去1年分の株価データとテクニカル指標を取得
get_financial_metrics PER・PBR・ROE・配当利回り(予想/実績)等の財務指標を取得
search_news 直近ニュース・IR情報を取得し影響度を評価
get_sector_peers 同一業種の上場企業を動的検索し財務指標を比較

同業種比較

同業種比較は、yfinance Screener APIを使って industry(業種小分類)単位で動的検索しています。
静的な銘柄リストだとニッチな業種がカバーできなかったため、ツール側に寄せました。

配当利回りの表記

どちらかというとyfinanceの仕様の揺らぎが原因ですが、銘柄によってはデータの持ち方が小数点表記だったり%表記だったりします。
表記ブレを吸収して、適切な利回りを表示するよう工夫。

また、実利回りと予想利回りの2つの情報があったので、両方の利回りを出すように指示。

スコアリング設計

各分析カテゴリにスコアを付与し、総合スコア(0〜100)と投資判断ラベル(弱気〜強気)を算出しています。

カテゴリ 配点
ファンダメンタルズ 30 点
テクニカル 25 点
ニュース 20 点
同業種比較 25 点

フロントエンドの並行処理

src/hooks/useStockAnalysis.ts で、GraphQLクエリとSSE通信の並行実行を制御しています。

銘柄コードで検索した場合

1. ユーザーが「7203」を入力
2. 2つのリクエストを並行発火
   - GraphQL getStockPrices(tickerCode: "7203") → Lambda → Yahoo Finance
   - HTTP POST /invocations → AgentCore Runtime
3. Lambda が 2〜3秒でチャートデータ返却 → 即チャート描画
4. エージェントが約60秒かけて分析テキストを SSE ストリーミング

銘柄名で検索した場合

銘柄名の場合は、そもそも銘柄コードが分からないのでチャート描画用データ取得を後追いにしています。

1. ユーザーが「トヨタ」を入力
2. 銘柄コードを抽出できないため、GraphQL クエリはスキップ
3. SSE ストリーミング開始 → エージェントが銘柄コードを特定
4. SSE テキストから銘柄コード(例: 7203)を検出
5. 後追いで GraphQL クエリを発火 → チャート描画

ユーザー入力の曖昧さをエージェントに吸収させる形で、UX側の分岐を最小化しました。

「三菱」のような曖昧ワードを入れた場合は、エージェントが三菱商事・三菱重工・三菱電機など複数候補を提示して、再入力を促す動きになります。

まとめ

今回はAmplify Gen 2 x AgentCore Runtime で日本株分析エージェントを作ってみました。

  • Cognito User Pool を共有し、AppSync と AgentCore の両方に1回のログインでアクセス
  • GraphQL(チャート)とSSE(分析テキスト)の並行実行で体感速度を改善

エージェント側の分析ロジックやスコアリングはまだまだ磨きどころがあるので、引き続きブラッシュアップしていきます。

今回作成した日本株分析エージェントはGitHubにも公開してあります。

参考リンク

本記事の内容は投資助言ではありません。実際の投資判断は自己責任でお願いします。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?