fix: 진입이 최소주문금액에 막혀 거래가 안 나던 문제 — 15만원으로 하향 + 종목당 4%

로그 집계(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) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 14:40:18 +09:00
parent f480eac25c
commit 9774c1249b
4 changed files with 37 additions and 6 deletions

View File

@@ -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건)*

View File

@@ -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))

View File

@@ -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회 연속 손실

View File

@@ -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