ai-hedge-fund 1) Benjamin Graham

Tasker_Jang·2025년 3월 18일
0

이 시스템은 벤 그레이엄, 빌 액크만, 캐시 우드, 찰리 멍거, 스탠리 드러켄밀러, 워렌 버핏과 같은 전설적인 투자자들의 투자 철학을 모방한 에이전트들과 가치평가, 시장 심리, 기본적 분석, 기술적 분석을 수행하는 특수 에이전트들이 함께 작동하는 흥미로운 구조를 가지고 있습니다.

깃허브 주소: https://github.com/virattt/ai-hedge-fund.git

Benjamin Graham AI 에이전트: 가치 투자의 원칙을 코드로 구현하기

벤 그레이엄의 투자 원칙

벤 그레이엄의 투자 철학은 다음과 같은 핵심 원칙을 바탕으로 합니다:

  1. 안전마진(Margin of Safety): 본질적 가치보다 낮은 가격에 구매
  2. 재무적 안정성: 낮은 부채비율과 충분한 유동성
  3. 수익 안정성: 여러 해에 걸친 안정적인 수익
  4. 배당 기록: 안정적인 배당금 지급 이력
  5. 투기적 가정 배제: 높은 성장 가정보다 입증된 지표에 집중

코드로 보는 벤 그레이엄 에이전트

def ben_graham_agent(state: AgentState):
    """
    Analyzes stocks using Benjamin Graham's classic value-investing principles:
    1. Earnings stability over multiple years.
    2. Solid financial strength (low debt, adequate liquidity).
    3. Discount to intrinsic value (e.g. Graham Number or net-net).
    4. Adequate margin of safety.
    """
    data = state["data"]
    end_date = data["end_date"]
    tickers = data["tickers"]

    analysis_data = {}
    graham_analysis = {}

    for ticker in tickers:
        progress.update_status("ben_graham_agent", ticker, "Fetching financial metrics")
        metrics = get_financial_metrics(ticker, end_date, period="annual", limit=10)

        progress.update_status("ben_graham_agent", ticker, "Gathering financial line items")
        financial_line_items = search_line_items(ticker, ["earnings_per_share", "revenue", "net_income", "book_value_per_share", "total_assets", "total_liabilities", "current_assets", "current_liabilities", "dividends_and_other_cash_distributions", "outstanding_shares"], end_date, period="annual", limit=10)

        progress.update_status("ben_graham_agent", ticker, "Getting market cap")
        market_cap = get_market_cap(ticker, end_date)
        
        # 세부 분석 수행
        progress.update_status("ben_graham_agent", ticker, "Analyzing earnings stability")
        earnings_analysis = analyze_earnings_stability(metrics, financial_line_items)

        progress.update_status("ben_graham_agent", ticker, "Analyzing financial strength")
        strength_analysis = analyze_financial_strength(metrics, financial_line_items)

        progress.update_status("ben_graham_agent", ticker, "Analyzing Graham valuation")
        valuation_analysis = analyze_valuation_graham(metrics, financial_line_items, market_cap)

        # 종합 점수 계산
        total_score = earnings_analysis["score"] + strength_analysis["score"] + valuation_analysis["score"]
        max_possible_score = 15  # 세 분석 함수에서 획득 가능한 최대 점수
        
        # 총 점수를 신호로 변환
        if total_score >= 0.7 * max_possible_score:
            signal = "bullish"
        elif total_score <= 0.3 * max_possible_score:
            signal = "bearish"
        else:
            signal = "neutral"

이 코드는 벤 그레이엄의 투자 원칙을 따라 주식을 분석하는 에이전트의 핵심 로직입니다. 각 종목에 대해 다음과 같은 분석을 수행합니다:

  1. 재무 지표 수집
  2. 수익 안정성 분석
  3. 재무 건전성 분석
  4. 그레이엄 방식의 가치 평가
  5. 종합 점수 계산 및 투자 신호 생성

수익 안정성 분석

def analyze_earnings_stability(metrics: list, financial_line_items: list) -> dict:
    """
    Graham wants at least several years of consistently positive earnings (ideally 5+).
    We'll check:
    1. Number of years with positive EPS.
    2. Growth in EPS from first to last period.
    """
    score = 0
    details = []

    # 데이터 확인
    if not metrics or not financial_line_items:
        return {"score": score, "details": "Insufficient data for earnings stability analysis"}

    # EPS 값 추출
    eps_vals = []
    for item in financial_line_items:
        if item.earnings_per_share is not None:
            eps_vals.append(item.earnings_per_share)

    if len(eps_vals) < 2:
        details.append("Not enough multi-year EPS data.")
        return {"score": score, "details": "; ".join(details)}

    # 1. 지속적인 양의 EPS
    positive_eps_years = sum(1 for e in eps_vals if e > 0)
    total_eps_years = len(eps_vals)
    if positive_eps_years == total_eps_years:
        score += 3
        details.append("EPS was positive in all available periods.")
    elif positive_eps_years >= (total_eps_years * 0.8):
        score += 2
        details.append("EPS was positive in most periods.")
    else:
        details.append("EPS was negative in multiple periods.")

    # 2. 가장 오래된 기간부터 최신까지의 EPS 성장
    if eps_vals[-1] > eps_vals[0]:
        score += 1
        details.append("EPS grew from earliest to latest period.")
    else:
        details.append("EPS did not grow from earliest to latest period.")

    return {"score": score, "details": "; ".join(details)}

