test(stock): Phase 4 회귀 (momentum_loss·멱등·non-KRX 경로)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 22:18:58 +09:00
parent 7d7064ae93
commit d0c057358a

View File

@@ -295,3 +295,93 @@ def test_compute_and_store_and_brief(monkeypatch):
assert brief["holdings"][0]["ticker"] == "005930"
assert "portfolio_health" in brief
assert brief["holdings"][0]["action"] in ("add", "hold", "trim", "sell")
def test_compute_momentum_loss_flag(monkeypatch):
"""직전 시그널 tech_score HIGH → 오늘 LOW → momentum_loss=True."""
import os, tempfile
from app import db
monkeypatch.setattr(db, "DB_PATH", os.path.join(tempfile.mkdtemp(), "stock.db"))
db.init_db()
yesterday = (dt.date.today() - dt.timedelta(days=1)).isoformat()
today = dt.date.today()
today_iso = today.isoformat()
# 어제 시그널 삽입: tech_score=90 (HIGH)
db.upsert_holdings_signal(
date=yesterday, ticker="005930", name="삼성전자",
action="hold", tech_score=90.0, exit_flags={}, issues=[],
close=1000, pnl_rate=0.0, reasons="x",
)
monkeypatch.setattr(hi, "get_holdings", lambda: [
{"ticker": "005930", "name": "삼성전자", "quantity": 10, "avg_price": 1000,
"current_price": 1000, "pnl_rate": 0.0, "is_krx": True}
])
ctx = _toy_ctx(("005930",))
monkeypatch.setattr(hi, "_load_ctx", lambda asof: ctx)
monkeypatch.setattr(hi, "_news_sentiment_map", lambda date: {})
monkeypatch.setattr(hi.db, "get_all_broker_cash", lambda: [])
# tech_score=30 → 낮음 (momentum_low=35 미만, 또한 90-30=60 > momentum_drop=15)
monkeypatch.setattr(hi, "technical_posture", lambda ctx, tickers: {"005930": 30.0})
res = hi.compute_and_store(asof=today, use_llm=False)
assert res["stored"] == 1
signals = db.get_holdings_signals(today_iso)
assert len(signals) == 1
assert signals[0]["exit_flags"]["momentum_loss"] is True
def test_compute_idempotent(monkeypatch):
"""동일 입력으로 compute_and_store 두 번 실행 → upsert로 1건만 저장."""
import os, tempfile
from app import db
monkeypatch.setattr(db, "DB_PATH", os.path.join(tempfile.mkdtemp(), "stock.db"))
db.init_db()
monkeypatch.setattr(hi, "get_holdings", lambda: [
{"ticker": "005930", "name": "삼성전자", "quantity": 10, "avg_price": 1000,
"current_price": 1100, "pnl_rate": 10.0, "is_krx": True}
])
ctx = _toy_ctx(("005930",))
monkeypatch.setattr(hi, "_load_ctx", lambda asof: ctx)
monkeypatch.setattr(hi, "_news_sentiment_map", lambda date: {})
monkeypatch.setattr(hi.db, "get_all_broker_cash", lambda: [])
hi.compute_and_store(asof=ctx.asof, use_llm=False)
hi.compute_and_store(asof=ctx.asof, use_llm=False)
signals = db.get_holdings_signals(ctx.asof.isoformat())
assert len(signals) == 1, f"upsert 실패: {len(signals)}건 저장됨"
def test_compute_non_krx_holding(monkeypatch):
"""is_krx=False 종목은 tech_score=None·action='hold'로 저장된다."""
import os, tempfile
from app import db
monkeypatch.setattr(db, "DB_PATH", os.path.join(tempfile.mkdtemp(), "stock.db"))
db.init_db()
monkeypatch.setattr(hi, "get_holdings", lambda: [
{"ticker": "AAPL", "name": "Apple", "quantity": 5, "avg_price": 200,
"current_price": 220, "pnl_rate": 10.0, "is_krx": False}
])
ctx = _toy_ctx(()) # ticker 없는 빈 ctx
monkeypatch.setattr(hi, "_load_ctx", lambda asof: ctx)
monkeypatch.setattr(hi, "_news_sentiment_map", lambda date: {})
monkeypatch.setattr(hi.db, "get_all_broker_cash", lambda: [])
res = hi.compute_and_store(asof=ctx.asof, use_llm=False)
assert res["stored"] == 1
signals = db.get_holdings_signals(ctx.asof.isoformat())
assert len(signals) == 1
sig = signals[0]
assert sig["ticker"] == "AAPL"
assert sig["tech_score"] is None
assert sig["action"] == "hold"