auto: 일일 백업 2026-07-16 02:00

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-07-16 02:00:03 +09:00
parent 66d372b50e
commit 9948122ed3
160 changed files with 3033 additions and 2242 deletions
+65 -1
View File
@@ -3057,6 +3057,12 @@ def _render_holding_row(r: dict, total_value: int, show_day_change: bool = False
pl_pairs: list[str] = [
f'<dt>평가손익</dt><dd class="num"><span class="{pcls} rt-profit-detail">{sign}{profit:,}원 ({sign}{profit_rate:.2f}%)</span></dd>',
]
# 수익률 마이너스(손실)일 때만 — 평단가 회복(본전)에 필요한 상승률.
if profit_rate < 0 and price > 0 and avg > price:
breakeven_pct = (avg - price) / price * 100
pl_pairs.append(
f'<dt>평단가대비</dt><dd class="num"><span class="muted small">(+{breakeven_pct:.2f}%)</span></dd>'
)
if day_change:
dcls = _profit_class(day_change)
dsign = '+' if day_change >= 0 else ''
@@ -3182,8 +3188,10 @@ def _render_holding_row(r: dict, total_value: int, show_day_change: bool = False
f'<dt>매수후 고점</dt><dd class="num">{peak_high:,}'
f'<span class="{ddcls}">({dd_pct:.2f}%)</span></dd>'
)
recover_pct = (peak_high - price) / price * 100
pl_pairs.append(
f'<dt>고점대비</dt><dd class="num"><span class="muted small">{dd_won:,}원</span></dd>'
f'<dt>고점대비</dt><dd class="num"><span class="muted small">{dd_won:,}원</span> '
f'<span class="muted small">(+{recover_pct:.2f}%)</span></dd>'
)
pl_pairs.append('<div class="pl-divider"></div>')
pl_dl_inner = ''.join(pl_pairs)
@@ -5706,6 +5714,9 @@ table.market-adr td.adr-breakdown { font-size: 11px; color: #8b8f9a; }
.order-info .pl-down { color: #8db4ff; white-space: nowrap; }
.order-info b.qty-over { color: #ff8a95; }
input[data-order-qty].qty-over { color: #ff8a95; }
.order-adr-banner { padding: 8px 12px; margin-bottom: 12px; border-radius: 8px; font-size: 12.5px; font-weight: 600; line-height: 1.4; }
.order-adr-banner.adr-over { background: #2a1418; border: 1px solid #5a1f28; color: #ff8a95; }
.order-adr-banner.adr-under { background: #101b2e; border: 1px solid #1f3a5a; color: #8ab6ff; }
.order-modal-msg { padding: 12px; margin-bottom: 12px; border-radius: 8px; font-size: 13px; line-height: 1.6; white-space: pre-wrap; font-variant-numeric: tabular-nums; }
.order-modal-msg.error { background: #2a1418; border: 1px solid #5a1f28; color: #ff8a95; }
.order-modal-msg.success { background: #142a18; border: 1px solid #1f5a28; color: #8aff95; }
@@ -5869,6 +5880,30 @@ def _adr_class(adr: float | None) -> tuple[str, str]:
return 'flat', ''
def _market_adr_signal(market: str) -> dict | None:
"""거래 모달용 — 시장(KOSPI/KOSDAQ) 20일 정통 ADR + 과매수/과매도 판정.
_adr_class와 동일 임계(120 과매수·80 과매도). 중립·20 미만 누적이면 None(배너 미표시).
반환 {'value': float, 'zone': '과매수'|'과매도', 'label': '코스피'|'코스닥'}."""
if market not in ('KOSPI', 'KOSDAQ'):
return None
try:
live_rows = _fetch_market_indicators() or []
today = next((r for r in live_rows if r.get('symbol') == market), None)
ma, count, _ = _compute_adr_ma_latest(market, today)
if ma is None or count < ADR_MA_WINDOW:
return None
if ma >= 120:
zone = '과매수'
elif ma <= 80:
zone = '과매도'
else:
return None
return {'value': round(ma, 1), 'zone': zone,
'label': _ADR_MARKET_LABELS.get(market, market)}
except Exception:
return None
def _build_adr_series_with_live(history_rows: list[dict], live_pt: dict | None) -> list[dict]:
"""history(오래된 순) + 오늘 라이브 1점을 합쳐 [{date,adr,rise,fall,is_live}, ...] 반환.
rise/fall(상승·하락 종목수) 정통 합계식 ADR 산출용. 라이브는 history 마지막 date 다를 때만 append."""
@@ -6772,6 +6807,7 @@ def _render_calc_modal() -> str:
'<div class="calc-result" data-calc-result>'
'<div class="calc-line"><span class="calc-lbl">등락률</span><span class="calc-val" data-calc-pct>—</span></div>'
'<div class="calc-line"><span class="calc-lbl">차이</span><span class="calc-val" data-calc-diff>—</span></div>'
'<div class="calc-line"><span class="calc-lbl">기준가 회복 필요</span><span class="calc-val" data-calc-recover>—</span></div>'
'<div class="calc-hint" data-calc-hint>기준가·현재가를 입력하세요.</div>'
'</div>'
'</div>'
@@ -6997,6 +7033,7 @@ def _render_order_modal() -> str:
'<span class="change" data-order-change>—</span>'
'<span class="order-market-phase" data-market-phase>—</span>'
'</div>'
'<div class="order-adr-banner hidden" data-order-adr></div>'
'<div class="order-modal-msg hidden" data-order-msg></div>'
# 1단계: 입력
'<div data-order-step="1">'
@@ -8602,11 +8639,13 @@ def render_html() -> str:
'var cur=num(document.querySelector("[data-calc-cur]"));'
'var pctEl=document.querySelector("[data-calc-pct]");'
'var diffEl=document.querySelector("[data-calc-diff]");'
'var recEl=document.querySelector("[data-calc-recover]");'
'var hintEl=document.querySelector("[data-calc-hint]");'
'if(!pctEl||!diffEl)return;'
'if(isNaN(base)||isNaN(cur)||base===0){'
'pctEl.textContent="";pctEl.style.color=NEU;'
'diffEl.textContent="";diffEl.style.color=NEU;'
'if(recEl){recEl.textContent="";recEl.style.color=NEU;}'
'if(hintEl)hintEl.textContent=(!isNaN(base)&&base===0)?"기준가는 0이 될 수 없어요.":"기준가·현재가를 입력하세요.";'
'return;'
'}'
@@ -8617,6 +8656,13 @@ def render_html() -> str:
'var dsign=diff>0?"+":(diff<0?"":"");'
'pctEl.textContent=psign+Math.abs(pct).toFixed(2)+"%";pctEl.style.color=col;'
'diffEl.textContent=dsign+Math.abs(diff).toLocaleString()+"";diffEl.style.color=col;'
'if(recEl){'
'if(cur===0){recEl.textContent="";recEl.style.color=NEU;}'
'else{var rec=(base-cur)/cur*100;'
'if(rec>0.005){recEl.textContent="+"+rec.toFixed(2)+"%";}'
'else{recEl.textContent="달성";}'
'recEl.style.color=NEU;}'
'}'
'if(hintEl)hintEl.textContent="";'
'}'
'document.addEventListener("input",function(e){'
@@ -9910,6 +9956,18 @@ function renderBook(book){
che.classList.remove('up','down');
if(book.change>0) che.classList.add('up'); else if(book.change<0) che.classList.add('down');
}
var adrEl = $('[data-order-adr]');
if(adrEl){
var a = book.adr;
if(a && a.zone){
var isOver = a.zone === '과매수';
adrEl.className = 'order-adr-banner ' + (isOver ? 'adr-over' : 'adr-under');
adrEl.textContent = (isOver?'🔴 ':'🔵 ') + a.label + ' ' + a.zone + ' 구간입니다 (20일 ADR ' + a.value + ')';
} else {
adrEl.className = 'order-adr-banner hidden';
adrEl.textContent = '';
}
}
var ob = $('[data-orderbook]'); if(!ob) return;
var prevScroll = ob.scrollTop; // 가격 변동에도 스크롤 고정
var cur = book.price || 0;
@@ -11480,6 +11538,12 @@ class Handler(BaseHTTPRequestHandler):
book['upper_limit'] = 0
book['lower_limit'] = 0
book['nxt_enable'] = nxt_enable # 위에서 판정한 값 재사용
# 종목이 속한 시장(KOSPI/KOSDAQ)의 20일 ADR 과매수/과매도 — 극단일 때만 실림.
_mkt = (meta or {}).get('market') or ''
book['market'] = _mkt
_adr_sig = _market_adr_signal(_mkt)
if _adr_sig:
book['adr'] = _adr_sig
book.pop('raw', None)
except Exception as e:
traceback.print_exc()