📖

株が買い時かAIエージェントに決めてもらう

2025/01/21に公開

皆さんは株を買うとき何を決め手にしていますか?
ニュース?好きな会社?
今はファンダメンタル分析というのがトレンドのようです。
ファンダメンタル分析とは企業の財務状況と株価のパフォーマンスを評価して実用的な洞察を提供することです。

このファンダメンタル分析をAIエージェントにやらせてみましょう。

https://x.com/LangChainAI/status/1881084215569129952

おおざっぱな流れ

  • Yahoo Finance を使って株価を取得
  • ta (テクニカル分析ライブラリ)を使ってRSI、MACD、VWAP などのテクニカル指標を計算
  • P/E 比率、負債比率、利益率などの財務指標を評価
    これらのデータからOpenAIを使って買いか判断してもらいます。

使うAIエージェントはLangGraphです。

実装は非常にシンプルで、Yahooファイナンスから指定した株のデータを取得し、それをもとにOpenAIのモデルに分析してもらいます。

データを取得するツールは以下の2つを設定します。

過去の株価データとテクニカル指標を取得するツール

@tool
def get_stock_prices(ticker: str) -> Union[Dict, str]:
    try:
        data = yf.download(
            ticker,
            start=dt.datetime.now() - dt.timedelta(weeks=24*3),
            end=dt.datetime.now(),
            interval='1wk'
        )
        df= data.copy()
        data.reset_index(inplace=True)
        data.Date = data.Date.astype(str)
        
        indicators = {}

        # Momentum Indicators
        rsi_series = RSIIndicator(df['Close'], window=14).rsi().iloc[-12:]
        indicators["RSI"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in rsi_series.dropna().to_dict().items()}
        sto_series = StochasticOscillator(
            df['High'], df['Low'], df['Close'], window=14).stoch().iloc[-12:]
        print(sto_series)
        indicators["Stochastic_Oscillator"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in sto_series.dropna().to_dict().items()}

        macd = MACD(df['Close'])
        macd_series = macd.macd().iloc[-12:]
        print(macd_series)
        indicators["MACD"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in macd_series.to_dict().items()}
        macd_signal_series = macd.macd_signal().iloc[-12:]
        print(macd_signal_series)
        indicators["MACD_Signal"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in macd_signal_series.to_dict().items()}
        
        vwap_series = volume_weighted_average_price(
            high=df['High'],
            low=df['Low'],
            close=df['Close'],
            volume=df['Volume'],
        ).iloc[-12:]
        indicators["vwap"] = {date.strftime('%Y-%m-%d'): int(value) for date, value in vwap_series.to_dict().items()}
        
        return {'stock_price': data.to_dict(orient='records'), 'indicators': indicators}
    except Exception as e:
        return f"Error fetching price data: {str(e)}"

主要な財務比率を取得するツール

@tool
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
    try:
        stock = yf.Ticker(ticker)
        info = stock.info
        return {
            'pe_ratio': info.get('forwardPE'),
            'price_to_book': info.get('priceToBook'),
            'debt_to_equity': info.get('debtToEquity'),
            'profit_margins': info.get('profitMargins')
        }
    except Exception as e:
        return f"Error fetching ratios: {str(e)}"

フローを管理するためのStateGraphを定義

add_messagesでヤフーファイナンスから得たデータと株式データのキーをキャシュしています。

class State(TypedDict):
    messages: Annotated[list, add_messages]
    stock: str
    
graph_builder = StateGraph(State)

OpenAIとバインディングツールの定義

ツールをLangGraphに統合し、分析のためのフィードバックループを作成します

import dotenv
dotenv.load_dotenv()

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model='gpt-4o-mini')

tools = [get_stock_prices, get_financial_metrics]
llm_with_tool = llm.bind_tools(tools)

アナリストノード

OpenAIに分析させるノード

