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

Tasker_Jang·2025년 5월 14일
2

금융 데이터 처리 시스템을 개발하다 보면 API에서 제공하는 원시 데이터(raw data)의 형태와 최종 출력 포맷 사이에 불일치가 발생할 수 있습니다. 특히 퍼센티지 값을 처리할 때 이런 문제가 자주 발생합니다. 이번 글에서는 Alpha Vantage API를 사용하면서 발생한 데이터 포맷팅 문제와 그 해결 과정을 공유하려고 합니다.

1. 문제 인식

ROA, ROE 등의 재무 지표가 출력될 때 값이 비정상적으로 크게 나타나는 문제를 발견했습니다. 예를 들어, ROE가 138%로 표시되어야 하는데 13800%로 표시되고 있었습니다. 이러한 문제의 원인을 파악하기 위해 원시 데이터 형태를 확인하는 작업부터 시작했습니다.

2. 원시 데이터 검사 테스트 코드 작성

Alpha Vantage API에서 제공하는 데이터의 형태를 확인하기 위해 다음과 같은 테스트 코드를 작성했습니다:

"""Raw financial metrics test for Alpha Vantage API."""

import os
import sys
import unittest
from dotenv import load_dotenv
from src.tools.us_stock.alpha_vantage_client import AlphaVantageAPIWrapper

# Load .env file
load_dotenv()

# Set path
sys.path.append("..")  # Add parent directory


class TestRawFinancialMetrics(unittest.TestCase):
    """Test class for inspecting raw financial metrics"""

    def setUp(self):
        """Test setup"""
        # Check if API key exists in environment variables
        self.api_key_exists = "ALPHA_VANTAGE_API_KEY" in os.environ

        if not self.api_key_exists:
            self.skipTest("API key not configured")
        
        # Initialize API client
        self.api = AlphaVantageAPIWrapper()

    def test_inspect_raw_financial_metrics_aapl(self):
        """Test raw financial metrics inspection for AAPL"""
        # This test will log raw financial metrics for AAPL
        result = self.api.get_company_overview("AAPL")
        
        # Log the raw financial metrics
        self.api.log_raw_financial_metrics(result)
        
        # Basic assertions to make sure the test is meaningful
        self.assertIn("ReturnOnEquityTTM", result)
        self.assertIn("ReturnOnAssetsTTM", result)
        self.assertIn("OperatingMarginTTM", result)
        
        # Additional outputs for inspection
        print("\n===== Raw Financial Metrics for AAPL =====")
        for key in ["ReturnOnEquityTTM", "ReturnOnAssetsTTM", "OperatingMarginTTM", "ProfitMargin"]:
            if key in result:
                print(f"{key}: {result[key]}")
                # Try to convert to float and multiply by 100
                try:
                    value = float(result[key])
                    print(f"{key} as float: {value}")
                    print(f"{key} x 100: {value * 100}")
                except (ValueError, TypeError):
                    print(f"Cannot convert {key} to float")

    def test_inspect_raw_financial_metrics_msft(self):
        """Test raw financial metrics inspection for MSFT"""
        # Test with a different company for comparison
        result = self.api.get_company_overview("MSFT")
        
        # Log the raw financial metrics
        self.api.log_raw_financial_metrics(result)
        
        # Basic assertions
        self.assertIn("ReturnOnEquityTTM", result)
        self.assertIn("ReturnOnAssetsTTM", result)
        
        # Additional outputs for inspection
        print("\n===== Raw Financial Metrics for MSFT =====")
        for key in ["ReturnOnEquityTTM", "ReturnOnAssetsTTM", "OperatingMarginTTM", "ProfitMargin"]:
            if key in result:
                print(f"{key}: {result[key]}")
                # Try to convert to float and multiply by 100
                try:
                    value = float(result[key])
                    print(f"{key} as float: {value}")
                    print(f"{key} x 100: {value * 100}")
                except (ValueError, TypeError):
                    print(f"Cannot convert {key} to float")


if __name__ == "__main__":
    unittest.main()

