""" 애플리케이션 설정 모듈 - 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 폴백) # ── 대시보드 로그인 (DB users 테이블, bcrypt) ───────────────────────── SIGNUP_ENABLED: bool = True # 회원가입 허용. False 면 /auth/register 403 SIGNUP_AUTO_APPROVE: bool = False # True: 가입 즉시 활성. False: 첫 가입자만 자동 관리자, 이후 관리자 승인 필요 SESSION_TOKEN_EXPIRE_HOURS: int = 24 # 로그인 세션 토큰 유효시간 LOGIN_MAX_FAILURES: int = 5 # IP당 연속 실패 허용 횟수 LOGIN_LOCKOUT_SECONDS: int = 300 # 초과 시 잠금 시간(초) BACKEND_HOST: str = "0.0.0.0" BACKEND_PORT: int = 8000 # ── 멀티테넌트(사용자별 매매) 1단계 ───────────────────────────────── MULTI_TENANT_ENABLED: bool = False # True 전환 시 사용자별 KIS 계좌·리스크 상태 분리 (기본 False = 기존 단일 계좌 동작) CREDENTIALS_ENCRYPTION_KEY: str = "" # user_kis_credentials 암복호화 키 파생 소스. 비어있으면 SECRET_KEY 폴백 CREDENTIALS_ENCRYPTION_KEY_PREVIOUS: str = "" # 키 로테이션 중 구 키(복호화만 허용) BOOTSTRAP_ADMIN_USERNAME: str = "admin" # 앱 최초 기동 시 users 테이블이 비어있으면 시드할 관리자 계정명 BOOTSTRAP_ADMIN_PASSWORD: str = "" # 비어있으면 부트스트랩 관리자 시드 건너뜀 # ═════════════════════════════════════════════════════════════════════════════ # 데이터베이스 & 메시지 큐 # ═════════════════════════════════════════════════════════════════════════════ 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_QUOTES_VIA_REAL: bool = True # 모의투자 모드에서 시세·차트·호가 조회를 실계좌 시세 서버(초당 20건)로 라우팅. 주문·잔고는 모의서버 유지 KIS_REAL_MIN_REQUEST_INTERVAL: float = 0.1 # 실계좌 시세 인스턴스 최소 요청 간격(초). 한도 20/s 대비 여유 10/s 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_REQUEST_TIMEOUT: float = 15.0 # MCP /analyze 호출 타임아웃(초) — claude_code CLI 모드는 60 권장 MCP_RISK_CACHE_TTL: int = 0 # 동일 종목셋 RISK_ASSESSMENT 결과 재사용(초). 0=매번 호출, 3600=1시간 BATCH_ANALYSIS_INTERVAL: int = 600 # 장중 배치 분석 주기(초). 3600=1시간 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.04 # 종목당 4% (6%→4%, 2026-09-23: 종목 수를 늘려 표본 확보) 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 이하 매수 금지 # 최소 매수 금액 가드 (수수료 대비 비효율 방지, trade_executor._execute_buy) # 큰 계좌는 이 값이 그대로 하한으로 사용되고, 소액 계좌는 자산 대비로 완화된다 # (trade_executor._MIN_BUY_AMOUNT 산출 로직 참고). MIN_BUY_AMOUNT_KRW: float = 150_000 # 최소 주문금액 (500,000→150,000, 2026-09-23) # 50만원은 종목당 예산(총자산×6%≈59만)과 거의 같아, 신뢰도·현금 보정으로 조금만 # 깎여도 전부 탈락했다(2026-09-23 하루 141건 차단). 계좌 대비 1.5~3% 주문을 허용한다. # 연속 손실 관리 (섹션 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 = 8 # 동시 최대 종목 수 (3→8, 2026-09-22 50:50 배분: 8×6%≈50%) DAY_TRADE_CAPITAL_RATIO: float = 0.50 # 전체 자본 대비 최대 50% (0.25→0.50, 2026-09-22) DAY_TRADE_PER_STOCK_RATIO: float = 0.06 # 종목당 최대 6% (0.04→0.06, MAX_POSITION_SIZE 상한과 동일) DAY_TRADE_MAX_CAPITAL_RATIO: float = 0.06 # 종목당 최대 6% 자본 (0.04→0.06, 2026-09-22) DAY_TRADE_MAX_RATIO_DT: float = 0.50 # 전체 데이트레이딩 최대 50% (0.25→0.50, 2026-09-22 스윙:DT = 50:50) SWING_MAX_CAPITAL_RATIO: float = 0.50 # 스윙 전략 합산 최대 50% (신규, 2026-09-22 — 초과 시 스윙 신규 매수 차단) 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.03 # ORB 손절 -3% (기존 2% → 노이즈 손절 방지) DAY_TRADE_REVERSION_STOP_LOSS_PCT: float = 0.035 # VWAP/RSI_BB 손절 -3.5% (기존 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 = 15 # 신규 진입 마감 시간 (14:30→15:00, 2026-09-22 시간대 제한 완화) DAY_TRADE_ENTRY_CUTOFF_MIN: int = 0 # 신규 진입 마감 분 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 = 45.0 # VWAP RSI <= 45 (기존 40 → 45: 5분봉에서 40 이하는 드뭄) DAY_TRADE_RSI_BB_RSI_THRESHOLD: float = 35.0 # RSI_BB RSI <= 35 (기존 30 → 35: 5분봉에서 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 = 1.5 # 목표가 = entry + risk × 1.5R (기존 2.0 → 현실적 목표) DAY_TRADE_TIME_STOP_MINUTES: int = 30 # 시간 손절 (30분) DAY_TRADE_TIME_STOP_MIN_GAIN: float = -0.003 # 시간 손절: -0.3% 미만 시 청산 (기존 +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분) DAY_TRADE_MIN_STOP_LOSS_PCT: float = 0.02 # 최소 손절폭 -2.0% (기존 1.0% → 노이즈 손절 방지) # ═════════════════════════════════════════════════════════════════════════════ # 코스닥 스캐너 (섹션 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 # 실적 필터 (Phase 2) — 기본 OFF. point-in-time 실적 데이터로 백테스트 검증 전엔 # 켜지 말 것(CLAUDE.md §8.1-4: 미검증 전략 실전 투입 금지). 현재 데이터원=네이버. FUNDAMENTAL_FILTER_ENABLED: bool = False FUNDAMENTAL_BLOCK_NEGATIVE_EPS: bool = True # 적자(EPS<0) 종목 진입 차단 FUNDAMENTAL_MIN_UPSIDE_PCT: float = 0.0 # 컨센서스 목표가 상승여력 최소치(%). 이하면 차단 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 # 하락장 방어 필터 SWING_INDEX_BREAKER_ENABLED: bool = False # 지수 서킷브레이커(KODEX200 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()