feat: 자동매매 신뢰성 개선 — 시세 실서버 라우팅·일일 한도 전역화·수동 매수 API
- korea_investment_api: KIS_QUOTES_VIA_REAL — 모의투자 모드에서 시세/차트/호가 조회를 실계좌 시세 서버(20/s)로 위임, 주문·잔고는 모의서버 유지 (EGW00201 대응) - recommendations: execute_auto_trade_background asyncio.Lock 중복 실행 방지 + MAX_DAILY_TRADES를 당일 BUY 건수 차감 전역 기준으로 적용 (11종목 동시 매수 재발 방지) - manual_buy.py 신규: POST /api/v1/portfolio/buy (리스크 가드 + safe_place_order + db_worker) - mcp-server: claude_code_client(claude -p 헤드리스 모드) 추가, trade_monitor 부분매도 시 active 유지·remaining_pct 갱신, API 키 헤더·/api/v1 경로 수정, ODNO 주문ID 수정 - config: MCP_REQUEST_TIMEOUT, MCP_RISK_CACHE_TTL, BATCH_ANALYSIS_INTERVAL, SWING_INDEX_BREAKER_ENABLED(OFF), API_TRIGGERED_AUTO_TRADE(OFF) 추가 - docker-compose: 포트 충돌 해소 (postgres 15433, frontend 3001) - frontend: 추천 패널 매수 버튼, 차트/호가/저널 패널 개선 - CLAUDE.md 압축 최적화 (규칙 변경 없음), _sweep_single_axis.py 백테스트 단일축 스윕 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -199,7 +199,8 @@ class KoreaInvestmentAPI:
|
||||
instance._http_client = None
|
||||
instance._http_client_lock = asyncio.Lock()
|
||||
instance.TOKEN_CACHE_FILE = Path.home() / ".cache" / "auto-stock" / "kis_token_cache_real.json"
|
||||
instance.min_request_interval = settings.API_MIN_REQUEST_INTERVAL
|
||||
# 실서버는 초당 20건 허용 — 모의서버용 간격(API_MIN_REQUEST_INTERVAL)과 분리
|
||||
instance.min_request_interval = getattr(settings, "KIS_REAL_MIN_REQUEST_INTERVAL", 0.1)
|
||||
instance.max_retries = settings.API_MAX_RETRIES
|
||||
instance.retry_delay = settings.API_RETRY_DELAY
|
||||
instance.retry_backoff = settings.API_RETRY_BACKOFF
|
||||
@@ -549,8 +550,24 @@ class KoreaInvestmentAPI:
|
||||
raise last_exception
|
||||
raise Exception("API 요청 실패 (알 수 없는 오류)")
|
||||
|
||||
# ── 시세 조회 실서버 라우팅 ──────────────────────────────────────────────
|
||||
# 모의투자 서버(openapivts)는 앱키당 초당 2건 제한 + 응답 지연이 잦아(EGW00201)
|
||||
# 시세·차트·호가 조회를 감당하지 못한다. KIS_QUOTES_VIA_REAL=True 이고 실계좌
|
||||
# 시세 인스턴스(real_market_api)가 있으면 조회성 호출만 실서버로 위임한다.
|
||||
# 주문·잔고는 그대로 모의서버. (real_market_api 자신은 위임하지 않음 → 재귀 방지)
|
||||
def _quote_delegate(self) -> "KoreaInvestmentAPI | None":
|
||||
if not getattr(settings, "KIS_QUOTES_VIA_REAL", False):
|
||||
return None
|
||||
_real = globals().get("real_market_api")
|
||||
if _real is None or _real is self:
|
||||
return None
|
||||
return _real
|
||||
|
||||
async def get_current_price(self, stock_code: str) -> Dict[str, Any]:
|
||||
"""현재가 조회"""
|
||||
_d = self._quote_delegate()
|
||||
if _d is not None:
|
||||
return await _d.get_current_price(stock_code)
|
||||
try:
|
||||
result, _ = await self._make_request(
|
||||
"GET",
|
||||
@@ -572,6 +589,9 @@ class KoreaInvestmentAPI:
|
||||
캐시 키: chart:daily:{stock_code}:{period}:{limit}
|
||||
TTL: 5분 (장중 업데이트 주기 고려)
|
||||
"""
|
||||
_d = self._quote_delegate()
|
||||
if _d is not None:
|
||||
return await _d.get_daily_chart(stock_code, period, limit)
|
||||
cache_key = f"chart:daily:{stock_code}:{period}:{limit}"
|
||||
|
||||
# ── 1. 캐시 히트 확인 ───────────────────────────────────────────────
|
||||
@@ -771,6 +791,9 @@ class KoreaInvestmentAPI:
|
||||
]
|
||||
}
|
||||
"""
|
||||
_d = self._quote_delegate()
|
||||
if _d is not None:
|
||||
return await _d.get_minute_chart(stock_code, ref_time, include_past)
|
||||
try:
|
||||
if not ref_time:
|
||||
from datetime import datetime
|
||||
@@ -829,6 +852,9 @@ class KoreaInvestmentAPI:
|
||||
Returns:
|
||||
List[dict] — output2 레코드 목록 (오래된 것 → 최신 순)
|
||||
"""
|
||||
_d = self._quote_delegate()
|
||||
if _d is not None:
|
||||
return await _d.get_minute_chart_full_day(stock_code, market_open)
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
kst = pytz.timezone("Asia/Seoul")
|
||||
@@ -1244,6 +1270,9 @@ class KoreaInvestmentAPI:
|
||||
|
||||
async def get_orderbook(self, stock_code: str) -> Dict[str, Any]:
|
||||
"""호가 정보 조회 (매수/매도 호가 10단계)"""
|
||||
_d = self._quote_delegate()
|
||||
if _d is not None:
|
||||
return await _d.get_orderbook(stock_code)
|
||||
try:
|
||||
result, _ = await self._make_request(
|
||||
"GET",
|
||||
|
||||
Reference in New Issue
Block a user