이 시스템은 벤 그레이엄, 빌 액크만, 캐시 우드, 찰리 멍거, 스탠리 드러켄밀러, 워렌 버핏과 같은 전설적인 투자자들의 투자 철학을 모방한 에이전트들과 가치평가, 시장 심리, 기본적 분석, 기술적 분석을 수행하는 특수 에이전트들이 함께 작동하는 흥미로운 구조를 가지고 있습니다.
깃허브 주소: https://github.com/virattt/ai-hedge-fund.git
벤 그레이엄의 투자 철학은 다음과 같은 핵심 원칙을 바탕으로 합니다:
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"
이 코드는 벤 그레이엄의 투자 원칙을 따라 주식을 분석하는 에이전트의 핵심 로직입니다. 각 종목에 대해 다음과 같은 분석을 수행합니다:
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)}
그레이엄 방식의 가치 평가는 다음과 같은 핵심 접근법을 사용합니다:
Net-Net 체크: (유동자산 - 총부채)와 시가총액 비교
그레이엄 넘버: sqrt(22.5 EPS 주당 장부가치)
안전마진 계산:
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": "투자 결정에 대한 이유"
}