수정본

This commit is contained in:
2026-02-22 17:23:20 +09:00
parent b147498c31
commit 32e2263f0a
109 changed files with 13979 additions and 4580 deletions

57
tests/insert_test_data.py Normal file
View File

@@ -0,0 +1,57 @@
"""
테스트용 샘플 분석 데이터 생성 스크립트
실제 배치 대신 테스트용 데이터를 DB에 넣습니다.
"""
import psycopg2
from datetime import date
# DB 연결
conn = psycopg2.connect(
host="shium.net",
port=55432,
database="stock",
user="stock_admin",
password="stock_pass"
)
cur = conn.cursor()
# 테스트 데이터
test_data = [
('005930', '삼성전자', 58100, -1.11, 20000000, 22.5, 61200, 65400, 68200, 14.5, 18.2, 72000, 58000, 64500, ['RSI 과매도', '스토캐스틱 과매도'], 3),
('122630', 'KODEX 레버리지', 41140, -3.52, 40000000, 25.9, 43200, 45100, 47800, 21.9, 24.1, 49000, 39000, 44000, ['RSI 과매도', '스토캐스틱 과매도'], 3),
('462330', 'KODEX 2차전지산업레버리지', 1690, -6.63, 50000000, 20.5, 1850, 1920, 2100, 29.4, 32.1, 2200, 1600, 1900, ['RSI 과매도', '볼린저밴드 하단'], 3),
('000660', 'SK하이닉스', 179000, -2.15, 15000000, 28.3, 185000, 192000, 198000, 18.5, 22.3, 205000, 172000, 188500, ['RSI 과매도'], 2),
('035720', '카카오', 42500, 1.43, 8000000, 52.1, 41800, 40200, 38500, 65.2, 68.1, 46000, 38000, 42000, ['RSI 중립-상승', '골든크로스'], 2),
]
today = date.today()
print(f"🚀 Inserting test data for {today}...")
for stock in test_data:
cur.execute("""
INSERT INTO stock_analysis (
stock_code, stock_name, analysis_date, current_price, change_rate, volume,
rsi, ma5, ma20, ma60, stochastic_k, stochastic_d,
bollinger_upper, bollinger_lower, bollinger_middle,
signals, score
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT DO NOTHING
""", (
stock[0], stock[1], today, stock[2], stock[3], stock[4],
stock[5], stock[6], stock[7], stock[8], stock[9], stock[10],
stock[11], stock[12], stock[13], stock[14], stock[15]
))
print(f" ✅ {stock[0]} ({stock[1]})")
conn.commit()
cur.close()
conn.close()
print(f"\n✅ Test data inserted successfully!")
print(f"📊 Total: {len(test_data)} stocks")
print(f"\nNow test the API:")
print(f" GET http://localhost:8000/api/v1/recommendations/rule-based")