FUNDAMENTAL_ANALYST_PROMPT = """

あなたは、企業(シンボルが {company} の会社)のパフォーマンスを株価、テクニカル指標、および財務指標に基づいて評価することを専門とするファンダメンタルアナリストです。あなたのタスクは、指定された株式のファンダメンタル分析に関する包括的な要約を提供することです。

使用可能なツール:
1. **get_stock_prices**: 最新の株価、過去の価格データ、およびRSI、MACD、ドローダウン、VWAPなどのテクニカル指標を取得します。
2. **get_financial_metrics**: 売上高、1株当たり利益(EPS)、株価収益率(P/E)、負債比率などの主要な財務指標を取得します。

### あなたのタスク:
1. **株式シンボルを入力する**: 提供された株式シンボルを使用してツールをクエリし、関連情報を収集します。
2. **データを分析する**: ツールからの結果を評価し、潜在的な抵抗線、主要なトレンド、強み、または懸念点を特定します。
3. **要約を提供する**: 以下を強調する簡潔で構造化された要約を作成します:
   - 最近の株価動向、トレンド、および潜在的な抵抗線。
   - テクニカル指標から得られる重要な洞察(例:株が買われすぎているのか売られすぎているのか)。
   - 財務指標に基づく財務の健全性とパフォーマンス。
### 制約条件:
 - 提供されたツールから得られるデータのみを使用してください。
 - 推測的な言葉を避け、観察可能なデータやトレンドに焦点を当ててください。
 - ツールがデータを提供できなかった場合、その旨を要約で明確に記載してください。

### 出力フォーマット:
以下の形式で応答してください:

"stock": "<株式シンボル>",
"price_analysis": "<株価動向の詳細な分析>",
"technical_analysis": "<全てのテクニカル指標に基づく時系列分析の詳細>",
"financial_analysis": "<財務指標に基づく詳細な分析>",
"final Summary": "<上記の分析に基づく総合的な結論>",
"Asked Question Answer": "<上記の詳細と分析に基づく質問への回答>"

応答は客観的で簡潔、かつ実用的なものにしてください。
"""

def fundamental_analyst(state: State):
    messages = [
        SystemMessage(content=FUNDAMENTAL_ANALYST_PROMPT.format(company=state['stock'])),
    ]  + state['messages']
    return {
        'messages': llm_with_tool.invoke(messages)
    }

graph_builder.add_node('fundamental_analyst', fundamental_analyst)
graph_builder.add_edge(START, 'fundamental_analyst')

グラフ化とコンパイルのためのツールの追加

graph_builder.add_node(ToolNode(tools)) 
graph_builder.add_conditional_edges( 'fundamental_analyst' , tools_condition) 
graph_builder.add_edge( ' tools ' , 'fundamental_analyst' ) 

graph = graph_builder.compile ()

画像でわかる通り、ノードはまずfundamental_analystノードを通りtoolsからデータを取得します。
その後fundamental_analystに戻って分析結果を返します。

グラフの実行

events = graph.stream({'messages':[('user', 'Should I buy this stock?')],
 'stock': 'TSLA'}, stream_mode='values')
for event in events:
    if 'messages' in event:
        event['messages'][-1].pretty_print()

結果

以下のような詳細な分析を得られました。
これ使ってなんかの株買ってみるかなぁ

"stock": "NVDA",
"price_analysis": "株価は最近の数週間で変動があり、2025年1月20日の時点での終値は140.83ドルです。過去数か月での株価は、120ドルから135ドルの間で推移しており、最近の増加傾向が見られます。特に、2024年11月初旬以降、株価は80ドルから90ドル台からの上昇が続いています。最も直近の抵抗線は145ドル付近にあると考えられます。",
"technical_analysis": "テクニカル指標では、RSIは前年比で66から41までの範囲が見られ、これは過去数週間で売られ過ぎの状態から回復傾向にあることを示しています。MACDは安定した高水準を維持しており、シグナルラインも同様です。ストキャスティクスは時折高値を示すため、短期的には調整が入る可能性がありますが、全体的には上昇トレンドを示しています。",
"financial_analysis": "財務指標は良好で、P/E比率は31.67、利益率は55%という高水準です。負債比率も17.22で低く、財務的な健全性が確認できます。これにより、NVDAは成長も見込まれる中、適切なバランスを維持しています。さらに、株価対簿価比率(P/B)は80.75であり、これも株の評価が高めであることを示しています。",
"final Summary": "NVDAは最近の株価動向とテクニカル指標から見て上昇基調にあり、中長期的に成長が期待されます。財務指標は強く、特に利益率および低負債比率が企業の健全性を示しています。しかし、投資にはリスクも伴いますので、バランスを取ることが重要です。",
"Asked Question Answer": "NVDAの最近のパフォーマンスと財務状態から判断すると、株を買うべきかどうかは個人のリスク許容度と投資スタンスによりますが、全体的にはポジティブな指標が揃っています。"

プロンプトを日本語に訳したnotebookはこちらです。

かなりシンプルでわかりやすくLangGraphの使い方が見えたと思います。

ではまた!

Discussion