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,3 +1,22 @@
|
||||
"""MCP 도구 레지스트리 모듈 (mcp_tools.py)
|
||||
|
||||
MCP 서버가 제공하는 도구(Tool)들을 등록하고 실행하는 모듈.
|
||||
Claude/Gemini AI가 function calling으로 이 도구들을 호출할 수 있다.
|
||||
|
||||
등록된 도구:
|
||||
- get_market_data: 국내/해외 주식 시장 데이터 조회 (KIS API)
|
||||
- calculate_indicators: 기술적 지표 계산 (RSI, MACD, BB, SMA, EMA)
|
||||
- check_portfolio: 포트폴리오 보유 현황 조회
|
||||
- validate_rules: 트레이딩 규칙 검증 (RSI 조건 등)
|
||||
- execute_order: 주문 실행 (차단됨 - 백엔드 API 경유 필수)
|
||||
- get_news: 네이버 금융 뉴스 크롤링
|
||||
|
||||
아키텍처 주의사항:
|
||||
- korea_investment_api는 None (MCP 서버에서 KIS API 직접 호출 불가)
|
||||
- execute_order()는 리스크 검증 우회 방지를 위해 항상 차단 응답 반환
|
||||
- 주문은 반드시 백엔드 /api/trades 엔드포인트를 통해 실행해야 함
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Callable
|
||||
import logging
|
||||
import pandas as pd
|
||||
@@ -9,11 +28,25 @@ import httpx
|
||||
from bs4 import BeautifulSoup
|
||||
from config import settings
|
||||
|
||||
# Optional: KIS API dependency (not currently available in MCP server directly)
|
||||
# KIS API 인스턴스 — MCP 서버에서는 직접 사용 불가 (None)
|
||||
# 주문은 백엔드 API를 통해서만 실행해야 한다 (리스크 검증 파이프라인 우회 방지)
|
||||
korea_investment_api = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Major US stock exchange mapping
|
||||
_EXCHANGE_MAP = {
|
||||
# NYSE stocks
|
||||
"JPM": "NYSE", "V": "NYSE", "JNJ": "NYSE", "WMT": "NYSE", "PG": "NYSE",
|
||||
"UNH": "NYSE", "HD": "NYSE", "BAC": "NYSE", "XOM": "NYSE", "CVX": "NYSE",
|
||||
"KO": "NYSE", "DIS": "NYSE", "MCD": "NYSE", "NKE": "NYSE", "BA": "NYSE",
|
||||
# AMEX (mostly ETFs)
|
||||
"SPY": "AMEX", "QQQ": "AMEX", "IWM": "AMEX", "XLF": "AMEX",
|
||||
}
|
||||
|
||||
def _detect_exchange(ticker: str) -> str:
|
||||
return _EXCHANGE_MAP.get(ticker.upper(), "NAS") # Default to NAS
|
||||
|
||||
|
||||
class MCPTools:
|
||||
"""MCP Tools Registry"""
|
||||
@@ -22,8 +55,6 @@ class MCPTools:
|
||||
def __init__(self):
|
||||
self.tools: Dict[str, Callable] = {}
|
||||
self._register_tools()
|
||||
# Initialize KIS API if available
|
||||
self.korea_investment_api = None
|
||||
|
||||
def _register_tools(self):
|
||||
"""도구 등록"""
|
||||
@@ -201,9 +232,8 @@ class MCPTools:
|
||||
"data": formatted_data
|
||||
}
|
||||
else:
|
||||
# 해외 주식 logic (기본 NASD 가정)
|
||||
exchange = "NASD"
|
||||
# TODO: 거래소 판별 로직 고도화 필요 (현재는 NASD 고정)
|
||||
# 해외 주식 logic (거래소 자동 판별)
|
||||
exchange = _detect_exchange(stock_code)
|
||||
|
||||
current_price_data = await korea_investment_api.get_overseas_price(stock_code, exchange)
|
||||
chart_data = await korea_investment_api.get_overseas_daily_chart(stock_code, exchange, period)
|
||||
@@ -364,38 +394,28 @@ class MCPTools:
|
||||
quantity: int,
|
||||
price: float = None
|
||||
) -> Dict[str, Any]:
|
||||
"""주문 실행 (실제로는 backend API 호출)"""
|
||||
if not korea_investment_api:
|
||||
return {"error": "Korea Investment API not available"}
|
||||
"""주문 실행.
|
||||
|
||||
try:
|
||||
# 국내/해외 구분
|
||||
is_domestic = stock_code.isdigit() and len(stock_code) == 6
|
||||
|
||||
if is_domestic:
|
||||
result = await korea_investment_api.place_order(
|
||||
stock_code=stock_code,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
price=int(price) if price else None
|
||||
)
|
||||
else:
|
||||
# 해외 주문 (지정가 필수)
|
||||
if price is None:
|
||||
return {"error": "Overseas orders require a limit price."}
|
||||
|
||||
result = await korea_investment_api.place_overseas_order(
|
||||
symbol=stock_code,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
price=price,
|
||||
exchange_code="NASD" # 기본값
|
||||
)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to place order: {e}")
|
||||
raise
|
||||
[수정 3] MCP 서버에서 KIS API를 직접 호출하는 것은 리스크 검증 파이프라인을 우회하므로
|
||||
허용하지 않는다. 주문은 반드시 백엔드 API(/api/trades)를 통해 실행해야 한다.
|
||||
|
||||
MCP 서버 내부에서 korea_investment_api 인스턴스가 None인지 여부와 무관하게
|
||||
이 함수는 항상 에러를 반환한다. 실제 주문은 auto_trade_claude.py의
|
||||
auto_trade_from_claude() 함수(리스크 검증 포함)를 사용하거나
|
||||
백엔드 /api/trades 엔드포인트를 호출해야 한다.
|
||||
"""
|
||||
logger.warning(
|
||||
"[execute_order] MCP 서버에서 직접 주문 호출 차단 — "
|
||||
"백엔드 /api/trades 엔드포인트를 통해 주문하십시오. "
|
||||
f"요청 정보: stock_code={stock_code}, order_type={order_type}, qty={quantity}, price={price}"
|
||||
)
|
||||
return {
|
||||
"error": (
|
||||
"MCP 서버에서 KIS API 직접 주문은 리스크 검증 우회 위험으로 차단되었습니다. "
|
||||
"백엔드 API(/api/trades)를 통해 주문하십시오."
|
||||
),
|
||||
"blocked": True,
|
||||
}
|
||||
|
||||
async def get_news(self, stock_code: str = None, limit: int = 5) -> Dict[str, Any]:
|
||||
"""네이버 금융 뉴스 크롤링"""
|
||||
@@ -424,7 +444,9 @@ class MCPTools:
|
||||
continue
|
||||
|
||||
title = title_tag.get_text(strip=True)
|
||||
link = "https://finance.naver.com" + title_tag['href']
|
||||
# [수정 6] href 속성이 없는 경우 KeyError 방지
|
||||
href = title_tag.get('href', '')
|
||||
link = ("https://finance.naver.com" + href) if href else ''
|
||||
date_tag = row.select_one('.date')
|
||||
date = date_tag.get_text(strip=True) if date_tag else ""
|
||||
source_tag = row.select_one('.info')
|
||||
|
||||
Reference in New Issue
Block a user