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

@@ -1,8 +1,31 @@
from fastapi import FastAPI, HTTPException
"""MCP 서버 메인 모듈 (main.py)
Model Context Protocol(MCP) 서버 — AI 기반 주식 매매를 위한 REST API 제공.
백엔드(FastAPI)에서 이 서버를 호출하여 AI 분석 및 도구 실행을 요청한다.
엔드포인트:
GET / — 서버 상태 확인
GET /health — 헬스 체크 (AI 프로바이더 설정 확인)
GET /tools — 사용 가능한 MCP 도구 목록
POST /tools/call — MCP 도구 호출 (시장 데이터, 지표 계산, 뉴스 조회 등)
POST /analyze — AI 분석 실행 (규칙 검증, 시장 분석, 리스크 평가 등)
보안:
- MCP_API_KEY가 설정된 경우 X-API-Key 헤더 검증 (constant-time 비교)
- CORS: MCP_CORS_ORIGINS로 origin 제한
AI 프로바이더:
- AI_PROVIDER=gemini 이면 GeminiClient, 아니면 ClaudeClient 사용
"""
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import Dict, Any, List
import hmac
import logging
import os
from config import settings
from claude_client import claude_client
@@ -44,10 +67,33 @@ app.add_middleware(
allow_origins=_allowed_origins,
allow_credentials=False, # 서버-서버 통신; 브라우저 세션 쿠키 불필요
allow_methods=["GET", "POST"],
allow_headers=["Content-Type", "Authorization"],
allow_headers=["Content-Type", "Authorization", "X-API-Key"],
)
# ─── API Key 인증 미들웨어 ──────────────────────────────────────────────
# MCP_API_KEY가 설정되어 있으면 모든 요청에 X-API-Key 헤더 검증 (health/root 제외)
_MCP_API_KEY = settings.MCP_API_KEY
_PUBLIC_PATHS = {"/", "/health"}
@app.middleware("http")
async def verify_api_key(request: Request, call_next):
"""X-API-Key 헤더를 constant-time 비교로 검증.
MCP_API_KEY가 빈 문자열이면 인증을 비활성화 (개발 환경 호환).
"""
if _MCP_API_KEY and request.url.path not in _PUBLIC_PATHS:
api_key = request.headers.get("X-API-Key", "")
if not api_key or not hmac.compare_digest(api_key.encode(), _MCP_API_KEY.encode()):
logger.warning(f"[MCP Security] API 키 검증 실패 — path={request.url.path}")
return JSONResponse(
status_code=401,
content={"detail": "유효한 X-API-Key 헤더가 필요합니다."},
)
return await call_next(request)
class ToolCallRequest(BaseModel):
tool_name: str
parameters: Dict[str, Any]
@@ -112,6 +158,12 @@ async def analyze(request: AnalysisRequest):
detail="rule and market_data are required"
)
stock_code = market_data.get("stock_code")
# TOKEN_SAVE_MODE: OHLCV 배열 제거, 핵심 지표만 유지
if settings.TOKEN_SAVE_MODE:
_keep = {"stock_code", "current_price", "change_rate", "volume",
"rsi", "atr", "adx", "bollinger_upper", "bollinger_lower",
"bollinger_middle", "ma5", "ma20", "ma60", "signals", "score"}
market_data = {k: v for k, v in market_data.items() if k in _keep}
news_data = []
if stock_code:
try:
@@ -132,6 +184,12 @@ async def analyze(request: AnalysisRequest):
status_code=400,
detail="stock_code and market_data are required"
)
# TOKEN_SAVE_MODE: OHLCV 배열 제거
if settings.TOKEN_SAVE_MODE and isinstance(market_data, dict):
market_data = {k: v for k, v in market_data.items() if k != "data"}
# 뉴스 제목만 유지
if settings.TOKEN_SAVE_MODE and news:
news = [{"title": n.get("title", "")} for n in news[:settings.NEWS_LIMIT]]
result = await ai_client.analyze_market(stock_code, market_data, news)
return {"analysis_type": analysis_type, "result": result}
@@ -145,6 +203,9 @@ async def analyze(request: AnalysisRequest):
status_code=400,
detail="trade_plan is required"
)
# signals를 trade_plan에 병합하여 AI에 전달
if signals:
trade_plan["signals"] = signals
result = await ai_client.assess_risk(portfolio, trade_plan)
return {"analysis_type": analysis_type, "result": result}
@@ -206,9 +267,12 @@ async def analyze(request: AnalysisRequest):
if __name__ == "__main__":
import uvicorn
# [수정 5] reload=True는 운영 환경에서 성능 저하 및 다중 프로세스 부작용을 유발한다.
# 개발 환경에서는 MCP_DEV_RELOAD=true 환경변수로 활성화할 수 있다.
_dev_reload = os.getenv("MCP_DEV_RELOAD", "false").lower() == "true"
uvicorn.run(
"main:app",
host=settings.MCP_SERVER_HOST,
port=settings.MCP_SERVER_PORT,
reload=True
reload=_dev_reload,
)