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:
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user