fix: CRITICAL/HIGH 버그 30건 수정 + 전체 코드 주석 추가 + 중복 정리

## CRITICAL 수정 (20건)
- day_trading: global 선언 누락, loss_tracker 연동, ban 체크 추가
- trade_executor: BUY 체결가 order_price로 수정, remaining_pct 누적 계산
- risk_validator: get_current_price dict 파싱, MAX_POSITION_SIZE 동기화, DB 세션 누수
- risk_management/recommendations: date.today() → KST 변환
- mdd_manager: Lock 밖 executed_key 참조 수정
- correlation_filter: output → output2 키 수정
- portfolio: 해외 종목 USD/KRW 환율 적용 분리
- Sharpe Ratio 거래빈도 기반 연간화
- telegram_notifier: send_message() 공개 래퍼 추가
- position_manager: 앱 재시작 시 DB 포지션 복원
- swing exit_service: remaining_pct 조건 범위 수정
- run_backtest: original_qty 기반 remaining_pct 계산
- us_market_schedule: 2027년 미국 공휴일 추가
- Login.tsx: res.ok 검증 추가
- mean_reversion_strategy: RSI 로그 하드코딩 수정

## HIGH 수정 (10건)
- db_worker: DLQ 파일 백업 메커니즘 추가
- korea_investment_api: 만료 토큰 임의 연장 제거
- market_data: chart_cache 최대 100개 + TTL 만료
- main.py: rate_limit_store 1시간 미접근 IP 정리
- recommendations: fire-and-forget try/except 추가
- swing/scheduler: DB 세션 3개 분리
- Dashboard: wsMessages 전체 순회
- Layout: /health 엔드포인트 + 30초 폴링
- ml_ensemble: POST /train require_api_key 추가
- day_trading: record_trade_result_safe() 전환

