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:
+13
-5
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user