금융 데이터 처리 시스템을 개발하다 보면 API에서 제공하는 원시 데이터(raw data)의 형태와 최종 출력 포맷 사이에 불일치가 발생할 수 있습니다. 특히 퍼센티지 값을 처리할 때 이런 문제가 자주 발생합니다. 이번 글에서는 Alpha Vantage API를 사용하면서 발생한 데이터 포맷팅 문제와 그 해결 과정을 공유하려고 합니다.
ROA
, ROE
등의 재무 지표가 출력될 때 값이 비정상적으로 크게 나타나는 문제를 발견했습니다. 예를 들어, ROE가 138%로 표시되어야 하는데 13800%로 표시되고 있었습니다. 이러한 문제의 원인을 파악하기 위해 원시 데이터 형태를 확인하는 작업부터 시작했습니다.
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에서 제공하는 원시 데이터의 형태와 그 값을 퍼센티지로 변환했을 때의 결과를 확인할 수 있습니다.
테스트 코드를 실행하여 얻은 로그는 다음과 같습니다:
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%
로그를 통해 다음 사항을 확인할 수 있었습니다:
즉, Alpha Vantage API는 모든 비율 값을 소수점 형태(0.xx)로 제공하고 있었습니다.
코드를 분석하여 문제의 원인을 파악했습니다. 다음과 같은 두 부분에서 값 변환이 중복으로 이루어지고 있었습니다:
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
)
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이 됨문제 해결을 위해 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을 곱하지 않고 단순히 퍼센트 기호만 추가하게 됩니다.
수정 후, 재무 지표가 올바르게 표시되는 것을 확인할 수 있었습니다:
### 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% 등 올바른 퍼센티지 값이 출력되는 것을 확인할 수 있었습니다.
데이터 원본 형태 확인의 중요성: API 응답 데이터의 형태와 의미를 명확히 이해하는 것이 중요합니다. 테스트 코드와 로깅을 통해 원시 데이터의 형태를 확인하는 습관을 가지면 예상치 못한 문제를 방지할 수 있습니다.
변환 로직의 중앙화: 데이터 변환 로직은 가능한 한 한 곳에서만 처리하는 것이 좋습니다. 여러 곳에서 동일한 변환을 수행하면 중복 변환이나 오류의 위험이 높아집니다.
함수 인터페이스 명확화: format_financial_value
함수의 경우, 인자로 include_percent=True
를 받을 때 어떤 형태의 값을 기대하는지 명확히 문서화했다면 이러한 문제를 예방할 수 있었을 것입니다.
테스트의 중요성: 실제 데이터로 테스트하고 결과를 검증하는 과정이 중요합니다. 작은 변환 오류가 큰 차이를 만들 수 있으며, 특히 금융 데이터에서는 정확성이 매우 중요합니다.
단계적 문제 해결 접근법: 문제 인식 → 테스트 코드 작성 → 원인 파악 → 해결책 구현 → 검증이라는 단계적 접근법이 복잡한 문제를 효과적으로 해결하는 데 도움이 됩니다.
이러한 교훈을 바탕으로, 앞으로 데이터 처리 시스템을 개발할 때 더 명확한 인터페이스 설계와 철저한 테스트를 통해 유사한 문제를 예방할 수 있을 것입니다.