From d0c057358a6aa3484506aa32dd616ae6f31ee0d8 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 31 May 2026 22:18:58 +0900 Subject: [PATCH] =?UTF-8?q?test(stock):=20Phase=204=20=ED=9A=8C=EA=B7=80?= =?UTF-8?q?=20(momentum=5Floss=C2=B7=EB=A9=B1=EB=93=B1=C2=B7non-KRX=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- stock/app/test_holdings_intel.py | 90 ++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/stock/app/test_holdings_intel.py b/stock/app/test_holdings_intel.py index 84825ef..ea34580 100644 --- a/stock/app/test_holdings_intel.py +++ b/stock/app/test_holdings_intel.py @@ -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"