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:
@@ -10,7 +10,7 @@ KIS API 호출량을 절감한다.
|
||||
- market_analyzer.get_all_tradable_stocks() 대체
|
||||
- market_analyzer.analyze_stock() 배치 분석용 일봉 데이터 제공
|
||||
|
||||
URL (종목 목록): https://finance.naver.com/sise/sise_market_sum.naver?sosok=0&page=N
|
||||
URL (종목 목록): https://m.stock.naver.com/api/stocks/marketValue/{KOSPI|KOSDAQ}?page=N&pageSize=50
|
||||
URL (일봉 차트): https://fchart.stock.naver.com/siseJson.naver?symbol=CODE&requestType=1&startTime=YYYYMMDD&endTime=YYYYMMDD&timeframe=day
|
||||
"""
|
||||
|
||||
@@ -65,16 +65,43 @@ def _parse_page(html: bytes) -> List[Tuple[str, str, int]]:
|
||||
return result
|
||||
|
||||
|
||||
_LIST_API_URL = "https://m.stock.naver.com/api/stocks/marketValue/{market}"
|
||||
_LIST_PAGE_SIZE = 50 # 구 HTML 페이지(~50종목/페이지)와 동일한 페이지 의미 유지
|
||||
_SOSOK_TO_MARKET = {0: "KOSPI", 1: "KOSDAQ"}
|
||||
|
||||
|
||||
def _parse_json_page(data: Dict) -> List[Tuple[str, str, int]]:
|
||||
"""m.stock.naver.com JSON → [(종목코드, 종목명, 현재가), ...]"""
|
||||
result = []
|
||||
for item in data.get("stocks") or []:
|
||||
code = item.get("itemCode") or ""
|
||||
name = (item.get("stockName") or "").strip()
|
||||
if not code or not name:
|
||||
continue
|
||||
try:
|
||||
price = int(str(item.get("closePrice") or "0").replace(",", ""))
|
||||
except ValueError:
|
||||
price = 0
|
||||
result.append((code, name, price))
|
||||
return result
|
||||
|
||||
|
||||
async def _fetch_page(client: httpx.AsyncClient, sosok: int, page: int) -> List[Tuple[str, str, int]]:
|
||||
"""시가총액 순위 1페이지 조회.
|
||||
|
||||
2026-09 네이버가 finance.naver.com/sise/sise_market_sum.naver(HTML)를 폐기하고
|
||||
stock.naver.com으로 302 리다이렉트 → m.stock.naver.com JSON API로 교체.
|
||||
"""
|
||||
market = _SOSOK_TO_MARKET.get(sosok, "KOSPI")
|
||||
try:
|
||||
resp = await client.get(
|
||||
_BASE_URL,
|
||||
params={"sosok": sosok, "page": page},
|
||||
_LIST_API_URL.format(market=market),
|
||||
params={"page": page, "pageSize": _LIST_PAGE_SIZE},
|
||||
headers=_HEADERS,
|
||||
timeout=10,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return _parse_page(resp.content)
|
||||
return _parse_json_page(resp.json())
|
||||
except Exception as exc:
|
||||
logger.warning("네이버 파이낸스 조회 실패 sosok=%d page=%d: %s", sosok, page, exc)
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user