From 9774c1249b92c7602bafff90c3ac456606619858 Mon Sep 17 00:00:00 2001 From: shuim Date: Wed, 23 Sep 2026 14:40:18 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=A7=84=EC=9E=85=EC=9D=B4=20=EC=B5=9C?= =?UTF-8?q?=EC=86=8C=EC=A3=BC=EB=AC=B8=EA=B8=88=EC=95=A1=EC=97=90=20?= =?UTF-8?q?=EB=A7=89=ED=98=80=20=EA=B1=B0=EB=9E=98=EA=B0=80=20=EC=95=88=20?= =?UTF-8?q?=EB=82=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=E2=80=94=2015?= =?UTF-8?q?=EB=A7=8C=EC=9B=90=EC=9C=BC=EB=A1=9C=20=ED=95=98=ED=96=A5=20+?= =?UTF-8?q?=20=EC=A2=85=EB=AA=A9=EB=8B=B9=204%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 로그 집계(2026-09-23): 진입 차단 141건이 min_amount_guard. 최소 주문금액 50만원이 종목당 예산(총자산 982만×6%≈59만)과 거의 같아, 신뢰도·현금 보정으로 20~30만원이 되면 전부 탈락했다(기아 230,800원·엘앤에프 217,000원 등). - MIN_BUY_AMOUNT_KRW 500,000 → 150,000 - MAX_POSITION_SIZE 0.06 → 0.04 (종목당 금액을 낮추고 동시 보유 종목 수를 늘려 표본 확보) - AI 분석 매수 추천 금액의 신뢰도 하한 0.5 → 0.8 (과도 축소 방지) - 설정 관계를 테스트로 고정(최소주문 ≤ 종목당 예산의 50%, 스윙 상한 내 10종목 이상) Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 8 +++--- backend/app/api/endpoints/market_data.py | 2 +- backend/app/core/config.py | 6 +++-- backend/tests/test_entry_throughput_config.py | 27 +++++++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 backend/tests/test_entry_throughput_config.py diff --git a/CLAUDE.md b/CLAUDE.md index ff6e220..9fd8f8a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,8 @@ ## 5. 리스크 규칙 ### 5.1 사이징·필터 -- 종목당 최대 **6%** (`MAX_POSITION_SIZE=0.06`), 최대 **20종목**(`MAX_HOLDINGS_COUNT`), 레버리지/인버스 ETF 최대 **20%**(`LEVERAGE_ETF_MAX_RATIO`). +- 종목당 최대 **4%** (`MAX_POSITION_SIZE=0.04`), 최대 **20종목**(`MAX_HOLDINGS_COUNT`), 레버리지/인버스 ETF 최대 **20%**(`LEVERAGE_ETF_MAX_RATIO`). +- 최소 주문금액 **15만원**(`MIN_BUY_AMOUNT_KRW`). 이 값이 종목당 예산에 근접하면 신뢰도·현금 보정으로 깎인 주문이 전부 탈락한다(2026-09-23 하루 141건 차단). - 가격 필터: 국내 >5,000원, 미국 >$4 (ETF·리츠 예외) (`MIN_PRICE_KRW`, `MIN_PRICE_USD`). - 상관관계: 섹터당 최대 3종목, 기존 포트폴리오와 상관계수 >0.7 진입 거부 (`correlation_filter`). - 연속 손실: 동일 종목 **3연패 → 42캘린더일(≈30거래일) 진입 금지** (`loss_tracker`). `record_trade_result()` 후 반드시 `save_to_db()`(db_worker upsert_stock_ban 경유). 재시작 시 `load_from_db()` 복원. @@ -91,7 +92,8 @@ | 설정 | 값 | 비고 | |------|-----|------| -| `MAX_POSITION_SIZE` | 0.06 | 0.04→0.06 | +| `MAX_POSITION_SIZE` | 0.04 | 0.06→0.04 (2026-09-23, 종목 수 확대) | +| `MIN_BUY_AMOUNT_KRW` | 150,000 | 500,000→150,000 (거래 차단 해소) | | `MAX_HOLDINGS_COUNT` | 20 | | | `LEVERAGE_ETF_MAX_RATIO` | 0.20 | | | `ATR_STOP_MULTIPLIER` | 2.5 | 스윙 ATR SL 배수 | @@ -122,4 +124,4 @@ | Defensive | 38 | 4% | -6% | ATR백분위>70 or 5일수익<-3% | | Crisis | 25 | 1.5% | -3% | ATR백분위>90 or 5일수익<-5% | -*마지막 업데이트: 2026-09-22 (스윙:DT 50:50, DT 시간대 제한 완화, ORB 감시목록 선채움)* +*마지막 업데이트: 2026-09-23 (종목당 4%·최소주문 15만원, 시간대 KST 통일, 감사 수정 14건)* diff --git a/backend/app/api/endpoints/market_data.py b/backend/app/api/endpoints/market_data.py index 87d96ee..065b883 100644 --- a/backend/app/api/endpoints/market_data.py +++ b/backend/app/api/endpoints/market_data.py @@ -693,7 +693,7 @@ def _suggest_buy_amount( budget = total_assets * position_cap # 신뢰도 보정 (기준 0.7) if confidence > 0: - budget *= min(1.2, max(0.5, confidence / 0.7)) + budget *= min(1.2, max(0.8, confidence / 0.7)) # 하한 0.8 (과도 축소 방지) # 현금 여력 reserve = total_assets * (1 - float(getattr(_cfg, "MAX_TOTAL_CAPITAL_USAGE", 0.95))) budget = min(budget, max(0.0, available_cash - reserve)) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index ace92c3..b3db768 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -138,7 +138,7 @@ class Settings(BaseSettings): # ═════════════════════════════════════════════════════════════════════════════ # 공통 리스크 관리 (섹션 5) # ═════════════════════════════════════════════════════════════════════════════ - MAX_POSITION_SIZE: float = 0.06 # 백테스트 최적: 종목당 6% (기존 4%, 적응형 모드에서 오버라이드) + MAX_POSITION_SIZE: float = 0.04 # 종목당 4% (6%→4%, 2026-09-23: 종목 수를 늘려 표본 확보) MAX_HOLDINGS_COUNT: int = 20 # 동시 보유 최대 20종목 (섹션 5.1) LEVERAGE_ETF_MAX_RATIO: float = 0.20 # 레버리지/인버스 ETF 최대 20% (섹션 5.1) @@ -169,7 +169,9 @@ class Settings(BaseSettings): # 최소 매수 금액 가드 (수수료 대비 비효율 방지, trade_executor._execute_buy) # 큰 계좌는 이 값이 그대로 하한으로 사용되고, 소액 계좌는 자산 대비로 완화된다 # (trade_executor._MIN_BUY_AMOUNT 산출 로직 참고). - MIN_BUY_AMOUNT_KRW: float = 500_000 # 기존 하드코딩 _MIN_BUY_AMOUNT와 동일한 기본값 + MIN_BUY_AMOUNT_KRW: float = 150_000 # 최소 주문금액 (500,000→150,000, 2026-09-23) + # 50만원은 종목당 예산(총자산×6%≈59만)과 거의 같아, 신뢰도·현금 보정으로 조금만 + # 깎여도 전부 탈락했다(2026-09-23 하루 141건 차단). 계좌 대비 1.5~3% 주문을 허용한다. # 연속 손실 관리 (섹션 5.5) CONSECUTIVE_LOSS_BAN_COUNT: int = 3 # 3회 연속 손실 diff --git a/backend/tests/test_entry_throughput_config.py b/backend/tests/test_entry_throughput_config.py new file mode 100644 index 0000000..59b7081 --- /dev/null +++ b/backend/tests/test_entry_throughput_config.py @@ -0,0 +1,27 @@ +"""진입이 차단되지 않도록 하는 설정 관계 검증. + +2026-09-23: 최소 주문금액(50만)이 종목당 예산(총자산×6%≈59만)과 거의 같아, +신뢰도·현금 보정으로 조금만 깎여도 전부 탈락했다(하루 141건 min_amount_guard). +""" +import pytest + +from app.core.config import settings + + +def test_min_buy_amount_is_well_below_position_budget(): + """1,000만원 계좌 기준 종목당 예산의 절반 이하여야 보정 후에도 주문이 살아남는다.""" + budget = 10_000_000 * settings.MAX_POSITION_SIZE + assert settings.MIN_BUY_AMOUNT_KRW <= budget * 0.5, ( + f"최소주문 {settings.MIN_BUY_AMOUNT_KRW:,.0f} vs 종목당 예산 {budget:,.0f}" + ) + + +def test_position_size_allows_enough_concurrent_holdings(): + """스윙 자본 상한 안에서 최소 10종목은 동시 보유할 수 있어야 표본이 쌓인다.""" + swing_cap = getattr(settings, "SWING_MAX_CAPITAL_RATIO", 1.0) + assert swing_cap / settings.MAX_POSITION_SIZE >= 10 + + +def test_values_are_intended(): + assert settings.MAX_POSITION_SIZE == 0.04 + assert settings.MIN_BUY_AMOUNT_KRW == 150_000