test(stock): Phase 3 커버리지 보강 (volume Z경로·외인매도·severity경계·빈포트)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -152,7 +152,7 @@ def market_events(ticker: str, ticker_prices: "pd.DataFrame",
|
||||
last_vol = vol.iloc[-1]
|
||||
if mu > 0 and (
|
||||
(sd and (last_vol - mu) / sd >= p["vol_z"])
|
||||
or (not sd and last_vol >= mu * p["vol_z"]) # sd=0 fallback: plain ratio
|
||||
or (not sd and last_vol >= mu * p["vol_z"]) # sd=0 (평탄 기준선): vol_z를 Z-score가 아닌 단순 배수로 사용
|
||||
):
|
||||
z_txt = f"{(last_vol - mu) / sd:.1f}" if sd else f"ratio={last_vol / mu:.1f}x"
|
||||
events.append({
|
||||
|
||||
@@ -226,3 +226,50 @@ def test_portfolio_health():
|
||||
assert h["positions"] == 2
|
||||
assert 0 <= h["max_weight"] <= 1.0
|
||||
assert "total_eval" in h and "total_pnl" in h and "cash_ratio" in h
|
||||
|
||||
|
||||
def test_market_events_volume_surge_zscore_path():
|
||||
# 변동 있는 기준선 → Z-score 경로(sd>0) 검증 (sd=0 fallback 아님)
|
||||
import random as _r
|
||||
_r.seed(1)
|
||||
base_vols = [1000 + _r.randint(-50, 50) for _ in range(30)]
|
||||
closes = [1000] * 30 + [1010]
|
||||
vols = base_vols + [max(base_vols) * 10] # 마지막날 큰 급증
|
||||
df = _ticker_prices(closes, vols)
|
||||
evts = hi.market_events("005930", df, None, DEFAULT_EVENT)
|
||||
assert any(e["type"] == "volume_surge" for e in evts)
|
||||
|
||||
|
||||
def test_market_events_foreign_selling():
|
||||
closes = [1000] * 5
|
||||
df = _ticker_prices(closes)
|
||||
import datetime as _dt
|
||||
base = _dt.date(2025, 1, 1)
|
||||
flow = pd.DataFrame({
|
||||
"ticker": ["005930"] * 5,
|
||||
"date": [(base + _dt.timedelta(days=i)).isoformat() for i in range(5)],
|
||||
"foreign_net": [100, 50, -10, -20, -30], # 최근 3일 연속 순매도
|
||||
"institution_net": [0] * 5,
|
||||
})
|
||||
evts = hi.market_events("005930", df, flow, DEFAULT_EVENT)
|
||||
assert any(e["type"] == "foreign_selling" for e in evts)
|
||||
|
||||
|
||||
def test_news_issues_severity_high_boundary(monkeypatch):
|
||||
monkeypatch.setattr(hi, "_news_sentiment_map", lambda date: {
|
||||
"005930": {"score_raw": -0.6, "news_count": 5}}) # 정확히 high 경계
|
||||
issues = hi.news_issues(["005930"], date="2026-05-29", use_llm=False)
|
||||
assert issues["005930"][0]["severity"] == "high"
|
||||
|
||||
|
||||
def test_portfolio_health_empty_and_zero():
|
||||
# 빈 포트 → 0/빈값, 크래시 없음
|
||||
h0 = hi.portfolio_health([], total_cash=0)
|
||||
assert h0["positions"] == 0
|
||||
assert h0["max_weight"] == 0.0
|
||||
assert h0["total_pnl_rate"] == 0.0
|
||||
assert h0["cash_ratio"] == 0.0
|
||||
# total_buy=0 (avg_price 0) → div-by-zero 없이 0.0
|
||||
h1 = hi.portfolio_health([{"ticker": "X", "quantity": 1, "avg_price": 0,
|
||||
"current_price": 0, "is_krx": True}], total_cash=0)
|
||||
assert h1["total_pnl_rate"] == 0.0
|
||||
|
||||
Reference in New Issue
Block a user