벤 그레이엄은 최소 5년 이상의 지속적인 양의 수익을 중요시했습니다. 이 함수는:
1. 양의 EPS가 있는 연도 수 확인
2. 첫 기간부터 마지막 기간까지의 EPS 성장 확인

을 통해 수익 안정성을 평가합니다.

재무 건전성 분석

def analyze_financial_strength(metrics: list, financial_line_items: list) -> dict:
    """
    Graham checks liquidity (current ratio >= 2), manageable debt,
    and dividend record (preferably some history of dividends).
    """
    score = 0
    details = []

    if not financial_line_items:
        return {"score": score, "details": "No data for financial strength analysis"}

    latest_item = financial_line_items[-1]
    total_assets = latest_item.total_assets or 0
    total_liabilities = latest_item.total_liabilities or 0
    current_assets = latest_item.current_assets or 0
    current_liabilities = latest_item.current_liabilities or 0

    # 1. 유동비율(Current ratio)
    if current_liabilities > 0:
        current_ratio = current_assets / current_liabilities
        if current_ratio >= 2.0:
            score += 2
            details.append(f"Current ratio = {current_ratio:.2f} (>=2.0: solid).")
        elif current_ratio >= 1.5:
            score += 1
            details.append(f"Current ratio = {current_ratio:.2f} (moderately strong).")
        else:
            details.append(f"Current ratio = {current_ratio:.2f} (<1.5: weaker liquidity).")
    
    # 2. 부채 대 자산 비율
    if total_assets > 0:
        debt_ratio = total_liabilities / total_assets
        if debt_ratio < 0.5:
            score += 2
            details.append(f"Debt ratio = {debt_ratio:.2f}, under 0.50 (conservative).")
        elif debt_ratio < 0.8:
            score += 1
            details.append(f"Debt ratio = {debt_ratio:.2f}, somewhat high but could be acceptable.")
        else:
            details.append(f"Debt ratio = {debt_ratio:.2f}, quite high by Graham standards.")
    
    # 3. 배당금 지급 이력
    div_periods = [item.dividends_and_other_cash_distributions for item in financial_line_items 
                  if item.dividends_and_other_cash_distributions is not None]
    if div_periods:
        # 많은 데이터 피드에서 배당금 유출은 음수로 표시됨
        div_paid_years = sum(1 for d in div_periods if d < 0)
        if div_paid_years > 0:
            # 예: 기간의 절반 이상에서 배당금을 지급한 경우
            if div_paid_years >= (len(div_periods) // 2 + 1):
                score += 1
                details.append("Company paid dividends in the majority of the reported years.")
            else:
                details.append("Company has some dividend payments, but not most years.")
        else:
            details.append("Company did not pay dividends in these periods.")
    
    return {"score": score, "details": "; ".join(details)}

재무 건전성 분석은 다음 세 가지 측면을 평가합니다:
1. 유동비율(Current ratio) - 2.0 이상이면 탁월
2. 부채 대 자산 비율 - 0.5 미만이면 보수적으로 양호
3. 배당금 지급 이력 - 기간의 절반 이상 배당금 지급 시 추가 점수

그레이엄 방식의 가치 평가

def analyze_valuation_graham(metrics: list, financial_line_items: list, market_cap: float) -> dict:
    """
    Core Graham approach to valuation:
    1. Net-Net Check: (Current Assets - Total Liabilities) vs. Market Cap
    2. Graham Number: sqrt(22.5 * EPS * Book Value per Share)
    3. Compare per-share price to Graham Number => margin of safety
    """
    # 데이터 확인
    if not financial_line_items or not market_cap or market_cap <= 0:
        return {"score": 0, "details": "Insufficient data to perform valuation"}

    latest = financial_line_items[-1]
    current_assets = latest.current_assets or 0
    total_liabilities = latest.total_liabilities or 0
    book_value_ps = latest.book_value_per_share or 0
    eps = latest.earnings_per_share or 0
    shares_outstanding = latest.outstanding_shares or 0

    details = []
    score = 0

    # 1. Net-Net 체크
    #   NCAV = Current Assets - Total Liabilities
    #   NCAV > Market Cap => 역사적으로 강한 매수 신호
    net_current_asset_value = current_assets - total_liabilities
    if net_current_asset_value > 0 and shares_outstanding > 0:
        net_current_asset_value_per_share = net_current_asset_value / shares_outstanding
        price_per_share = market_cap / shares_outstanding if shares_outstanding else 0

        details.append(f"Net Current Asset Value = {net_current_asset_value:,.2f}")
        details.append(f"NCAV Per Share = {net_current_asset_value_per_share:,.2f}")
        details.append(f"Price Per Share = {price_per_share:,.2f}")

        if net_current_asset_value > market_cap:
            score += 4  # 매우 강한 그레이엄 신호
            details.append("Net-Net: NCAV > Market Cap (classic Graham deep value).")
        else:
            # 부분적인 net-net 할인의 경우
            if net_current_asset_value_per_share >= (price_per_share * 0.67):
                score += 2
                details.append("NCAV Per Share >= 2/3 of Price Per Share (moderate net-net discount).")

    # 2. 그레이엄 넘버
    #   GrahamNumber = sqrt(22.5 * EPS * BVPS)
    #   현재 주가와 비교
    graham_number = None
    if eps > 0 and book_value_ps > 0:
        graham_number = math.sqrt(22.5 * eps * book_value_ps)
        details.append(f"Graham Number = {graham_number:.2f}")
    else:
        details.append("Unable to compute Graham Number (EPS or Book Value missing/<=0).")

    # 3. 그레이엄 넘버 대비 안전마진
    if graham_number and shares_outstanding > 0:
        current_price = market_cap / shares_outstanding
        if current_price > 0:
            margin_of_safety = (graham_number - current_price) / current_price
            details.append(f"Margin of Safety (Graham Number) = {margin_of_safety:.2%}")
            if margin_of_safety > 0.5:
                score += 3
                details.append("Price is well below Graham Number (>=50% margin).")
            elif margin_of_safety > 0.2:
                score += 1
                details.append("Some margin of safety relative to Graham Number.")
            else:
                details.append("Price close to or above Graham Number, low margin of safety.")

    return {"score": score, "details": "; ".join(details)}

그레이엄 방식의 가치 평가는 다음과 같은 핵심 접근법을 사용합니다:

  1. Net-Net 체크: (유동자산 - 총부채)와 시가총액 비교

    • 순유동자산가치(NCAV)가 시가총액보다 크면 강한 매수 신호
  2. 그레이엄 넘버: sqrt(22.5 EPS 주당 장부가치)

    • 그레이엄 넘버와 현재 주가 비교를 통한 안전마진 평가
  3. 안전마진 계산:

    • 주가가 그레이엄 넘버보다 50% 이상 낮으면 높은 점수
    • 20% 이상 낮으면 중간 점수

최종 투자 신호 생성

def generate_graham_output(
    ticker: str,
    analysis_data: dict[str, any],
    model_name: str,
    model_provider: str,
) -> BenGrahamSignal:
    """
    Generates an investment decision in the style of Benjamin Graham:
    - Value emphasis, margin of safety, net-nets, conservative balance sheet, stable earnings.
    - Return the result in a JSON structure: { signal, confidence, reasoning }.
    """

    template = ChatPromptTemplate.from_messages([
        (
            "system",
            """You are a Benjamin Graham AI agent, making investment decisions using his principles:
            1. Insist on a margin of safety by buying below intrinsic value (e.g., using Graham Number, net-net).
            2. Emphasize the company's financial strength (low leverage, ample current assets).
            3. Prefer stable earnings over multiple years.
            4. Consider dividend record for extra safety.
            5. Avoid speculative or high-growth assumptions; focus on proven metrics.
                        
            Return a rational recommendation: bullish, bearish, or neutral, with a confidence level (0-100) and concise reasoning.
            """
        ),
        (
            "human",
            """Based on the following analysis, create a Graham-style investment signal:

            Analysis Data for {ticker}:
            {analysis_data}

            Return JSON exactly in this format:
            {{
              "signal": "bullish" or "bearish" or "neutral",
              "confidence": float (0-100),
              "reasoning": "string"
            }}
            """
        )
    ])

    # LLM을 사용하여 분석 데이터 기반의 투자 신호 생성
    prompt = template.invoke({
        "analysis_data": json.dumps(analysis_data, indent=2),
        "ticker": ticker
    })

    # LLM 호출 및 결과 반환
    return call_llm(
        prompt=prompt,
        model_name=model_name,
        model_provider=model_provider,
        pydantic_model=BenGrahamSignal,
        agent_name="ben_graham_agent",
        default_factory=create_default_ben_graham_signal,
    )

마지막으로, 이 함수는 모든 분석 데이터를 바탕으로 LLM(대형 언어 모델)을 사용하여 벤 그레이엄 스타일의 투자 신호를 생성합니다. 결과는 다음 형식으로 반환됩니다:

{
  "signal": "bullish" 또는 "bearish" 또는 "neutral",
  "confidence": 0-100 사이의 float 값,
  "reasoning": "투자 결정에 대한 이유"
}
profile
터널을 지나고 있을 뿐, 길은 여전히 열려 있다.

0개의 댓글

관련 채용 정보