Files
auto-stock/backend/app/core/config.py
shuim de7921d270 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>
2026-03-26 16:05:41 +09:00

433 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
애플리케이션 설정 모듈
- pydantic-settings 기반 .env 파일 로드
- DB, Redis, KIS API, AI 프로바이더 등 전역 설정 관리
"""
import os
from pathlib import Path
from pydantic_settings import BaseSettings
from functools import lru_cache
# 프로젝트 루트 디렉토리 (backend의 상위 디렉토리)
BASE_DIR = Path(__file__).resolve().parent.parent.parent
BACKEND_ENV_FILE = BASE_DIR / ".env"
class Settings(BaseSettings):
# ═════════════════════════════════════════════════════════════════════════════
# 서버 & 환경 설정
# ═════════════════════════════════════════════════════════════════════════════
APP_NAME: str = "Auto Stock Trading System"
ENVIRONMENT: str = "development"
DEBUG: bool = False # 운영 기본값 False (.env에서 DEBUG=true로 개발 모드 활성화)
SECRET_KEY: str = "change-this-secret-key" # JWT/WS 서명용
API_KEY: str = "" # API 인증용 (비어있으면 SECRET_KEY 폴백)
BACKEND_HOST: str = "0.0.0.0"
BACKEND_PORT: int = 8000
# ═════════════════════════════════════════════════════════════════════════════
# 데이터베이스 & 메시지 큐
# ═════════════════════════════════════════════════════════════════════════════
DATABASE_URL: str = "postgresql://autostock:autostock_password@localhost:5432/autostock_db"
REDIS_URL: str = "redis://localhost:6379/0"
RABBITMQ_URL: str = "amqp://autostock:autostock_password@localhost:5672/"
# ═════════════════════════════════════════════════════════════════════════════
# KIS API 설정 (Korea Investment Securities)
# ═════════════════════════════════════════════════════════════════════════════
# 모드 선택: "virtual" (모의투자) 또는 "real" (실전투자)
KIS_MODE: str = "virtual"
# 모의투자 설정
KIS_VIRTUAL_APP_KEY: str = ""
KIS_VIRTUAL_APP_SECRET: str = ""
KIS_VIRTUAL_ACCOUNT_NO: str = ""
KIS_VIRTUAL_URL: str = "https://openapivts.koreainvestment.com:29443"
# 실전투자 설정
KIS_REAL_APP_KEY: str = ""
KIS_REAL_APP_SECRET: str = ""
KIS_REAL_ACCOUNT_NO: str = ""
KIS_REAL_URL: str = "https://openapi.koreainvestment.com:9443"
# 실제 사용되는 KIS API 설정 (KIS_MODE에 따라 자동 결정)
KOREA_INVESTMENT_APP_KEY: str = ""
KOREA_INVESTMENT_APP_SECRET: str = ""
KOREA_INVESTMENT_ACCOUNT_NUMBER: str = ""
KOREA_INVESTMENT_API_BASE_URL: str = "https://openapi.koreainvestment.com:9443"
# 레거시 호환 (직접 지정 시 우선)
KIS_APP_KEY: str = ""
KIS_APP_SECRET: str = ""
KIS_ACCOUNT_NO: str = ""
KIS_BASE_URL: str = ""
# WebSocket 관련
KIS_WS_APPROVAL_KEY: str = "" # 수동 지정 시 우선, 비어 있으면 자동 발급
KIS_WS_REAL_URL: str = "wss://openapi.koreainvestment.com:21000/websocket"
KIS_WS_VIRTUAL_URL: str = "wss://openapivts.koreainvestment.com:29443/websocket"
KIS_WS_ENABLED: bool = True # WebSocket 실시간 스트림 활성화
KIS_SYNC_INTERVAL: int = 10 # 구독 목록 동기화 주기 (초)
KIS_RECONNECT_WAIT: int = 5 # 재연결 대기 시간 (초)
KIS_APPROVAL_KEY_TTL: int = 82800 # approval_key 캐시 유효시간 (초, 23시간)
# 기타 외부 API
DATAGOKR_API_KEY: str = "" # 공공데이터포털 금융위원회 주식시세 API
# ═════════════════════════════════════════════════════════════════════════════
# AI & MCP 설정
# ═════════════════════════════════════════════════════════════════════════════
AI_PROVIDER: str = "gemini" # "gemini" 또는 "claude" (CLAUDE.md 1.2)
ANTHROPIC_API_KEY: str = ""
GEMINI_API_KEY: str = ""
GEMINI_MODEL: str = "gemini-2.0-flash"
MCP_SERVER_URL: str = "http://localhost:8001"
MCP_API_KEY: str = "" # MCP 서버 인증 키 (backend -> MCP 통신 시 X-API-Key 헤더)
MCP_VALIDATION_ENABLED: bool = True # 기본 ON — AI 검증 없이 자동매매 차단
MCP_CONFIDENCE_THRESHOLD: float = 0.5 # AI 신뢰도 최소값 (0.0~1.0)
MCP_ANALYSIS_MIN_SCORE: int = 70 # 기본 70점 — .env에서 MCP_ANALYSIS_MIN_SCORE=N 으로 조정
NEWS_API_KEY: str = ""
NEWS_ENABLED: bool = True # 뉴스 수집/감성 분석 활성화
NEWS_COLLECT_INTERVAL: int = 1800 # 뉴스 수집 주기 (초, 기본 30분)
NEWS_CACHE_TTL: int = 1800 # Redis 캐시 TTL (초, 30분)
NEWS_BLOCK_SCORE: float = -0.5 # 이 이하 → 진입 차단
NEWS_REDUCE_SCORE: float = -0.3 # 이 이하 → 포지션 50% 축소
NEWS_MAX_ARTICLES: int = 20 # 종목당 수집할 최대 뉴스 개수
# ═════════════════════════════════════════════════════════════════════════════
# 토큰 & JSON 최적화
# ═════════════════════════════════════════════════════════════════════════════
TOKEN_SAVE_MODE: bool = False # True: JSON 압축 + 차트 30일 제한 (CLAUDE.md 1.2)
# ═════════════════════════════════════════════════════════════════════════════
# 기술적 지표 기본값
# ═════════════════════════════════════════════════════════════════════════════
RSI_PERIOD: int = 14 # RSI 계산 기간
RSI_OVERSOLD_LEVEL: float = 30.0 # RSI 과매도 수준
RSI_OVERBOUGHT_LEVEL: float = 70.0 # RSI 과매수 수준
RSI_RECOVERY_RANGE_MIN: float = 30.0 # RSI 회복 범위 최소값
RSI_RECOVERY_RANGE_MAX: float = 45.0 # RSI 회복 범위 최대값
RSI_STABLE_RANGE_MIN: float = 45.0 # RSI 안정 범위 최소값
RSI_STABLE_RANGE_MAX: float = 60.0 # RSI 안정 범위 최대값
ATR_PERIOD: int = 14 # ATR 계산 기간
BOLLINGER_PERIOD: int = 20 # 볼린저 밴드 기간 (20일)
BOLLINGER_STDDEV: float = 2.0 # 볼린저 밴드 표준편차 배수
MA_SHORT_PERIOD: int = 5 # 단기 이동평균 기간 (5일)
MA_MEDIUM_PERIOD: int = 20 # 중기 이동평균 기간 (20일)
MA_LONG_PERIOD: int = 60 # 장기 이동평균 기간 (60일)
# ═════════════════════════════════════════════════════════════════════════════
# 공통 리스크 관리 (섹션 5)
# ═════════════════════════════════════════════════════════════════════════════
MAX_POSITION_SIZE: float = 0.06 # 백테스트 최적: 종목당 6% (기존 4%, 적응형 모드에서 오버라이드)
MAX_HOLDINGS_COUNT: int = 20 # 동시 보유 최대 20종목 (섹션 5.1)
LEVERAGE_ETF_MAX_RATIO: float = 0.20 # 레버리지/인버스 ETF 최대 20% (섹션 5.1)
# 자본 배분 글로벌 가드 — 전 전략 합산 현금 사용 상한
MAX_TOTAL_CAPITAL_USAGE: float = 0.95 # 전체 자본의 95%까지만 투자 (5% 현금 보유)
# 수수료/세금 (net PnL 계산용)
KR_COMMISSION_RATE: float = 0.00015 # 매수+매도 각 0.015%
KR_SELL_TAX_RATE_KOSPI: float = 0.0018 # 코스피 증권거래세 0.18%
KR_SELL_TAX_RATE_KOSDAQ: float = 0.0023 # 코스닥 증권거래세 0.23%
US_COMMISSION_RATE: float = 0.0 # 미국 주식 수수료 (증권사별 상이)
# 손절(SL) 및 익절(TP) 기본값
STOP_LOSS_PERCENT: float = 7.0 # 백테스트 최적: 저변동 종목 손절 7% (기존 6%, 적응형 모드에서 오버라이드)
STOP_LOSS_HIGH_VOL: float = 10.0 # 고변동 종목 손절 10% (양수로 저장, 사용 시 음수 변환)
TAKE_PROFIT_PERCENT: float = 5.0 # 익절 기본값
ATR_STOP_MULTIPLIER: float = 2.5 # 2.5 × ATR 손절 (섹션 5.2)
ATR_TRAILING_MULTIPLIER: float = 1.5 # 1.5 × ATR 트레일링
# MDD(최대 낙폭) 관리 (섹션 5.3)
MDD_PAUSE_THRESHOLD: float = -10.0 # -10% MDD: 신규진입 중단 + 30% 축소
MDD_FORCE_THRESHOLD: float = -20.0 # -20% MDD: 50% 강제 축소
# 가격 필터 (섹션 5.4)
MIN_PRICE_KRW: int = 5000 # 국내 5,000원 이하 매수 금지
MIN_PRICE_USD: float = 4.0 # 미국 $4 이하 매수 금지
# 연속 손실 관리 (섹션 5.5)
CONSECUTIVE_LOSS_BAN_COUNT: int = 3 # 3회 연속 손실
CONSECUTIVE_LOSS_BAN_DAYS: int = 30 # 30거래일 진입 금지
# ═════════════════════════════════════════════════════════════════════════════
# 평균회귀(스윙) 전략 (섹션 6)
# ═════════════════════════════════════════════════════════════════════════════
MEAN_REVERSION_RSI_THRESHOLD: float = 46.0 # 백테스트 최적: BULL 진입 RSI < 46 (기존 40)
MEAN_REVERSION_MULTIPLIER_STRONG: float = 1.50 # 강한 신호 배수 (+50%)
MEAN_REVERSION_MULTIPLIER_NORMAL: float = 1.25 # 중간 신호 배수 (+25%)
MEAN_REVERSION_MULTIPLIER_WEAK: float = 1.00 # 약한 신호 배수 (기본값)
MEAN_REVERSION_MAX_CAPITAL: float = 0.30 # 전체 자본의 30% 이내
MEAN_REVERSION_MA200_PERIOD: int = 200 # MA200 기간
MEAN_REVERSION_MA200_LOOKBACK: int = 205 # MA200 계산용 데이터 요구량
MEAN_REVERSION_SLOPE_THRESHOLD: float = 0.001 # MA200 기울기 횡보 판별 (0.1%)
MEAN_REVERSION_ADX_SIDEWAYS: float = 20.0 # ADX < 20 → SIDEWAYS
MEAN_REVERSION_BEAR_RSI_THRESHOLD: float = 35.0 # 백테스트 최적: BEAR 진입 RSI < 35 (기존 30)
MEAN_REVERSION_BEAR_VOLUME_MULT: float = 1.5 # BEAR 거래량 > 1.5 × 평균
MEAN_REVERSION_BB_UPPER_THRESHOLD: float = 0.99 # BB 상단 도달 임계값 (99%)
MEAN_REVERSION_RSI_EXIT_THRESHOLD: float = 55.0 # 백테스트 최적: 1차 익절 RSI >= 55 (기존 50)
MEAN_REVERSION_RSI_FULL_EXIT: float = 70.0 # 백테스트 최적: 전량 청산 RSI >= 70 (기존 65)
MEAN_REVERSION_RSI_STRONG_THRESHOLD: float = 20.0 # RSI <= 20 → 강한 신호
MEAN_REVERSION_RSI_NORMAL_THRESHOLD: float = 25.0 # RSI <= 25 → 중간 신호
MEAN_REVERSION_ADX_STRONG_THRESHOLD: float = 15.0 # ADX < 15 → 추세 약함
MEAN_REVERSION_ADX_STRONG_REDUCE: float = 30.0 # ADX > 30 → 추세 강함
# ═════════════════════════════════════════════════════════════════════════════
# 단기(15분봉) 전략 (섹션 7.2)
# ═════════════════════════════════════════════════════════════════════════════
SHORT_TERM_MIN_ATR_RATIO: float = 0.008 # ATR/close ≥ 0.8% : 거래비용 커버
SHORT_TERM_RSI_LOOKBACK: int = 3 # RSI 반등 확인용 과거 봉 수
SHORT_TERM_VOLUME_MULTIPLIER: float = 1.3 # 거래량 > 1.3배 증가
SHORT_TERM_SKIP_OPEN_HOUR: int = 9 # 장 초반 제외 시간
SHORT_TERM_SKIP_OPEN_MIN: int = 30 # 장 초반 제외 분 (09:30 이전 제외)
SHORT_TERM_SKIP_CLOSE_HOUR: int = 15 # 마감 제외 시간
SHORT_TERM_SKIP_CLOSE_MIN: int = 15 # 마감 제외 분 (15:15 이후 제외)
SHORT_TERM_BB_LOWER_BUFFER: float = 1.01 # BB 하단 근처 버퍼 (+1%)
SHORT_TERM_HARD_STOP_PCT: float = 0.02 # 하드 손절 -2%
SHORT_TERM_ATR_PROFIT_MULTIPLIER: float = 1.5 # 익절 목표 = entry + 1.5×ATR
SHORT_TERM_COMMISSION_RATE: float = 0.003 # 수수료율
SHORT_TERM_SLIPPAGE_RATE: float = 0.002 # 슬리피지율
# ═════════════════════════════════════════════════════════════════════════════
# 데이트레이딩(5분봉) 전략 (섹션 7.3)
# ═════════════════════════════════════════════════════════════════════════════
DAY_TRADE_ENABLED: bool = True # 데이트레이딩 활성화
DAY_TRADE_MAX_POSITIONS: int = 3 # 동시 최대 종목 수
DAY_TRADE_CAPITAL_RATIO: float = 0.25 # 전체 자본 대비 최대 25%
DAY_TRADE_PER_STOCK_RATIO: float = 0.04 # 종목당 최대 4%
DAY_TRADE_MAX_CAPITAL_RATIO: float = 0.04 # 종목당 최대 4% 자본
DAY_TRADE_MAX_RATIO_DT: float = 0.25 # 전체 데이트레이딩 최대 25%
DAY_TRADE_STOP_LOSS_PCT: float = 0.02 # 종목 손절 -2%
DAY_TRADE_DAILY_LOSS_LIMIT: float = -0.03 # 일일 손실 한도 -3%
DAY_TRADE_EOD_FLAT_HOUR: int = 15 # EOD 강제 청산 시간 (15시)
DAY_TRADE_EOD_FLAT_MIN: int = 20 # EOD 강제 청산 분 (20분)
DAY_TRADE_ORB_VOL_MULT: float = 1.5 # ORB 돌파 거래량 배수
DAY_TRADE_TRAILING_TRIGGER: float = 0.01 # 트레일링 발동 최소 수익률 (1%)
DAY_TRADE_RSI_BREAKEVEN_THRESHOLD: float = 40.0 # RSI_BB 진입 기준 (RSI ≤ 40)
DAY_TRADE_ORB_STOP_LOSS_PCT: float = 0.02 # ORB 손절 -2%
DAY_TRADE_REVERSION_STOP_LOSS_PCT: float = 0.025 # VWAP/RSI_BB 손절 -2.5%
DAY_TRADE_TRAILING_ATR_MULT: float = 1.5 # 트레일링 ATR 배수 (1.0→1.5 whipsaw 방지)
DAY_TRADE_ORB_BREAKOUT_MARGIN: float = 0.002 # ORB 돌파 최소 여유 +0.2%
DAY_TRADE_COST_PCT: float = 0.005 # 수수료+슬리피지 합산 (매수+매도)
DAY_TRADE_ENTRY_CUTOFF_HOUR: int = 14 # 신규 진입 마감 시간 (CLAUDE.md 7.3: 14:30)
DAY_TRADE_ENTRY_CUTOFF_MIN: int = 30 # 신규 진입 마감 분
DAY_TRADE_EOD_HOUR: int = 15 # EOD 강제 청산 시작 시간
DAY_TRADE_EOD_MIN: int = 20 # EOD 강제 청산 시작 분
DAY_TRADE_ORB_BREAKOUT_VOL_MULT: float = 1.5 # ORB 거래량 배수 (vs 분당평균)
DAY_TRADE_VWAP_PRICE_LOWER: float = 0.99 # VWAP -1% 하단 경계
DAY_TRADE_VWAP_PRICE_UPPER: float = 1.005 # VWAP +0.5% 상단 경계
DAY_TRADE_VWAP_RSI_THRESHOLD: float = 40.0 # VWAP RSI <= 40
DAY_TRADE_RSI_BB_RSI_THRESHOLD: float = 30.0 # RSI_BB RSI <= 30
DAY_TRADE_RSI_BB_BB_LOWER_BUFFER: float = 1.01 # RSI_BB BB 하단 +1% 버퍼
DAY_TRADE_VOLUME_MULTIPLIER: float = 1.3 # 거래량 배수 (1.3배 이상)
DAY_TRADE_ORB_ATR_FLOOR_MULT: float = 1.0 # ORB ATR 손절 기본 배수
DAY_TRADE_VWAP_ATR_MULT: float = 1.5 # VWAP ATR 손절 배수
DAY_TRADE_RSI_BB_ATR_MULT: float = 1.5 # RSI_BB ATR 손절 배수
DAY_TRADE_TARGET_MULTIPLIER: float = 2.0 # 목표가 = entry + risk × 2.0R
DAY_TRADE_TIME_STOP_MINUTES: int = 30 # 시간 손절 (30분)
DAY_TRADE_TIME_STOP_MIN_GAIN: float = 0.005 # 시간 손절 최소 수익 +0.5% (수수료 커버)
DAY_TRADE_ORB_MARGIN_CHECK: float = 0.03 # ORB 목표가 최소 마진 (3%)
DAY_TRADE_TRAILING_DEFAULT: float = 0.02 # 트레일링 기본값 -2% (ATR 없을 때)
DAY_TRADE_TRAILING_MIN_HOLD_SEC: int = 300 # 트레일링 활성화 최소 보유 시간 (초, 5분)
# ═════════════════════════════════════════════════════════════════════════════
# 코스닥 스캐너 (섹션 7.1)
# ═════════════════════════════════════════════════════════════════════════════
# (코스닥 스캐너 파라미터 추가 필요 시 여기에 추가)
# ═════════════════════════════════════════════════════════════════════════════
# 미국 주식 매매 (섹션 7.4)
# ═════════════════════════════════════════════════════════════════════════════
US_TRADING_ENABLED: bool = False # 미국 주식 자동매매 활성화 (기본 OFF)
US_MAX_CAPITAL_RATIO: float = 0.30 # 전체 자본 대비 미국 주식 최대 비중 (30%)
US_SCAN_INTERVAL: int = 300 # 미국장 스캔 간격 (초, 기본 5분)
# ═════════════════════════════════════════════════════════════════════════════
# 필터 & 시장 국면 (섹션 5.4)
# ═════════════════════════════════════════════════════════════════════════════
TREND_FILTER_ENABLED: bool = False
TREND_MA_SHORT: int = 20
TREND_MA_LONG: int = 60
TREND_TOLERANCE_PCT: float = 0.005
OVERHEAT_FILTER_ENABLED: bool = True # 급등 과열 필터
OVERHEAT_3DAY_MAX: float = 15.0
OVERHEAT_5DAY_MAX: float = 20.0
OVERHEAT_10DAY_MAX: float = 30.0
OVERHEAT_DISPARITY_MAX: float = 108.0
BEAR_MARKET_FILTER_ENABLED: bool = True # 하락장 방어 필터
BEAR_MARKET_MIN_SCORE: int = 5
BEAR_MARKET_MAX_TRADES: int = 3
BEAR_MARKET_POSITION_RATIO: float = 0.5
# ═════════════════════════════════════════════════════════════════════════════
# 시장 상황 & 비상 정지
# ═════════════════════════════════════════════════════════════════════════════
TRADING_HALTED: bool = False # .env에서 TRADING_HALTED=true 로 즉시 전체 매수 중단
MARKET_CRASH_HALT_PCT: float = -3.0 # 지수 당일 -3% 이상 급락 시 자동 매수 차단
# ═════════════════════════════════════════════════════════════════════════════
# 일반 트레이딩 설정
# ═════════════════════════════════════════════════════════════════════════════
AUTO_TRADE_ENABLED: bool = True
MAX_DAILY_TRADES: int = 5
MAX_DAILY_BUYS_PER_STOCK: int = 2 # 종목당 일일 최대 매수 횟수
ANALYSIS_STOCK_COUNT: int = 300 # 배치 분석 종목 수
AUTO_TRADE_TOP_N_PER_RULE: int = 2 # 규칙당 자동매매 실행 최대 종목 수
# ═════════════════════════════════════════════════════════════════════════════
# 감시 목록 (watchlist.py)
# ═════════════════════════════════════════════════════════════════════════════
WATCHLIST_INIT_SIZE: int = 150 # 아침 배치 초기화 종목 수
WATCHLIST_MAX_SIZE: int = 150 # 최대 관리 종목 수
WATCHLIST_ENTRY_SCORE: int = 60 # 장중 신규 진입 기준 점수
WATCHLIST_EXIT_SCORE: int = 20 # 장중 제거 기준 점수
# ═════════════════════════════════════════════════════════════════════════════
# ML / 머신러닝
# ═════════════════════════════════════════════════════════════════════════════
ML_TF_ENABLED: bool = True # ML 모델 기반 보조지표 사용
ML_ENSEMBLE_ENABLED: bool = True # ML Ensemble (LightGBM + LSTM + RF)
ML_ENSEMBLE_MODEL_DIR: str = "models/ensemble"
ML_ENSEMBLE_RULE_WEIGHT: float = 0.3 # 룰베이스 스코어 비중
ML_ENSEMBLE_ML_WEIGHT: float = 0.7 # ML 스코어 비중
ML_ENSEMBLE_BUY_THRESHOLD: int = 60 # 매수 최소 앙상블 스코어 (0~100)
ML_ENSEMBLE_CONFIDENCE_MIN: float = 0.5 # 최소 신뢰도
# ═════════════════════════════════════════════════════════════════════════════
# API & 네트워크
# ═════════════════════════════════════════════════════════════════════════════
API_MAX_RETRIES: int = 3 # 최대 재시도 횟수
API_RETRY_DELAY: float = 1.5 # 초기 재시도 대기 시간 (초)
API_RETRY_BACKOFF: float = 2.0 # 지수 백오프 배율
API_MIN_REQUEST_INTERVAL: float = 0.15 # 최소 요청 간격 (초) — 기존 0.5→0.15 (캐시+커넥션풀로 부하 감소)
API_MAX_CONCURRENT_REQUESTS: int = 8 # 동시 최대 요청 수 (기존 4→8, 커넥션풀로 안정적)
API_TIMEOUT: float = 10.0 # API 타임아웃 (초) — 기존 30→10 (블로킹 방지)
API_ERROR_THRESHOLD: float = 0.3 # 오류율 임계값 (30%)
API_MONITORING_WINDOW: int = 100 # 모니터링 윈도우 (최근 N개 요청)
# ═════════════════════════════════════════════════════════════════════════════
# Telegram 알림
# ═════════════════════════════════════════════════════════════════════════════
TELEGRAM_BOT_TOKEN: str = "" # 텔레그램 봇 토큰 (빈 문자열 = 비활성화)
TELEGRAM_CHAT_ID: str = "" # 알림 수신 채팅 ID (빈 문자열 = 비활성화)
# ═════════════════════════════════════════════════════════════════════════════
# 로깅 & 모니터링
# ═════════════════════════════════════════════════════════════════════════════
LOG_LEVEL: str = "INFO"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._apply_kis_mode()
self._apply_docker_db_override()
def _apply_docker_db_override(self):
"""If running inside Docker, convert localhost in DATABASE_URL to the
postgres service hostname.
Users often copy `.env.example` which defaults to
`postgresql://...@localhost:5432/...`. That works on the host machine,
but inside the backend container `localhost` refers to the container
itself, not the Postgres server. When `/.dockerenv` exists we assume the
code is running in Docker and automatically patch the URL.
"""
if os.path.exists("/.dockerenv"):
# simple string replacement; pydantic does not throw if format
# changes after initialization
if "localhost" in self.DATABASE_URL:
self.DATABASE_URL = self.DATABASE_URL.replace("localhost", "postgres")
def _apply_kis_mode(self):
"""KIS_MODE에 따라 모의/실전 API 설정을 자동 선택"""
is_real = self.KIS_MODE.lower() == "real"
# 1순위: KIS_MODE에 따른 VIRTUAL/REAL 변수
if is_real:
mode_key = self.KIS_REAL_APP_KEY
mode_secret = self.KIS_REAL_APP_SECRET
mode_account = self.KIS_REAL_ACCOUNT_NO
mode_url = self.KIS_REAL_URL
else:
mode_key = self.KIS_VIRTUAL_APP_KEY
mode_secret = self.KIS_VIRTUAL_APP_SECRET
mode_account = self.KIS_VIRTUAL_ACCOUNT_NO
mode_url = self.KIS_VIRTUAL_URL
# 2순위: 레거시 KIS_APP_KEY 등 (직접 지정된 경우)
fallback_key = self.KIS_APP_KEY
fallback_secret = self.KIS_APP_SECRET
fallback_account = self.KIS_ACCOUNT_NO
fallback_url = self.KIS_BASE_URL
# 최종 적용 (mode > legacy > KOREA_INVESTMENT_ 기존값)
self.KOREA_INVESTMENT_APP_KEY = mode_key or fallback_key or self.KOREA_INVESTMENT_APP_KEY
self.KOREA_INVESTMENT_APP_SECRET = mode_secret or fallback_secret or self.KOREA_INVESTMENT_APP_SECRET
self.KOREA_INVESTMENT_ACCOUNT_NUMBER = mode_account or fallback_account or self.KOREA_INVESTMENT_ACCOUNT_NUMBER
self.KOREA_INVESTMENT_API_BASE_URL = mode_url or fallback_url or self.KOREA_INVESTMENT_API_BASE_URL
@property
def is_virtual_trading(self) -> bool:
"""모의투자 모드 여부"""
return self.KIS_MODE.lower() != "real"
class Config:
env_file = str(BACKEND_ENV_FILE) if BACKEND_ENV_FILE.exists() else ".env"
case_sensitive = True
extra = "ignore" # .env 파일의 추가 필드 무시
env_file_encoding = "utf-8"
def validate_required(self) -> list[str]:
"""필수 환경변수 누락 여부를 검사하고 경고 목록을 반환한다.
시작 시 lifespan에서 호출하여 운영자에게 조기 경보를 제공한다.
누락 시 예외를 발생시키지 않으므로 서비스 기동은 계속된다.
"""
warnings: list[str] = []
# KIS API 키: 현재 모드에 맞는 키 필수
if not self.KOREA_INVESTMENT_APP_KEY:
warnings.append(
f"KIS_{self.KIS_MODE.upper()}_APP_KEY 미설정 — "
f"{'KIS_VIRTUAL_APP_KEY' if self.is_virtual_trading else 'KIS_REAL_APP_KEY'} 확인 필요"
)
if not self.KOREA_INVESTMENT_APP_SECRET:
warnings.append(
f"KIS_{self.KIS_MODE.upper()}_APP_SECRET 미설정"
)
if not self.KOREA_INVESTMENT_ACCOUNT_NUMBER:
warnings.append(
f"KIS_{self.KIS_MODE.upper()}_ACCOUNT_NO 미설정"
)
# 약한 시크릿 키 감지
weak_secrets = {"change-this-secret-key", "auto_stock_secret_key_change_in_production_2024", "secret"}
if self.SECRET_KEY.lower() in weak_secrets or len(self.SECRET_KEY) < 32:
warnings.append(
"SECRET_KEY가 약합니다! 운영 환경에서는 반드시 강력한 키를 사용하세요: "
"python -c \"import secrets; print(secrets.token_urlsafe(64))\""
)
# 운영 환경 DEBUG 모드 경고
if self.DEBUG and not self.is_virtual_trading:
warnings.append("⚠️ 실전투자 모드(KIS_MODE=real)에서 DEBUG=true — 보안 위험!")
# DB / Redis
if "localhost" in self.DATABASE_URL and os.path.exists("/.dockerenv"):
warnings.append("DATABASE_URL에 localhost 포함 — Docker 환경에서 postgres 호스트명 확인 필요")
# MCP 검증 활성화 시 AI 키 필요
if self.MCP_VALIDATION_ENABLED and not self.ANTHROPIC_API_KEY and not self.GEMINI_API_KEY:
warnings.append("MCP_VALIDATION_ENABLED=True 이지만 ANTHROPIC_API_KEY / GEMINI_API_KEY 미설정")
return warnings
@lru_cache()
def get_settings() -> Settings:
return Settings()
settings = get_settings()