이 테스트 코드를 통해 Alpha Vantage API에서 제공하는 원시 데이터의 형태와 그 값을 퍼센티지로 변환했을 때의 결과를 확인할 수 있습니다.

3. 원시값 출력 로그

테스트 코드를 실행하여 얻은 로그는 다음과 같습니다:

2025-05-14 19:49:50,839 - src.tools.us_stock.alpha_vantage_client - INFO - ====== RAW FINANCIAL METRICS ======
2025-05-14 19:49:50,839 - src.tools.us_stock.alpha_vantage_client - INFO - ReturnOnEquityTTM: Raw Value = 1.38, Float Value = 1.38
2025-05-14 19:49:50,840 - src.tools.us_stock.alpha_vantage_client - INFO - ReturnOnEquityTTM as percentage (x100): 138.00%
2025-05-14 19:49:50,840 - src.tools.us_stock.alpha_vantage_client - INFO - ReturnOnAssetsTTM: Raw Value = 0.238, Float Value = 0.238
2025-05-14 19:49:50,840 - src.tools.us_stock.alpha_vantage_client - INFO - ReturnOnAssetsTTM as percentage (x100): 23.80%
2025-05-14 19:49:50,840 - src.tools.us_stock.alpha_vantage_client - INFO - OperatingMarginTTM: Raw Value = 0.31, Float Value = 0.31
2025-05-14 19:49:50,841 - src.tools.us_stock.alpha_vantage_client - INFO - OperatingMarginTTM as percentage (x100): 31.00%
2025-05-14 19:49:50,841 - src.tools.us_stock.alpha_vantage_client - INFO - ProfitMargin: Raw Value = 0.243, Float Value = 0.243
2025-05-14 19:49:50,841 - src.tools.us_stock.alpha_vantage_client - INFO - ProfitMargin as percentage (x100): 24.30%

4. 문제 원인

로그를 통해 다음 사항을 확인할 수 있었습니다:

  • ROE(ReturnOnEquityTTM)는 1.38이라는 소수점 형태로 제공됨 (퍼센트로는 138%)
  • ROA(ReturnOnAssetsTTM)는 0.238이라는 소수점 형태로 제공됨 (퍼센트로는 23.8%)
  • OperatingMargin은 0.31이라는 소수점 형태로 제공됨 (퍼센트로는 31%)
  • ProfitMargin은 0.243이라는 소수점 형태로 제공됨 (퍼센트로는 24.3%)

즉, Alpha Vantage API는 모든 비율 값을 소수점 형태(0.xx)로 제공하고 있었습니다.

코드를 분석하여 문제의 원인을 파악했습니다. 다음과 같은 두 부분에서 값 변환이 중복으로 이루어지고 있었습니다:

  1. alpha_vantage_profitability.py 파일에서 API로부터 받은 값에 100을 곱하여 퍼센트 값으로 변환
if roe is not None:
    roe_value = roe * 100
    analysis["roe"] = api_wrapper.format_financial_value(
        roe_value, include_dollar=False, include_percent=True
    )
  1. alpha_vantage_client.py 파일의 format_financial_value 함수에서도 include_percent=True일 때 또다시 100을 곱하는 코드가 있었음
if include_percent:
    return f"{float_value * 100:.2f}%"

이로 인해 원시값에 100을 두 번 곱하는 문제가 발생했습니다. 예를 들어, ROE의 원시값이 1.38일 때:

  • alpha_vantage_profitability.py에서 100을 곱하면 138이 됨
  • 그 다음 format_financial_value 함수에서 다시 100을 곱하면 13800이 됨
  • 결과적으로 ROE가 13800%로 표시됨 (정상적으로는 138%여야 함)

5. 수정

문제 해결을 위해 alpha_vantage_client.py 파일의 format_financial_value 함수를 수정했습니다. 퍼센트 형식으로 포맷팅할 때 더 이상 값에 100을 곱하지 않도록 변경했습니다.

수정 전:

