From 05682a8a3e2feb3e8b3f606df96a1788b0534fc5 Mon Sep 17 00:00:00 2001 From: kyu Date: Wed, 3 Jun 2026 22:12:11 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B3=B5=EB=A7=A4=EB=8F=84=20=EC=A0=90?= =?UTF-8?q?=EC=88=98=20=EC=96=91=EB=B0=A9=ED=96=A5=ED=99=94=20(=EA=B3=BC?= =?UTF-8?q?=EB=A7=A4=EB=8F=84+=EA=B3=B5=EB=A7=A4=EB=8F=84=EA=B3=BC?= =?UTF-8?q?=EB=8B=A4=20=E2=86=92=20=EC=88=8F=EC=8A=A4=ED=80=B4=EC=A6=88=20?= =?UTF-8?q?=EB=B0=98=EB=93=B1=20=EA=B0=80=EC=82=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit calc_short_score에 RSI 전달, 거래비중≥5% + RSI≤35면 숏커버링 반등 기대로 최대 +35 가산해 단방향 패널티 상쇄. 기관/외국인 수급은 기존대로 반영 중 (외국인 calc_foreign_score 14%, 기관 flow_sig ±10 aux_total). Co-Authored-By: Claude Opus 4.8 (1M context) --- score-engine/main.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/score-engine/main.py b/score-engine/main.py index 7d1650c..50086ca 100644 --- a/score-engine/main.py +++ b/score-engine/main.py @@ -743,8 +743,9 @@ def calc_foreign_score(foreign_data: list) -> tuple[float, str]: return max(-100.0, min(100.0, score)), reason -def calc_short_score(short_data: list) -> tuple[float, str]: - """공매도 점수 (-100~+100), 공매도 많을수록 패널티""" +def calc_short_score(short_data: list, rsi: float | None = None) -> tuple[float, str]: + """공매도 점수 (-100~+100). 기본은 공매도 많을수록 패널티지만, + 공매도 과다 + 과매도(RSI≤35)면 숏커버링 반등(스퀴즈) 기대로 가산 — 양방향.""" if not short_data: return 0.0, "" score = 0.0 @@ -767,6 +768,12 @@ def calc_short_score(short_data: list) -> tuple[float, str]: elif bal_chg_pct > 5: score -= 10 elif bal_chg_pct < -20: score += 15 elif bal_chg_pct < -5: score += 7 + # 숏스퀴즈 기대: 공매도 과다(거래비중≥5%) + 과매도(RSI≤35) → 숏커버링 반등 가능 + # → 단방향 패널티를 상쇄/가산 (공매도 많을수록·과매도 깊을수록 가산 ↑, 최대 +35) + if rsi is not None and weight >= 5.0 and rsi <= 35.0: + squeeze = min(35.0, (weight - 5.0) * 2.0 + (35.0 - rsi) * 0.8) + score += squeeze + reason = (reason or "") + f" 숏스퀴즈기대(RSI{rsi:.0f})" return max(-100.0, min(100.0, score)), reason @@ -2321,12 +2328,13 @@ async def calculate_daily_scores(as_of: date | None = None, notify: bool = False except: pass # 기술적 점수 (stock_technical 테이블 - Redis TTL/DB 불일치 방지) - technical_score = 0.0 + technical_score = 0.0; rsi_val = None try: ta_row = await conn.fetchrow( - "SELECT tech_score FROM stock_technical WHERE stock_code=$1", stock) + "SELECT tech_score, rsi FROM stock_technical WHERE stock_code=$1", stock) if ta_row: technical_score = float(ta_row["tech_score"] or 0) + rsi_val = float(ta_row["rsi"]) if ta_row["rsi"] is not None else None except: pass # 외국인 수급 점수 (Redis foreign:{code}) @@ -2345,7 +2353,7 @@ async def calculate_daily_scores(as_of: date | None = None, notify: bool = False s_raw = await redis_cl.get(f"short:{stock}") if s_raw: s_data = json.loads(s_raw) - short_score, short_reason = calc_short_score(s_data) + short_score, short_reason = calc_short_score(s_data, rsi=rsi_val) short_weight_val = s_data[0].get("trade_weight", 0) if s_data else 0 except: pass