Alpha Vantage API에서 미국 주식 분석기 만들기 10)

Tasker_Jang·2025년 5월 31일
0

🎯 배경: 왜 백테스팅이 필요했을까?

금융 시장 분석 AI 에이전트 시스템을 개발하던 중, 트레이딩팀에서 이런 요청이 들어왔습니다:

"현재 데이터만으로는 부족해요. 과거 특정 시점의 재무제표를 기준으로 분석해서 우리 전략을 백테스팅하고 싶어요."

기존 시스템은 가장 최근 데이터만 분석할 수 있었는데, 백테스팅을 위해서는 특정 날짜 기준의 히스토리컬 분석이 필요했습니다.

🏗️ 기존 시스템 구조

우리 시스템은 LangGraph 기반의 멀티 에이전트 아키텍처로 구성되어 있었습니다:

# 기존 구조
SupervisorNode → USFinancialAnalyzerNode → AlphaVantage API
  • LangGraph: 에이전트 간 상태 관리 및 워크플로우
  • AlphaVantage API: 미국 주식 재무제표 데이터
  • 상태 관리: 세션이 아닌 그래프 실행 단위로 상태 유지

기존 Tool의 한계

class USStockInput(BaseModel):
    query: str = Field(
        description="company name or ticker symbol (e.g., Apple, AAPL)"
    )

단순히 회사명이나 티커만 받아서 최신 데이터만 분석하는 구조였습니다.

💡 해결 방안: 프롬프트 기반 날짜 추출

복잡한 구조 변경보다는 LLM의 자연어 이해 능력을 활용하기로 했습니다.

1단계: 입력 스키마 확장

class USStockInput(BaseModel):
    query: str = Field(
        description="Query containing company name/ticker and optionally a specific date. "
                   "Examples: 'Apple', 'AAPL as of 2023-12-31', 'Microsoft in Q2 2023', "
                   "'Tesla 2022년 말 기준'"
    )

2단계: 프롬프트로 티커와 날짜 동시 추출

def _extract_ticker_and_date(self, query: str) -> Tuple[Optional[str], Optional[str]]:
    prompt = f"""
    Extract the ticker symbol and analysis date from this query:
    "{query}"
    
    Date conversion examples:
    - "Q1 2023" → "2023-03-31" (end of Q1)
    - "Q2 2023" → "2023-06-30" (end of Q2)  
    - "2023년 말" → "2023-12-31"
    - "as of 2023-12-31" → "2023-12-31"
    
    Return format: TICKER|DATE or TICKER|CURRENT
    """
    
    response = self.llm.invoke(prompt)
    result = response.content.strip().upper()
    
    if "|" in result:
        ticker, date = result.split("|", 1)
        return ticker, date if date != "CURRENT" else None

🚀 구현 결과

지원 가능한 쿼리 형태

# 현재 분석 (기존과 동일)
"Apple 분석해줘"
"MSFT 재무상태는?"

# 특정 날짜 분석 (NEW!)
"Apple as of 2023-12-31"
"Microsoft 2023년 2분기 기준"
"Tesla 2022년 말 재무상태"
"NVDA Q3 2023 financial performance"

실제 분석 결과

현재 분석:

애플(Apple Inc., Ticker: AAPL) 최신 재무제표 분석
- 기준일: 2024-09-30
- ROE: 138.0%, ROA: 23.8%
- 매출: $391,035백만

히스토리컬 분석:

📊 Historical Financial Analysis: AAPL as of 2023-12-31
- 기준일: 2023-09-30 (해당 날짜 기준 최신 데이터)
- ROE: 138.0%, ROA: 23.8%
- 매출: $383,285백만
*Note: 백테스팅 목적의 과거 시점 분석*

📊 발견한 흥미로운 사실들

미국 재무제표 공시 주기

구현 과정에서 알게 된 중요한 사실:

  • 연간 보고서(10-K): 1년에 1번, 회계연도 말 기준
  • 분기 보고서(10-Q): 1년에 3번 (Q1, Q2, Q3만)
  • Apple의 회계연도: 9월 30일 마감
2024-09-30 (Q4/연말) → 연간보고서
2024-06-30 (Q3)     → 분기보고서  
2024-03-31 (Q2)     → 분기보고서
2023-12-31 (Q1)     → 분기보고서

이로 인해 "분기별 백테스팅"보다는 "연도별 백테스팅"이 더 현실적임을 깨달았습니다.

🛠️ 기술적 도전과 해결책

1. 날짜 형식의 다양성

문제: 사용자가 다양한 형태로 날짜를 표현

  • "Q2 2023", "2023년 2분기", "June 2023", "2023-06-30"

해결: LLM 프롬프트에 변환 예시를 충분히 제공

# 프롬프트에 포함된 변환 예시
- "Q1 2023""2023-03-31" (end of Q1)
- "Q2 2023""2023-06-30" (end of Q2)  
- "2023년 말""2023-12-31"

2. API 응답 시간 최적화

문제: 히스토리컬 데이터 요청 시 응답 시간 증가

해결: 캐싱 시스템 활용

def _get_cached_or_fetch(self, endpoint: str, func, *args, **kwargs):
    cache_key = endpoint
    current_time = time.time()
    
    if (cache_key in self.cache and 
        current_time - self.cache_timestamp.get(cache_key, 0) < self.base_cache_time):
        return self.cache[cache_key]
    
    result = func(*args, **kwargs)
    self.cache[cache_key] = result
    return result

3. 데이터 일관성 보장

문제: 과거 시점에 실제로 공개된 데이터만 사용해야 함

해결: 엄격한 날짜 필터링

# 타겟 날짜보다 이후에 공시된 데이터는 제외
if report_date <= target_dt:
    valid_reports.append(report)

결과물

📚 배운 점들

1. 점진적 개발의 중요성

처음에는 복잡한 새 시스템을 구축하려 했지만, 기존 구조를 최대한 활용하는 방향이 더 효과적이었습니다.

2. LLM의 자연어 처리 능력 활용

복잡한 날짜 파싱 로직 대신 LLM 프롬프트로 해결한 것이 더 유연하고 확장 가능했습니다.

3. 도메인 지식의 중요성

미국 재무제표 공시 주기를 이해하고 나서야 현실적인 백테스팅 전략을 수립할 수 있었습니다.

profile
ML Engineer 🧠 | AI 모델 개발과 최적화 경험을 기록하며 성장하는 개발자 🚀 The light that burns twice as bright burns half as long ✨

0개의 댓글