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:
@@ -118,8 +118,10 @@ async def check_market_regime() -> dict:
|
||||
chart_data = None
|
||||
try:
|
||||
from app.services.naver_finance import get_naver_daily_chart
|
||||
naver_data = await get_naver_daily_chart("069500", days=240) # oldest-first
|
||||
if naver_data and len(naver_data) >= 60:
|
||||
# MA200+기울기(5봉) 판별에 205봉 이상 필요 — 캘린더일 기준이므로 400일 요청(≈270거래일)
|
||||
naver_data = await get_naver_daily_chart("069500", days=400) # oldest-first
|
||||
# 205봉 미만이면 MA200/SIDEWAYS 판별 불가 → KIS 폴백(240봉 페이지네이션)으로 넘김
|
||||
if naver_data and len(naver_data) >= 205:
|
||||
chart_data = list(reversed(naver_data)) # → newest-first
|
||||
except Exception:
|
||||
pass
|
||||
@@ -128,14 +130,17 @@ async def check_market_regime() -> dict:
|
||||
_api = _real_market_api or korea_investment_api
|
||||
kospi_chart = await _api.get_daily_chart("069500", "D", limit=240)
|
||||
if "output2" not in kospi_chart or not kospi_chart["output2"]:
|
||||
return {"regime": "neutral", "message": "시장 지수 확인 불가", "position_multiplier": 0.5}
|
||||
# 국면 판별 불가 시 신규 진입 차단 (기존 0.5배 매매 지속 → 무국면 매매가 실손실 원인)
|
||||
return {"regime": "neutral", "regime_200d": "bear", "message": "시장 지수 확인 불가 — 신규 진입 차단",
|
||||
"position_multiplier": 0.0, "min_score": 9999, "max_trades": 0}
|
||||
chart_data = kospi_chart["output2"] # newest-first
|
||||
|
||||
# closes: newest-first (closes[0]=최신, closes[-1]=가장 오래된)
|
||||
closes = [float(item.get("stck_clpr", 0)) for item in chart_data]
|
||||
|
||||
if len(closes) < 60:
|
||||
return {"regime": "neutral", "message": "데이터 부족", "position_multiplier": 0.5}
|
||||
return {"regime": "neutral", "regime_200d": "bear", "message": "데이터 부족 — 신규 진입 차단",
|
||||
"position_multiplier": 0.0, "min_score": 9999, "max_trades": 0}
|
||||
|
||||
def _cache_and_return(result: dict) -> dict:
|
||||
_market_regime_cache["result"] = result
|
||||
@@ -173,9 +178,17 @@ async def check_market_regime() -> dict:
|
||||
is_sideways_200 = regime_enum == MarketRegime.SIDEWAYS
|
||||
ma200 = sum(closes[:200]) / 200
|
||||
else:
|
||||
is_bull_200 = current_price > ma20
|
||||
is_sideways_200 = False
|
||||
ma200 = ma60
|
||||
# 205봉 미만 → MA200/ADX 국면 판별 불가. 기존에는 MA20 프록시로 상승장 판정 후
|
||||
# 매매를 지속했으나(실계좌 손실 원인), 판별 불가 시 신규 진입을 차단한다.
|
||||
logger.warning(f"국면 판별 불가: 일봉 {len(closes)}개 < 205 — 신규 진입 차단")
|
||||
return _cache_and_return({
|
||||
"regime": "neutral",
|
||||
"regime_200d": "bear",
|
||||
"message": f"국면 판별 불가 (일봉 {len(closes)}개 < 205) — 신규 진입 차단",
|
||||
"position_multiplier": 0.0,
|
||||
"min_score": 9999,
|
||||
"max_trades": 0,
|
||||
})
|
||||
|
||||
# ── HMM 앙상블 (optional) ────────────────────────────────────
|
||||
# 규칙 기반 결과를 먼저 결정한 뒤, HMM 결과와 앙상블
|
||||
|
||||
Reference in New Issue
Block a user