feat: AI 투자 분석에 매수 추천 금액 표시
BUY 신호일 때 실제 자동매매와 동일한 기준으로 매수 금액·수량을 계산해 패널 상단에 표시. · 종목당 상한 = 총자산 × MAX_POSITION_SIZE(적응형 모드면 그 값) · 신뢰도 보정 = 예산 × (confidence/0.7), 0.5~1.2배 · 현금 여력(최소 현금보유 제외)·스윙 합산 상한(SWING_MAX_CAPITAL_RATIO) 잔여로 클램프 BUY 가 아니거나 예산이 1주 미만이면 금액 대신 사유를 표시. 테스트 6건. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
68
backend/tests/test_ai_analysis_buy_amount.py
Normal file
68
backend/tests/test_ai_analysis_buy_amount.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""AI 투자 분석 — 매수 추천 금액 산출 검증 (자동매매 사이징과 동일 기준)."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app.api.endpoints.market_data import _suggest_buy_amount
|
||||
from app.api.endpoints.portfolio import portfolio_cache
|
||||
|
||||
|
||||
def _summary(total_assets=10_000_000, cash=5_000_000, stock_value=3_000_000):
|
||||
return {"total_assets": total_assets, "available_cash": cash, "total_value": stock_value}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _cache():
|
||||
portfolio_cache.summary = _summary()
|
||||
yield
|
||||
portfolio_cache.summary = None
|
||||
|
||||
|
||||
class _Adp:
|
||||
fallback_used = False
|
||||
position_size = 0.06
|
||||
|
||||
|
||||
def test_buy_amount_uses_position_cap_and_confidence():
|
||||
with patch("app.services.adaptive_strategy.get_current_adaptive_params", return_value=_Adp()):
|
||||
out = _suggest_buy_amount(current_price=70_000, recommendation="BUY",
|
||||
confidence=0.7, stock_code="005930")
|
||||
# 1,000만 × 6% = 60만, 신뢰도 0.7 → 보정 1.0 → 70,000원 × 8주 = 560,000
|
||||
assert out["suggested_quantity"] == 8
|
||||
assert out["suggested_amount"] == 560_000
|
||||
assert out["position_ratio"] == pytest.approx(5.6, abs=0.01)
|
||||
assert "6%" in out["sizing_note"]
|
||||
|
||||
|
||||
def test_high_confidence_scales_up_capped_at_120pct():
|
||||
with patch("app.services.adaptive_strategy.get_current_adaptive_params", return_value=_Adp()):
|
||||
out = _suggest_buy_amount(70_000, "BUY", 1.0, "005930")
|
||||
# 60만 × min(1.2, 1.0/0.7=1.43) = 72만 → 10주
|
||||
assert out["suggested_quantity"] == 10
|
||||
|
||||
|
||||
def test_non_buy_recommendation_returns_zero():
|
||||
for rec in ("HOLD", "SELL"):
|
||||
out = _suggest_buy_amount(70_000, rec, 0.8, "005930")
|
||||
assert out["suggested_amount"] == 0 and rec in out["sizing_note"]
|
||||
|
||||
|
||||
def test_limited_by_available_cash_and_reserve():
|
||||
portfolio_cache.summary = _summary(cash=600_000) # 예수금 60만, 최소보유 5% = 50만
|
||||
with patch("app.services.adaptive_strategy.get_current_adaptive_params", return_value=_Adp()):
|
||||
out = _suggest_buy_amount(70_000, "BUY", 0.7, "005930")
|
||||
assert out["suggested_quantity"] == 1 # 사용 가능 10만 → 1주
|
||||
|
||||
|
||||
def test_limited_by_swing_capital_cap():
|
||||
# 스윙 상한 50% = 500만, 이미 490만 보유 → 잔여 10만
|
||||
portfolio_cache.summary = _summary(stock_value=4_900_000)
|
||||
with patch("app.services.adaptive_strategy.get_current_adaptive_params", return_value=_Adp()):
|
||||
out = _suggest_buy_amount(70_000, "BUY", 0.7, "005930")
|
||||
assert out["suggested_quantity"] == 1
|
||||
|
||||
|
||||
def test_no_balance_info_reports_reason():
|
||||
portfolio_cache.summary = None
|
||||
out = _suggest_buy_amount(70_000, "BUY", 0.8, "005930")
|
||||
assert out["suggested_amount"] == 0 and "잔고" in out["sizing_note"]
|
||||
Reference in New Issue
Block a user