감성평가 정확성 강화 (A+B+C+D+E)

score-engine/main.py:
  A) 시간감쇠: exp 3일 반감기 (3d→0.5, 7d→0.2)
  A) 유사뉴스 중복 보정: sqrt(similar_count) cap 2.5x
  C) catalyst 패턴 매칭: "영업이익 급증" 등 세분 라벨을 실적/수주/배당/리스크/
     M&A/신제품/규제/정책/모멘텀 9개 그룹으로 통합
  B) calc_sentiment_momentum: 최근 3일 vs 이전 4일 변화율 (-50~+50)
  D) calc_market_sentiment_baseline + per-stock sentiment_alpha
     (-100~+100, 시장 평균 대비 고유 edge)
  E) calc_news_surge_and_attention: 7d/28d 비율 + log 관심도(0~100)

  종합점수 통합:
   - news_score (0.18)는 A+C로 직접 정확도 향상
   - sentiment_momentum * 0.06 + sentiment_alpha * 0.03 → max ±5 보너스
   - surge≥3 AND news_score>10 → +2 (강한 호재 attention 가산)

stock_scores 스키마: 4컬럼 추가
  - sentiment_momentum, sentiment_alpha, attention_score, news_surge_ratio

dashboard-api:
  - /api/formulas/matrix: 5컬럼(news_score + 4신규) 추가 노출
  - index.html: 10공식 매트릭스에 뉴스/M/α/관심/× 5컬럼 추가

검증: 1464종목 재산출 완료. attention >0 종목 1294(88%), 모멘텀 변동 259종목.
추천 분포 안정 (강력매수 19 / 매수관심 104 / 관망 1249 / 매도관심 73 / 강력매도 22).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kyu
2026-05-20 22:54:17 +09:00
parent 324c7a4b95
commit 47e7a5fb66
3 changed files with 223 additions and 26 deletions
+35
View File
@@ -1730,6 +1730,11 @@ async function renderFormulas(){
<th title="Mohanram G-Score (vs 섹터 중앙값)">G</th>
<th title="Amihud 비유동성">Amh</th>
<th title="저베타 알파 (Frazzini-Pedersen BAB)">β</th>
<th title="catalyst×감쇠×중복 가중 뉴스점수 (-100~100)">뉴스</th>
<th title="감정 모멘텀: 최근 3일 vs 이전 4일 변화율">M</th>
<th title="시장 평균 대비 sentiment alpha">α</th>
<th title="최근 7일 뉴스건수 log 스케일 관심도 (0~100)">관심</th>
<th title="평소 대비 뉴스 폭증 배율 (x)">×</th>
</tr></thead><tbody>`;
rows.forEach(r=>{
let sig = r.signals || {};
@@ -1757,6 +1762,36 @@ async function renderFormulas(){
${_cell('G', r.g_score!=null?(+r.g_score).toFixed(0):'-', hit('gscore'), 'Mohanram G (vs 섹터)')}
${_cell('Amh', r.amihud_illiq!=null?(+r.amihud_illiq).toFixed(0):'-', hit('amihud'), 'Amihud 비유동성')}
${_cell('β', r.market_beta!=null?(+r.market_beta).toFixed(2):'-', hit('beta'), '60일 KOSPI 회귀 베타')}
${(()=>{ // 뉴스점수
const v = r.news_score;
if(v==null) return '<td style="text-align:center;color:#37474F">-</td>';
const c = v>=10?'#69F0AE':v<=-10?'#FF8A80':'#90A4AE';
return `<td style="text-align:center;font-family:'JetBrains Mono',monospace;color:${c};font-weight:700">${(+v).toFixed(0)}</td>`;
})()}
${(()=>{ // 모멘텀
const v = r.sentiment_momentum;
if(v==null) return '<td style="text-align:center;color:#37474F">-</td>';
const c = v>=5?'#69F0AE':v<=-5?'#FF8A80':'#90A4AE';
return `<td style="text-align:center;font-family:'JetBrains Mono',monospace;color:${c}">${(+v).toFixed(1)}</td>`;
})()}
${(()=>{ // alpha
const v = r.sentiment_alpha;
if(v==null) return '<td style="text-align:center;color:#37474F">-</td>';
const c = v>=5?'#69F0AE':v<=-5?'#FF8A80':'#90A4AE';
return `<td style="text-align:center;font-family:'JetBrains Mono',monospace;color:${c}">${v>0?'+':''}${(+v).toFixed(0)}</td>`;
})()}
${(()=>{ // attention
const v = r.attention_score;
if(v==null) return '<td style="text-align:center;color:#37474F">-</td>';
const c = v>=70?'#FFD740':v>=40?'#69F0AE':'#90A4AE';
return `<td style="text-align:center;font-family:'JetBrains Mono',monospace;color:${c}">${(+v).toFixed(0)}</td>`;
})()}
${(()=>{ // surge
const v = r.news_surge_ratio;
if(v==null) return '<td style="text-align:center;color:#37474F">-</td>';
const c = v>=3?'#FFD740':v>=1.5?'#69F0AE':'#546E7A';
return `<td style="text-align:center;font-family:'JetBrains Mono',monospace;color:${c}">${(+v).toFixed(1)}×</td>`;
})()}
</tr>`;
});
h += `</tbody></table></div>`;
+3 -1
View File
@@ -2017,7 +2017,9 @@ async def formulas_matrix(limit: int = Query(default=50, ge=5, le=200)):
s.total_score, s.recommendation, s.buy_votes, s.sell_votes,
s.magic_score, s.f_score, s.altman_z, s.peg, s.momentum_pct,
s.beneish_score, s.gpa_pct, s.g_score, s.amihud_illiq, s.market_beta,
s.signals, s.sector
s.signals, s.sector,
s.news_score, s.sentiment_momentum, s.sentiment_alpha,
s.attention_score, s.news_surge_ratio
FROM stock_scores s
LEFT JOIN dart_corps d ON d.stock_code = s.stock_code
WHERE s.score_date = (SELECT MAX(score_date) FROM stock_scores)