160
tests/test_api_safety.py Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
API 모니터링 테스트 스크립트
KIS API의 재시도 및 쓰로틀링 기능을 테스트합니다.
"""
import asyncio
import sys
from pathlib import Path
# 프로젝트 루트 추가
sys.path.insert(0, str(Path(__file__).parent.parent))
from app.services.korea_investment_api import KoreaInvestmentAPI
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
async def test_throttling():
"""쓰로틀링 테스트: 빠른 연속 호출"""
logger.info("=" * 60)
logger.info("쓰로틀링 테스트 시작: 5개 종목 연속 조회")
logger.info("=" * 60)
kis_api = KoreaInvestmentAPI()
test_stocks = [
"005930", # 삼성전자
"000660", # SK하이닉스
"035420", # 네이버
"051910", # LG화학
"005380", # 현대차
]
import time
start = time.time()
for stock_code in test_stocks:
logger.info(f"📊 Fetching {stock_code}...")
try:
result = await kis_api.get_current_price(stock_code)
if result and result.get("output"):
price = result["output"].get("stck_prpr", "N/A")
logger.info(f"✅ {stock_code}: {price}원")
except Exception as e:
logger.error(f"❌ {stock_code}: {e}")
elapsed = time.time() - start
logger.info(f"⏱️ Total time: {elapsed:.2f}초")
logger.info(f"📈 Expected: ~{len(test_stocks) * kis_api.min_request_interval:.1f}초 (쓰로틀링 적용)")
# 통계 출력
stats = kis_api.get_monitor_stats()
logger.info("=" * 60)
logger.info("📡 API 통계:")
logger.info(f" - 총 호출: {stats['total_calls']}건")
logger.info(f" - 오류: {stats['total_errors']}건")
logger.info(f" - 재시도: {stats['total_retries']}건")
logger.info(f" - 오류율: {stats['error_rate']:.1%}")
logger.info(f" - 평균 응답: {stats['avg_duration']:.2f}초")
logger.info("=" * 60)
async def test_retry():
"""재시도 테스트: 잘못된 종목 코드로 테스트"""
logger.info("=" * 60)
logger.info("재시도 테스트 시작: 오류 케이스")
logger.info("=" * 60)
kis_api = KoreaInvestmentAPI()
# 잘못된 종목 코드
invalid_code = "000000"
try:
logger.info(f"📊 Fetching invalid stock: {invalid_code}")
result = await kis_api.get_current_price(invalid_code)
logger.info(f"✅ Result: {result}")
except Exception as e:
logger.error(f"❌ Expected error: {e}")
# 통계 출력
stats = kis_api.get_monitor_stats()
logger.info("=" * 60)
logger.info("📡 API 통계:")
logger.info(f" - 총 호출: {stats['total_calls']}건")
logger.info(f" - 오류: {stats['total_errors']}건")
logger.info(f" - 재시도: {stats['total_retries']}건")
logger.info(f" - 오류율: {stats['error_rate']:.1%}")
logger.info("=" * 60)
async def test_concurrent():
"""동시 요청 테스트: 세마포어 확인"""
logger.info("=" * 60)
logger.info(f"동시 요청 테스트 시작 (최대 동시 요청: 2건)")
logger.info("=" * 60)
kis_api = KoreaInvestmentAPI()
test_stocks = [
"005930", # 삼성전자
"000660", # SK하이닉스
"035420", # 네이버
"051910", # LG화학
]
import time
start = time.time()
# 병렬 실행
tasks = [kis_api.get_current_price(code) for code in test_stocks]
results = await asyncio.gather(*tasks, return_exceptions=True)
elapsed = time.time() - start
success = sum(1 for r in results if not isinstance(r, Exception))
logger.info(f"✅ 성공: {success}/{len(test_stocks)}")
logger.info(f"⏱️ Total time: {elapsed:.2f}초")
# 통계 출력
stats = kis_api.get_monitor_stats()
logger.info("=" * 60)
logger.info("📡 API 통계:")
logger.info(f" - 총 호출: {stats['total_calls']}건")
logger.info(f" - 오류: {stats['total_errors']}건")
logger.info(f" - 재시도: {stats['total_retries']}건")
logger.info(f" - 오류율: {stats['error_rate']:.1%}")
logger.info(f" - 평균 응답: {stats['avg_duration']:.2f}초")
logger.info("=" * 60)
async def main():
"""메인 테스트 실행"""
logger.info("🚀 KIS API 안전 운영 테스트 시작")
logger.info("")
# 1. 쓰로틀링 테스트
await test_throttling()
await asyncio.sleep(2)
# 2. 재시도 테스트
await test_retry()
await asyncio.sleep(2)
# 3. 동시 요청 테스트
await test_concurrent()
logger.info("")
logger.info("✅ 모든 테스트 완료")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
DB 연결 테스트 스크립트
실행: python test_db_connection.py
"""
import asyncio
import asyncpg
async def test_connection():
print("🔵 Testing PostgreSQL connection...")
print("Host: shium.net")
print("Port: 55432")
print("Database: stock")
print("User: stock")
print()
try:
# 직접 asyncpg로 연결 시도
conn = await asyncpg.connect(
host='shium.net',
port=55432,
database='stock',
user='stock',
password='Tjduddn!!12',
timeout=10,
ssl=False,
)
print("✅ Connection successful!")
# 간단한 쿼리 테스트
version = await conn.fetchval('SELECT version()')
print(f"PostgreSQL version: {version}")
# 테이블 목록 조회
tables = await conn.fetch("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
""")
print(f"\nTables in database:")
for table in tables:
print(f" - {table['table_name']}")
await conn.close()
print("\n✅ Connection test completed successfully!")
except asyncpg.PostgresConnectionError as e:
print(f"❌ PostgreSQL connection error: {e}")
except ConnectionRefusedError as e:
print(f"❌ Connection refused: {e}")
print("Possible causes:")
print(" 1. PostgreSQL server is not running")
print(" 2. Firewall blocking port 55432")
print(" 3. PostgreSQL not configured to accept external connections")
print(" 4. Need SSH tunnel or VPN to access the server")
except TimeoutError as e:
print(f"❌ Connection timeout: {e}")
print("The server is not responding. Check network connectivity.")
except Exception as e:
print(f"❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(test_connection())