diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..19100fc --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "frontend", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "port": 3000 + }, + { + "name": "backend", + "runtimeExecutable": "uvicorn", + "runtimeArgs": ["app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"], + "port": 8000 + }, + { + "name": "docker-dev", + "runtimeExecutable": "docker-compose", + "runtimeArgs": ["-f", "docker-compose.dev.yml", "up"], + "port": 8090 + } + ] +} diff --git a/CLAUDE.md b/CLAUDE.md index 690f8ed..fd12989 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ ## 1. 프로젝트 개요 ### 1.1 기술 스택 -- **백엔드**: Python 3.11, FastAPI, SQLAlchemy (Async), PostgreSQL, Redis +- **백엔드**: Python 3.11, FastAPI, SQLAlchemy (Async), PostgreSQL, Redis, Rabbitmq - **프론트엔드**: React, TypeScript, Vite, Recharts - **인프라**: Docker Compose, Nginx - **AI 계층**: Claude 3.5 Sonnet / Gemini 2.0 Flash (`.env`에서 전환) diff --git a/backend/app/api/endpoints/recommendations.py b/backend/app/api/endpoints/recommendations.py index 81f04bc..44373a6 100644 --- a/backend/app/api/endpoints/recommendations.py +++ b/backend/app/api/endpoints/recommendations.py @@ -497,6 +497,7 @@ async def get_rule_based_recommendations_realtime(): use_bollinger=True, use_stochastic=True, for_batch=True, + min_volume_ratio=0, # intraday acml_vol << avg_vol_20 → 장중엔 항상 0으로 고정 ) batch_size = 1 diff --git a/backend/app/services/korea_investment_api.py b/backend/app/services/korea_investment_api.py index c2430cd..e6ce64e 100644 --- a/backend/app/services/korea_investment_api.py +++ b/backend/app/services/korea_investment_api.py @@ -149,6 +149,26 @@ async def _cache_set(key: str, ttl: int, data: Any) -> None: pass +def _round_to_krx_tick(price: int) -> int: + """KRX 호가단위에 맞춰 가격을 올림(ceil) 처리. 모의투자 호가단위 오류 방지.""" + if price < 1_000: + tick = 1 + elif price < 5_000: + tick = 5 + elif price < 10_000: + tick = 10 + elif price < 50_000: + tick = 50 + elif price < 100_000: + tick = 100 + elif price < 500_000: + tick = 500 + else: + tick = 1_000 + remainder = price % tick + return price if remainder == 0 else price + (tick - remainder) + + class KoreaInvestmentAPI: """한국투자증권 API 클라이언트 (안전한 재시도 및 쓰로틀링 적용)""" @@ -1012,7 +1032,7 @@ class KoreaInvestmentAPI: tr_id = "VTTC0801U" if is_virtual else "TTTC0801U" order_dvsn = "01" if price is None else "00" - order_price = "0" if price is None else str(int(price)) + order_price = "0" if price is None else str(_round_to_krx_tick(int(price))) logger.info(f"📤 주문 요청: {order_type} {stock_code} {quantity}주 @{order_price}원 (tr_id={tr_id}, is_virtual={is_virtual})") diff --git a/backend/app/services/market_analyzer.py b/backend/app/services/market_analyzer.py index f2be348..1dd687c 100644 --- a/backend/app/services/market_analyzer.py +++ b/backend/app/services/market_analyzer.py @@ -219,11 +219,6 @@ def _analyze_chart_items( logger.info("필터[저가] %s: %.0f원 < %d원", stock_code, current_price, settings.MIN_PRICE_KRW) return None - # 전일 거래량 필터: 100만주 미만 종목 제외 (유동성 부족) - if volume < 1_000_000: - logger.info("필터[거래량] %s: %d주 < 100만주", stock_code, volume) - return None - if len(chart_data) >= 2: prev_price = float(chart_data[-2].get("stck_clpr", current_price)) change_rate = ((current_price - prev_price) / prev_price) * 100 if prev_price > 0 else 0 @@ -240,6 +235,14 @@ def _analyze_chart_items( avg_vol_20 = sum(volumes[-20:]) / 20 if len(volumes) >= 20 else 0 volume_ratio = (volume / avg_vol_20) if avg_vol_20 > 0 else 0 + # 유동성 필터: 20일 평균 거래량 기준으로 판단 (intraday 누적량 대신 사용) + # chart_data[-1].acml_vol은 장 초반에 당일 누적량이므로 실제 유동성을 반영 못함 + # avg_vol_20 기준 10만주 미만 = 진입하기 어려운 비유동 종목 + liquidity_vol = avg_vol_20 if avg_vol_20 > 0 else volume + if liquidity_vol < 100_000: + logger.info("필터[거래량] %s: 20일평균 %.0f주 < 10만주", stock_code, liquidity_vol) + return None + if not criteria.for_batch and avg_vol_20 < 50000 and current_price < 50000: return None diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 407acd5..4f2f98a 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -82,6 +82,8 @@ services: - "8001:8001" environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + - GEMINI_API_KEY=${GEMINI_API_KEY} + - AI_PROVIDER=${AI_PROVIDER:-claude} - LOG_LEVEL=debug - DATABASE_URL=postgresql://${POSTGRES_USER:-stock}:${POSTGRES_PASSWORD:-stock1234}@postgres:5432/${POSTGRES_DB:-stock} command: uvicorn main:app --host 0.0.0.0 --port 8001 --reload diff --git a/mcp-server/config.py b/mcp-server/config.py index dab701a..1bceed8 100644 --- a/mcp-server/config.py +++ b/mcp-server/config.py @@ -1,7 +1,7 @@ import os from dotenv import load_dotenv -load_dotenv() +load_dotenv(override=True) # 컨테이너 빈 env var보다 .env 파일 우선 class Settings: