feat: 공매도 점수 양방향화 (과매도+공매도과다 → 숏스퀴즈 반등 가산)

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) <noreply@anthropic.com>
This commit is contained in:
kyu
2026-06-03 22:12:11 +09:00
parent 04ab01a166
commit 05682a8a3e
+13 -5
View File
@@ -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). 기본은 공매도 많을수록 패널티지만,
공매도 과다 + 과매도(RSI35) 숏커버링 반등(스퀴즈) 기대로 가산 양방향."""
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