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.
453 lines
16 KiB
Python
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() |