def format_financial_value(
    self, value: Any, include_dollar: bool = True, include_percent: bool = False
) -> str:
    """
    Format financial values.
    Return 'No data' for None values.
    """
    if value is None:
        return "No data"

    try:
        float_value = self.safe_float_or_empty(value)
        if float_value is None:
            return "No data"

        if include_percent:
            return f"{float_value * 100:.2f}%"
        elif include_dollar:
            return f"${float_value:,.2f}"
        else:
            return f"{float_value:,.2f}"
    except (ValueError, TypeError):
        return "No data"

수정 후:

def format_financial_value(
    self, value: Any, include_dollar: bool = True, include_percent: bool = False
) -> str:
    """
    Format financial values.
    Return 'No data' for None values.
    """
    if value is None:
        return "No data"

    try:
        float_value = self.safe_float_or_empty(value)
        if float_value is None:
            return "No data"

        # 수정: include_percent=True일 경우 100을 곱하지 않음
        # 호출 코드에서 이미 100을 곱했기 때문
        if include_percent:
            return f"{float_value:.2f}%"
        elif include_dollar:
            return f"${float_value:,.2f}"
        else:
            return f"{float_value:,.2f}"
    except (ValueError, TypeError):
        return "No data"

이 수정으로 호출 코드에서 이미 100을 곱한 값을 전달받은 경우, format_financial_value 함수는 추가로 100을 곱하지 않고 단순히 퍼센트 기호만 추가하게 됩니다.

6. 해결

수정 후, 재무 지표가 올바르게 표시되는 것을 확인할 수 있었습니다:

### Financial Statement Analysis for Apple Inc (Ticker: AAPL)

#### Company Profile
- **Sector**: Technology
- **Industry**: Electronic Computers
- **Market Capitalization**: $3.18 Trillion

#### 📈 Profitability Analysis
**Overall Assessment**: Exceptional profitability with industry-leading performance.

- **Return on Equity (ROE)**: **138.00%**
- **Return on Assets (ROA)**: **23.80%**

- **Margin Analysis**:
 - **Gross Margin**: **46.21%** - Excellent, strong pricing power.
 - **Operating Margin**: **31.51%** - Elite efficiency and cost control.
 - **Net Margin**: **23.97%** - Represents an elite level of profitability.
 - **EBITDA Margin**: **34.44%** - Exceptional margin indicative of operational excellence.

이제 ROE는 138.00%, ROA는 23.80% 등 올바른 퍼센티지 값이 출력되는 것을 확인할 수 있었습니다.

결론

  1. 데이터 원본 형태 확인의 중요성: API 응답 데이터의 형태와 의미를 명확히 이해하는 것이 중요합니다. 테스트 코드와 로깅을 통해 원시 데이터의 형태를 확인하는 습관을 가지면 예상치 못한 문제를 방지할 수 있습니다.

  2. 변환 로직의 중앙화: 데이터 변환 로직은 가능한 한 한 곳에서만 처리하는 것이 좋습니다. 여러 곳에서 동일한 변환을 수행하면 중복 변환이나 오류의 위험이 높아집니다.

  3. 함수 인터페이스 명확화: format_financial_value 함수의 경우, 인자로 include_percent=True를 받을 때 어떤 형태의 값을 기대하는지 명확히 문서화했다면 이러한 문제를 예방할 수 있었을 것입니다.

  4. 테스트의 중요성: 실제 데이터로 테스트하고 결과를 검증하는 과정이 중요합니다. 작은 변환 오류가 큰 차이를 만들 수 있으며, 특히 금융 데이터에서는 정확성이 매우 중요합니다.

  5. 단계적 문제 해결 접근법: 문제 인식 → 테스트 코드 작성 → 원인 파악 → 해결책 구현 → 검증이라는 단계적 접근법이 복잡한 문제를 효과적으로 해결하는 데 도움이 됩니다.

이러한 교훈을 바탕으로, 앞으로 데이터 처리 시스템을 개발할 때 더 명확한 인터페이스 설계와 철저한 테스트를 통해 유사한 문제를 예방할 수 있을 것입니다.

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

0개의 댓글