배치 실계좌에서 종목조회로 변경
This commit is contained in:
18
backend/app/core/logging_config.py
Normal file
18
backend/app/core/logging_config.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""공통 로깅 설정 — KST(Asia/Seoul) 타임존 포매터 제공."""
|
||||
|
||||
import logging
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
KST = pytz.timezone("Asia/Seoul")
|
||||
|
||||
|
||||
class KSTFormatter(logging.Formatter):
|
||||
"""%(asctime)s 를 KST(UTC+9) 기준으로 출력하는 포매터."""
|
||||
|
||||
def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
|
||||
ct = datetime.datetime.fromtimestamp(record.created, tz=KST)
|
||||
if datefmt:
|
||||
return ct.strftime(datefmt)
|
||||
t = ct.strftime(self.default_time_format)
|
||||
return self.default_msec_format % (t, record.msecs)
|
||||
@@ -27,22 +27,24 @@ from app.core.market_schedule import is_market_open
|
||||
# 로깅 설정
|
||||
import os
|
||||
import logging.handlers
|
||||
from app.core.logging_config import KSTFormatter
|
||||
_log_level = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO)
|
||||
_log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
_log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "logs")
|
||||
os.makedirs(_log_dir, exist_ok=True)
|
||||
_log_file = os.path.join(_log_dir, "backend.log")
|
||||
|
||||
logging.basicConfig(
|
||||
level=_log_level,
|
||||
format=_log_format,
|
||||
handlers=[
|
||||
logging.StreamHandler(),
|
||||
logging.handlers.RotatingFileHandler(
|
||||
_log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
|
||||
),
|
||||
]
|
||||
)
|
||||
_formatter = KSTFormatter(_log_format)
|
||||
_handlers: list[logging.Handler] = [
|
||||
logging.StreamHandler(),
|
||||
logging.handlers.RotatingFileHandler(
|
||||
_log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
|
||||
),
|
||||
]
|
||||
for _h in _handlers:
|
||||
_h.setFormatter(_formatter)
|
||||
|
||||
logging.basicConfig(level=_log_level, handlers=_handlers)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -97,7 +97,41 @@ class KoreaInvestmentAPI:
|
||||
|
||||
# 토큰 캐시 파일 경로 — 사용자 홈 디렉토리 하위에 저장 (/tmp는 보안 취약)
|
||||
TOKEN_CACHE_FILE = Path.home() / ".cache" / "auto-stock" / "kis_token_cache.json"
|
||||
|
||||
|
||||
@classmethod
|
||||
def for_real_market_data(cls) -> "KoreaInvestmentAPI":
|
||||
"""실계좌 자격증명으로 시세조회 전용 인스턴스 생성.
|
||||
|
||||
모의투자 모드에서도 실계좌 API로 시세를 조회할 때 사용한다.
|
||||
(모의투자 서버는 KOSDAQ 거래량 순위 등 일부 조회 API를 지원하지 않음)
|
||||
주문 API는 절대 호출하지 말 것.
|
||||
"""
|
||||
if not settings.KIS_REAL_APP_KEY:
|
||||
raise ValueError("KIS_REAL_APP_KEY가 설정되지 않아 실계좌 시세조회를 사용할 수 없습니다.")
|
||||
instance = cls.__new__(cls)
|
||||
instance.base_url = settings.KIS_REAL_URL
|
||||
instance.app_key = settings.KIS_REAL_APP_KEY
|
||||
instance.app_secret = settings.KIS_REAL_APP_SECRET
|
||||
instance.account_number = settings.KIS_REAL_ACCOUNT_NO
|
||||
instance.access_token = None
|
||||
instance.token_expires_at = None
|
||||
instance.last_request_time = None
|
||||
instance.TOKEN_CACHE_FILE = Path.home() / ".cache" / "auto-stock" / "kis_token_cache_real.json"
|
||||
instance.min_request_interval = settings.API_MIN_REQUEST_INTERVAL
|
||||
instance.max_retries = settings.API_MAX_RETRIES
|
||||
instance.retry_delay = settings.API_RETRY_DELAY
|
||||
instance.retry_backoff = settings.API_RETRY_BACKOFF
|
||||
instance.timeout = settings.API_TIMEOUT
|
||||
instance.semaphore = asyncio.Semaphore(settings.API_MAX_CONCURRENT_REQUESTS)
|
||||
instance._token_lock = asyncio.Lock()
|
||||
instance.monitor = APICallMonitor(window_size=settings.API_MONITORING_WINDOW)
|
||||
logger.info(
|
||||
"🔧 KIS 실계좌 시세조회 API 초기화: base_url=%s, app_key=%s...",
|
||||
instance.base_url, instance.app_key[:6]
|
||||
)
|
||||
instance._load_token_from_cache()
|
||||
return instance
|
||||
|
||||
def __init__(self):
|
||||
self.base_url = settings.KOREA_INVESTMENT_API_BASE_URL
|
||||
self.app_key = settings.KOREA_INVESTMENT_APP_KEY
|
||||
@@ -106,20 +140,20 @@ class KoreaInvestmentAPI:
|
||||
self.access_token: Optional[str] = None
|
||||
self.token_expires_at: Optional[datetime] = None
|
||||
self.last_request_time: Optional[float] = None
|
||||
|
||||
|
||||
# 설정 기반 쓰로틀링 파라미터
|
||||
self.min_request_interval = settings.API_MIN_REQUEST_INTERVAL # 기본 0.5초 (초당 2회)
|
||||
self.max_retries = settings.API_MAX_RETRIES # 기본 3회
|
||||
self.retry_delay = settings.API_RETRY_DELAY # 기본 1.5초
|
||||
self.retry_backoff = settings.API_RETRY_BACKOFF # 기본 2.0배
|
||||
self.timeout = settings.API_TIMEOUT # 기본 30초
|
||||
|
||||
|
||||
# 동시 요청 제한 (세마포어)
|
||||
self.semaphore = asyncio.Semaphore(settings.API_MAX_CONCURRENT_REQUESTS)
|
||||
|
||||
|
||||
# 토큰 발급 Lock (중복 발급 방지)
|
||||
self._token_lock = asyncio.Lock()
|
||||
|
||||
|
||||
# 모니터링
|
||||
self.monitor = APICallMonitor(window_size=settings.API_MONITORING_WINDOW)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user