feat: 관심·감시 등락률 실시간 + WS 빈응답 가드

- 관심·감시 행 등락률: KRX 기준 = 0B 피드 pct와 동일 → paintPrice가 .rt-wl-daypct를 피드 pct로 갱신 (보유행 NXT기준과 분리)
- realtime_hub: recv 빈응답 5회 연속 시 재연결(이론상 tight busy-loop 차단), 타임아웃은 정상 idle로 카운터 리셋

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-06-08 15:07:19 +09:00
parent 557f5bd8a5
commit 787834f93e
2 changed files with 12 additions and 1 deletions
+4 -1
View File
@@ -1845,7 +1845,7 @@ def _render_row(c: dict, source: str = 'watchlist') -> str:
if c.get('_show_day_change') and isinstance(day_pct, (int, float)):
d_sign = '+' if day_pct >= 0 else ''
d_cls = 'day-pct up' if day_pct > 0 else ('day-pct down' if day_pct < 0 else 'day-pct neutral')
price_cell = f'{mark_html}<span class="rt-px">{price:,}</span><span class="{d_cls}">{d_sign}{day_pct:.2f}%</span>'
price_cell = f'{mark_html}<span class="rt-px">{price:,}</span><span class="{d_cls} rt-wl-daypct">{d_sign}{day_pct:.2f}%</span>'
else:
price_cell = f'{mark_html}<span class="rt-px">{price:,}</span>'
if mode == 'watching' and isinstance(ref_price, (int, float)) and ref_price > 0:
@@ -8944,8 +8944,11 @@ window.openPinModal = openPinModal;
function paintPrice(code,q){
// 현재가 숫자 span(.rt-px) 갱신 등락률·매매표시·당일정산(실현손익) 셀은 .rt-px가 없어 자동 제외.
var t=q.price.toLocaleString();
var pctstr=(q.pct>=0?'+':'')+q.pct.toFixed(2)+'%';
document.querySelectorAll(selByCode(code)).forEach(function(r){
r.querySelectorAll('.rt-px').forEach(function(el){ if(el.textContent!==t) el.textContent=t; }); // 바뀔 때만 깜빡임 방지
// 관심·감시 등락률 KRX 기준이라 0B 피드 pct를 그대로 사용(보유행은 NXT 기준이라 paintDay가 따로 처리).
var dp=r.querySelector('.rt-wl-daypct'); if(dp){ dp.textContent=pctstr; setUD(dp,q.pct); }
});
}
function paintVI(code,active){
@@ -220,6 +220,7 @@ class RealtimeHub:
# 3) 수신 루프
_last_mkt_check = time.time()
empty_streak = 0 # 연속 빈응답 카운터 — tight busy-loop 방지
while not self._stop.is_set():
if time.time() - self.connected_at > _MAX_CONN_AGE:
sys.stderr.write('[realtime-hub] 토큰 만료 전 선제 재연결\n')
@@ -233,9 +234,16 @@ class RealtimeHub:
try:
raw = ws.recv()
except websocket.WebSocketTimeoutException:
empty_streak = 0 # 타임아웃은 정상 idle
continue
if not raw:
# 빈응답이 예외 없이 연속되면(드묾) tight loop → 5회 누적 시 재연결로 탈출
empty_streak += 1
if empty_streak >= 5:
sys.stderr.write('[realtime-hub] 빈 응답 연속 → 연결 재설정\n')
return
continue
empty_streak = 0
self._handle(raw)
finally:
self._connected.clear()