## 코드 정리
- 프론트엔드: isUsStock, formatTime 공통 유틸 추출
- 백엔드: dead code 제거, 중복 import 통합
- MCP: JSON 파싱 헬퍼 추출 (~60줄 감소)
- 전체 ~100개 파일 한국어 주석 추가
- 테스트 13건 코드 기준 수정 (468 passed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 16:05:41 +09:00
parent 739a8a2e5d
commit de7921d270
149 changed files with 133353 additions and 3338 deletions

View File

@@ -0,0 +1,273 @@
"""미국 시장 스케줄 관리
- 미국 정규장: 09:30~16:00 ET (한국시간 23:30~06:00, 서머타임 시 22:30~05:00)
- 프리마켓: 04:00~09:30 ET
- 애프터마켓: 16:00~20:00 ET
- 미국 공휴일 관리
"""
import logging
from datetime import datetime, time, timedelta
from typing import Optional
import pytz
logger = logging.getLogger(__name__)
# ── 타임존 ────────────────────────────────────────────────────────────────────
ET = pytz.timezone("US/Eastern")
KST = pytz.timezone("Asia/Seoul")
# ── 미국 시장 시간 (Eastern Time) ─────────────────────────────────────────────
PREMARKET_OPEN = time(4, 0) # 04:00 ET
MARKET_OPEN = time(9, 30) # 09:30 ET
MARKET_CLOSE = time(16, 0) # 16:00 ET
AFTERMARKET_CLOSE = time(20, 0) # 20:00 ET
# ── 2026년 미국 주식시장 휴장일 ──────────────────────────────────────────────
# NYSE/NASDAQ 공식 휴장일 (반장 제외)
US_HOLIDAYS_2026 = {
# New Year's Day
datetime(2026, 1, 1).date(),
# Martin Luther King Jr. Day (3rd Monday of January)
datetime(2026, 1, 19).date(),
# Presidents' Day (3rd Monday of February)
datetime(2026, 2, 16).date(),
# Good Friday
datetime(2026, 4, 3).date(),
# Memorial Day (last Monday of May)
datetime(2026, 5, 25).date(),
# Juneteenth National Independence Day
datetime(2026, 6, 19).date(),
# Independence Day (observed)
datetime(2026, 7, 3).date(),
# Labor Day (1st Monday of September)
datetime(2026, 9, 7).date(),
# Thanksgiving Day (4th Thursday of November)
datetime(2026, 11, 26).date(),
# Christmas Day
datetime(2026, 12, 25).date(),
}
# 추가 연도 지원을 위한 동적 휴일 계산
US_HOLIDAYS_2025 = {
datetime(2025, 1, 1).date(),
datetime(2025, 1, 20).date(),
datetime(2025, 2, 17).date(),
datetime(2025, 4, 18).date(),
datetime(2025, 5, 26).date(),
datetime(2025, 6, 19).date(),
datetime(2025, 7, 4).date(),
datetime(2025, 9, 1).date(),
datetime(2025, 11, 27).date(),
datetime(2025, 12, 25).date(),
}
# ── 2027년 미국 주식시장 휴장일 ──────────────────────────────────────────────
# NYSE/NASDAQ 공식 휴장일 (반장 제외)
US_HOLIDAYS_2027 = {
# New Year's Day (Friday)
datetime(2027, 1, 1).date(),
# Martin Luther King Jr. Day (3rd Monday of January)
datetime(2027, 1, 18).date(),
# Presidents' Day (3rd Monday of February)
datetime(2027, 2, 15).date(),
# Good Friday (Easter 2027-03-28 기준)
datetime(2027, 3, 26).date(),
# Memorial Day (last Monday of May)
datetime(2027, 5, 31).date(),
# Juneteenth (June 19 = Saturday → Friday June 18 observed)
datetime(2027, 6, 18).date(),
# Independence Day (July 4 = Sunday → Monday July 5 observed)
datetime(2027, 7, 5).date(),
# Labor Day (1st Monday of September)
datetime(2027, 9, 6).date(),
# Thanksgiving Day (4th Thursday of November)
datetime(2027, 11, 25).date(),
# Christmas Day (December 25 = Saturday → Friday December 24 observed)
datetime(2027, 12, 24).date(),
}
_ALL_HOLIDAYS = US_HOLIDAYS_2025 | US_HOLIDAYS_2026 | US_HOLIDAYS_2027
def _get_holidays_set():
"""holidays 패키지가 있으면 사용, 없으면 하드코딩 세트 반환."""
try:
import holidays as holidays_pkg
return holidays_pkg.US(years=[2025, 2026, 2027, 2028], categories={"public"})
except ImportError:
return None
def _now_et() -> datetime:
"""현재 시각을 US/Eastern 기준으로 반환 (timezone-aware)."""
return datetime.now(ET)
def is_dst() -> bool:
"""현재 미국이 서머타임(DST) 기간인지 확인.
DST 기간: 3월 둘째 일요일 ~ 11월 첫째 일요일
DST 기간에는 한국과의 시차가 13시간 (비DST: 14시간).
Returns:
True if 현재 DST 적용 중
"""
now = _now_et()
# pytz의 dst()는 DST 오프셋을 반환 (0이면 비DST)
return bool(now.dst())
def is_us_holiday(date: Optional[datetime] = None) -> bool:
"""해당 날짜가 미국 주식시장 휴장일인지 확인.
Args:
date: 확인할 날짜. None이면 현재 ET 날짜 사용.
datetime 또는 date 객체 모두 허용.
Returns:
True if 휴장일
"""
if date is None:
check_date = _now_et().date()
elif isinstance(date, datetime):
check_date = date.date()
else:
check_date = date
# holidays 패키지 시도
holidays_cal = _get_holidays_set()
if holidays_cal is not None:
return check_date in holidays_cal
# 하드코딩 fallback
return check_date in _ALL_HOLIDAYS
def is_us_market_open() -> bool:
"""미국 정규장이 현재 열려있는지 확인.
조건:
- 평일 (월~금)
- 휴장일 아님
- 09:30 ET <= 현재시각 < 16:00 ET
Returns:
True if 정규 거래시간
"""
now = _now_et()
# 주말 체크 (토: 5, 일: 6)
if now.weekday() >= 5:
return False
# 휴장일 체크
if is_us_holiday(now):
return False
current_time = now.time()
return MARKET_OPEN <= current_time < MARKET_CLOSE
def is_us_premarket() -> bool:
"""미국 프리마켓 시간인지 확인.
프리마켓: 04:00 ~ 09:30 ET (평일, 비휴장일)
Returns:
True if 프리마켓 시간
"""
now = _now_et()
if now.weekday() >= 5:
return False
if is_us_holiday(now):
return False
current_time = now.time()
return PREMARKET_OPEN <= current_time < MARKET_OPEN
def is_us_aftermarket() -> bool:
"""미국 애프터마켓 시간인지 확인.
애프터마켓: 16:00 ~ 20:00 ET (평일, 비휴장일)
Returns:
True if 애프터마켓 시간
"""
now = _now_et()
if now.weekday() >= 5:
return False
if is_us_holiday(now):
return False
current_time = now.time()
return MARKET_CLOSE <= current_time < AFTERMARKET_CLOSE
def get_next_us_market_open() -> datetime:
"""다음 미국 정규장 개장 시각 반환 (timezone-aware, US/Eastern).
현재 정규장이 열려있으면 오늘 개장 시각을 반환.
그렇지 않으면 다음 거래일의 09:30 ET을 반환.
Returns:
다음 개장 시각 (datetime, US/Eastern timezone)
"""
now = _now_et()
today_open = now.replace(hour=9, minute=30, second=0, microsecond=0)
# 오늘이 거래일이고 아직 개장 전이면 → 오늘 09:30
if now.weekday() < 5 and not is_us_holiday(now) and now.time() < MARKET_OPEN:
return ET.localize(today_open.replace(tzinfo=None)) if today_open.tzinfo is None else today_open
# 오늘이 거래일이고 정규장 중이면 → 오늘 09:30
if is_us_market_open():
return ET.localize(today_open.replace(tzinfo=None)) if today_open.tzinfo is None else today_open
# 다음 거래일 탐색 (최대 10일 — 연휴 대비)
next_day = now + timedelta(days=1)
for _ in range(10):
next_day = next_day.replace(hour=9, minute=30, second=0, microsecond=0)
if next_day.weekday() < 5 and not is_us_holiday(next_day):
if next_day.tzinfo is None:
return ET.localize(next_day)
return next_day
next_day += timedelta(days=1)
# fallback: 내일 09:30 (이론적으로 도달 불가)
fallback = (now + timedelta(days=1)).replace(hour=9, minute=30, second=0, microsecond=0)
return ET.localize(fallback.replace(tzinfo=None)) if fallback.tzinfo is None else fallback
def get_us_market_status() -> dict:
"""현재 미국 시장 상태 요약 반환.
Returns:
dict with keys:
- status: 'OPEN', 'PREMARKET', 'AFTERMARKET', 'CLOSED'
- is_dst: bool
- current_et: str (현재 ET 시각)
- next_open: str (다음 개장 시각)
"""
now = _now_et()
if is_us_market_open():
status = "OPEN"
elif is_us_premarket():
status = "PREMARKET"
elif is_us_aftermarket():
status = "AFTERMARKET"
else:
status = "CLOSED"
return {
"status": status,
"is_dst": is_dst(),
"current_et": now.strftime("%Y-%m-%d %H:%M:%S %Z"),
"next_open": get_next_us_market_open().strftime("%Y-%m-%d %H:%M:%S %Z"),
}