You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
stock-oracle/tests/test_15year_financial_data.py

453 lines
16 KiB
Python

#!/usr/bin/env python3
"""
15년 재무재표 데이터 가져오기 종합 테스트
다양한 방법으로 API를 테스트하고 문제점을 찾아 수정합니다.
"""
import requests
import yfinance_plus as yf
from datetime import datetime, timedelta
import json
import time
from typing import Dict, Any, List
# API 설정
API_URL = "http://localhost:18001/api/v1"
def print_section(title: str):
"""섹션 헤더 출력"""
print(f"\n{'='*80}")
print(f"🔍 {title}")
print(f"{'='*80}")
def print_test(test_name: str):
"""테스트 헤더 출력"""
print(f"\n📋 {test_name}")
print("-" * 60)
def test_yfinance_plus_availability():
"""YFinance Plus 사용 가능성 테스트"""
print_test("YFinance Plus 모듈 테스트")
try:
# 모듈 임포트 테스트
print("✅ yfinance_plus 모듈 임포트 성공")
# 기본 연결 테스트
stock = yf.Ticker("AAPL")
info = stock.info
print(f"✅ AAPL 기본 정보 가져오기 성공: {info.get('longName', 'N/A')}")
# 재무재표 가져오기 테스트
quarterly_income = stock.quarterly_income_stmt
print(f"✅ 분기별 손익계산서: {quarterly_income.shape}")
annual_income = stock.income_stmt
print(f"✅ 연간 손익계산서: {annual_income.shape}")
# 사용 가능한 기간 확인
if not quarterly_income.empty:
earliest = quarterly_income.columns[-1]
latest = quarterly_income.columns[0]
print(f"📅 사용 가능한 분기 데이터 기간: {earliest} ~ {latest}")
if not annual_income.empty:
earliest_annual = annual_income.columns[-1]
latest_annual = annual_income.columns[0]
print(f"📅 사용 가능한 연간 데이터 기간: {earliest_annual} ~ {latest_annual}")
return True
except Exception as e:
print(f"❌ YFinance Plus 테스트 실패: {str(e)}")
return False
def test_api_basic_connection():
"""API 기본 연결 테스트"""
print_test("API 기본 연결 테스트")
try:
# Health check
response = requests.get(f"{API_URL}/health", timeout=10)
if response.status_code == 200:
print("✅ Health check 성공")
else:
print(f"⚠️ Health check 응답: {response.status_code}")
# Database stats
response = requests.get(f"{API_URL}/database/stats", timeout=10)
if response.status_code == 200:
stats = response.json()
print(f"✅ DB 통계 조회 성공")
print(f" - 총 회사 수: {stats.get('companies', {}).get('total', 0)}")
print(f" - 재무 데이터: {stats.get('financial_data', {}).get('total_records', 0)}")
print(f" - 주가 데이터: {stats.get('price_data', {}).get('total_records', 0)}")
else:
print(f"❌ DB 통계 조회 실패: {response.status_code}")
return True
except Exception as e:
print(f"❌ API 연결 테스트 실패: {str(e)}")
return False
def test_current_financial_data():
"""현재 재무 데이터 조회 테스트"""
print_test("현재 재무 데이터 조회 테스트")
tickers = ["AAPL", "MSFT", "GOOGL"]
for ticker in tickers:
print(f"\n📊 {ticker} 테스트:")
# 최근 2년 데이터 테스트
request_data = {
"ticker": ticker,
"start_date": "2023-01-01",
"end_date": "2024-12-31",
"period_type": "all",
"include_metrics": True,
"force_refresh": False
}
try:
response = requests.post(
f"{API_URL}/financial/data",
json=request_data,
timeout=30
)
if response.status_code == 200:
data = response.json()
financial_data = data.get('financial_data', [])
print(f" ✅ 성공: {len(financial_data)}개 기간 데이터")
if financial_data:
real_data = sum(1 for d in financial_data if not d.get('is_estimated', True))
estimated_data = len(financial_data) - real_data
print(f" 📈 실제 데이터: {real_data}, 추정 데이터: {estimated_data}")
# 최신 데이터 확인
latest = financial_data[-1]
print(f" 📅 최신 기간: {latest.get('period_date')}")
print(f" 💰 매출: ${latest.get('revenue', 0):,.0f}")
print(f" 📊 데이터 소스: {latest.get('data_source')}")
else:
print(" ⚠️ 재무 데이터가 비어있음")
else:
print(f" ❌ 실패: {response.status_code}")
print(f" Error: {response.text[:200]}")
except Exception as e:
print(f" ❌ 예외: {str(e)}")
def test_15year_historical_data():
"""15년 과거 데이터 조회 테스트"""
print_test("15년 과거 재무 데이터 조회 테스트")
# 15년 전부터 현재까지
start_date = "2009-01-01"
end_date = "2024-12-31"
tickers = ["AAPL", "MSFT"]
for ticker in tickers:
print(f"\n🕰️ {ticker} 15년 데이터 테스트 ({start_date} ~ {end_date}):")
# 연간 데이터로 테스트
request_data = {
"ticker": ticker,
"start_date": start_date,
"end_date": end_date,
"period_type": "annual",
"include_metrics": True,
"force_refresh": True # 강제 새로고침으로 실제 데이터 가져오기
}
try:
print(f" ⏳ 요청 시작... (시간이 오래 걸릴 수 있습니다)")
start_time = time.time()
response = requests.post(
f"{API_URL}/financial/data",
json=request_data,
timeout=120 # 2분 타임아웃
)
end_time = time.time()
duration = end_time - start_time
if response.status_code == 200:
data = response.json()
financial_data = data.get('financial_data', [])
print(f" ✅ 성공: {len(financial_data)}개 연간 데이터 ({duration:.1f}초)")
if financial_data:
# 데이터 품질 분석
years = set()
real_count = 0
estimated_count = 0
for item in financial_data:
year = item.get('period_date', '')[:4]
years.add(year)
if item.get('is_estimated', True):
estimated_count += 1
else:
real_count += 1
print(f" 📊 커버리지: {len(years)}년 ({min(years)} ~ {max(years)})")
print(f" 📈 실제 데이터: {real_count}, 추정 데이터: {estimated_count}")
# 최신 및 가장 오래된 데이터 확인
oldest = financial_data[0]
newest = financial_data[-1]
print(f" 📅 가장 오래된 데이터: {oldest.get('period_date')} (매출: ${oldest.get('revenue', 0):,.0f})")
print(f" 📅 가장 최신 데이터: {newest.get('period_date')} (매출: ${newest.get('revenue', 0):,.0f})")
# 데이터 소스 분포
sources = {}
for item in financial_data:
source = item.get('data_source', 'Unknown')
sources[source] = sources.get(source, 0) + 1
print(f" 🔍 데이터 소스: {sources}")
else:
print(" ⚠️ 15년 데이터가 비어있음")
else:
print(f" ❌ 실패: {response.status_code} ({duration:.1f}초)")
print(f" Error: {response.text[:500]}")
except requests.exceptions.Timeout:
print(f" ⏰ 타임아웃: 2분 초과")
except Exception as e:
print(f" ❌ 예외: {str(e)}")
def test_quarterly_vs_annual():
"""분기별 vs 연간 데이터 비교 테스트"""
print_test("분기별 vs 연간 데이터 비교 테스트")
ticker = "AAPL"
period_range = {
"start_date": "2020-01-01",
"end_date": "2024-12-31"
}
results = {}
# 분기별 및 연간 데이터 테스트
for period_type in ["quarterly", "annual"]:
print(f"\n📊 {ticker} {period_type} 데이터:")
request_data = {
"ticker": ticker,
"period_type": period_type,
"include_metrics": True,
"force_refresh": False,
**period_range
}
try:
response = requests.post(
f"{API_URL}/financial/data",
json=request_data,
timeout=60
)
if response.status_code == 200:
data = response.json()
financial_data = data.get('financial_data', [])
results[period_type] = financial_data
print(f" ✅ 성공: {len(financial_data)}개 데이터")
if financial_data:
real_count = sum(1 for d in financial_data if not d.get('is_estimated', True))
print(f" 📈 실제 데이터: {real_count}/{len(financial_data)}")
# 날짜 범위 확인
dates = [d.get('period_date') for d in financial_data]
print(f" 📅 기간: {min(dates)} ~ {max(dates)}")
else:
print(f" ❌ 실패: {response.status_code}")
results[period_type] = []
except Exception as e:
print(f" ❌ 예외: {str(e)}")
results[period_type] = []
# 비교 분석
print(f"\n🔍 비교 분석:")
quarterly_count = len(results.get('quarterly', []))
annual_count = len(results.get('annual', []))
print(f" 분기별 데이터: {quarterly_count}")
print(f" 연간 데이터: {annual_count}")
print(f" 예상 비율: {quarterly_count/4:.1f}{annual_count} (이론적으로 4:1)")
def test_different_error_scenarios():
"""다양한 에러 시나리오 테스트"""
print_test("에러 시나리오 테스트")
error_tests = [
{
"name": "존재하지 않는 종목",
"data": {
"ticker": "NONEXISTENT",
"start_date": "2023-01-01",
"end_date": "2023-12-31",
"period_type": "quarterly"
}
},
{
"name": "잘못된 날짜 형식",
"data": {
"ticker": "AAPL",
"start_date": "invalid-date",
"end_date": "2023-12-31",
"period_type": "quarterly"
}
},
{
"name": "미래 날짜",
"data": {
"ticker": "AAPL",
"start_date": "2030-01-01",
"end_date": "2030-12-31",
"period_type": "quarterly"
}
},
{
"name": "극도로 오래된 날짜",
"data": {
"ticker": "AAPL",
"start_date": "1990-01-01",
"end_date": "1990-12-31",
"period_type": "quarterly"
}
}
]
for test in error_tests:
print(f"\n🚨 {test['name']} 테스트:")
try:
response = requests.post(
f"{API_URL}/financial/data",
json=test['data'],
timeout=30
)
print(f" 응답 코드: {response.status_code}")
if response.status_code == 200:
data = response.json()
financial_data = data.get('financial_data', [])
print(f" ✅ 성공: {len(financial_data)}개 데이터")
else:
print(f" ⚠️ 에러 응답: {response.text[:200]}")
except Exception as e:
print(f" ❌ 예외: {str(e)}")
def test_force_refresh_vs_cache():
"""강제 새로고침 vs 캐시 비교 테스트"""
print_test("강제 새로고침 vs 캐시 비교 테스트")
ticker = "AAPL"
request_base = {
"ticker": ticker,
"start_date": "2023-01-01",
"end_date": "2024-12-31",
"period_type": "quarterly",
"include_metrics": True
}
# 캐시 사용 테스트
print(f"\n🔄 캐시 사용 테스트:")
start_time = time.time()
response1 = requests.post(
f"{API_URL}/financial/data",
json={**request_base, "force_refresh": False},
timeout=60
)
cache_time = time.time() - start_time
if response1.status_code == 200:
data1 = response1.json()
print(f" ✅ 캐시 요청 성공: {len(data1.get('financial_data', []))}개 데이터 ({cache_time:.1f}초)")
else:
print(f" ❌ 캐시 요청 실패: {response1.status_code}")
# 강제 새로고침 테스트
print(f"\n🔄 강제 새로고침 테스트:")
start_time = time.time()
response2 = requests.post(
f"{API_URL}/financial/data",
json={**request_base, "force_refresh": True},
timeout=120
)
refresh_time = time.time() - start_time
if response2.status_code == 200:
data2 = response2.json()
print(f" ✅ 강제 새로고침 성공: {len(data2.get('financial_data', []))}개 데이터 ({refresh_time:.1f}초)")
# 성능 비교
print(f"\n⚡ 성능 비교:")
print(f" 캐시 사용: {cache_time:.1f}")
print(f" 강제 새로고침: {refresh_time:.1f}")
print(f" 속도 차이: {refresh_time/cache_time:.1f}배 느림")
else:
print(f" ❌ 강제 새로고침 실패: {response2.status_code}")
def main():
"""메인 테스트 실행"""
print_section("Stock Oracle 15년 재무재표 데이터 종합 테스트")
print(f"🚀 테스트 시작 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"🎯 API URL: {API_URL}")
# 테스트 실행
tests = [
("YFinance Plus 사용 가능성", test_yfinance_plus_availability),
("API 기본 연결", test_api_basic_connection),
("현재 재무 데이터 조회", test_current_financial_data),
("15년 과거 데이터 조회", test_15year_historical_data),
("분기별 vs 연간 데이터 비교", test_quarterly_vs_annual),
("에러 시나리오", test_different_error_scenarios),
("강제 새로고침 vs 캐시", test_force_refresh_vs_cache),
]
results = []
for test_name, test_func in tests:
print_section(test_name)
try:
result = test_func()
results.append((test_name, "성공" if result else "실패"))
except Exception as e:
print(f"{test_name} 실행 중 예외: {str(e)}")
results.append((test_name, "예외"))
# 최종 결과 요약
print_section("테스트 결과 요약")
for test_name, result in results:
status_icon = "" if result == "성공" else ""
print(f"{status_icon} {test_name}: {result}")
success_count = sum(1 for _, result in results if result == "성공")
total_count = len(results)
print(f"\n📊 전체 결과: {success_count}/{total_count} 성공 ({success_count/total_count*100:.1f}%)")
print(f"🕐 테스트 완료 시간: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
if __name__ == "__main__":
main()