auto: 일일 백업 2026-06-23 02:00

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-06-23 02:00:01 +09:00
parent 40cc290035
commit cee32e6dfb
104 changed files with 1106 additions and 371 deletions
+11 -3
View File
@@ -1390,13 +1390,13 @@ def _fetch_all_data(entries: list[dict], only_owner: str | None = None) -> dict:
r['ohlc_krx'] = {
'open': qk['open'], 'high': qk['high'], 'low': qk['low'],
'price': qk.get('price', 0), 'change_pct': qk.get('change_pct', 0.0),
'change': qk.get('change', 0),
'change': qk.get('change', 0), 'volume': qk.get('volume', 0),
}
if qn_valid:
r['ohlc_nxt'] = {
'open': qn['open'], 'high': qn['high'], 'low': qn['low'],
'price': qn.get('price', 0), 'change_pct': qn.get('change_pct', 0.0),
'change': qn.get('change', 0),
'change': qn.get('change', 0), 'volume': qn.get('volume', 0),
}
# 단일 캔들 primary — 4값(open/high/low/price) 모두 있는 응답만 후보. 거래 없는 시간대 0값 응답 배제.
qk_full = qk_valid and qk.get('open') and qk.get('high') and qk.get('low')
@@ -2364,6 +2364,12 @@ def _render_ohlc_mini_dual(krx: dict | None, nxt: dict | None, active_market: st
if not price:
return f'<span class="ohlc-cell-empty{dim_cls}">-</span>'
return f'<span class="ohlc-cell{dim_cls}"><span class="ohlc-price">{price:,}원</span></span>'
elif key == 'volume':
# 거래량 — 등락률 없이 주 수만 표시.
vol = d.get('volume', 0) or 0
if not vol:
return f'<span class="ohlc-cell-empty{dim_cls}">-</span>'
return f'<span class="ohlc-cell{dim_cls}"><span class="ohlc-price">{vol:,}주</span></span>'
else:
price = d.get(key, 0)
base = d.get('price', 0) - d.get('change', 0)
@@ -2397,6 +2403,7 @@ def _render_ohlc_mini_dual(krx: dict | None, nxt: dict | None, active_market: st
('고가', 'high'),
('저가', 'low'),
('당일종가', 'close_today'),
('거래량', 'volume'),
]
krx_th_dim = ' ohlc-dim' if krx_dim else ''
nxt_th_dim = ' ohlc-dim' if nxt_dim else ''
@@ -10336,7 +10343,8 @@ class Handler(BaseHTTPRequestHandler):
try:
import stock_analysis as sa
import kiwoom_client as kc
mins = kc.get_minute_candles(code, tic_scope=tic_scope, today_only=True)
# _AL(통합) — 정규장(KRX)→NXT(15:30~20:00)가 한 차트에 연속.
mins = kc.get_minute_candles(code, tic_scope=tic_scope, today_only=True, exchange='AL')
if not mins:
self.send_error(404, 'no minute candles')
return
@@ -979,18 +979,21 @@ def get_daily_candles(code: str, count: int = 250, base_dt: str | None = None,
def get_minute_candles(code: str, tic_scope: int = 5, today_only: bool = True,
account_label: str | None = None) -> list[dict]:
account_label: str | None = None, exchange: str = 'KRX') -> list[dict]:
"""주식 분봉 차트 (ka10080). 수정주가 기준.
tic_scope: 1/3/5/10/15/30/45/60 분. 한 호출에 ~900봉 (5분봉 ≈ 11일치).
today_only: True 면 오늘 봉만 추출.
exchange: 'KRX'=KRX 단독(기본) / 'NX'=NXT 단독 / 'AL'=통합(KRX→NXT 연속).
NX/AL은 stk_cd에 suffix를 붙여 NXT 시간대(15:30~20:00)에도 분봉이 이어진다.
반환: 시간 오름차순 list of dict — {date, time, open, high, low, close, volume}
date='YYYYMMDD', time='HHMMSS'.
"""
label = account_label or _default_account_label()
suffix = f'_{exchange}' if exchange in ('NX', 'AL') else ''
body = {
'stk_cd': _clean_code(code),
'stk_cd': _clean_code(code) + suffix,
'tic_scope': str(tic_scope),
'upd_stkpc_tp': '1',
}
@@ -859,7 +859,7 @@ def _build_data_block(snap: dict) -> str:
lines.append(f'### 종목: {snap["name"]} ({snap["code"]})')
lines.append(f'- 현재가: {b["price"]:,}원 ({b["change"]:+,} / {b["change_pct"]:+.2f}%)')
lines.append(f'- 오늘 시고저: 시 {b["open"]:,} / 고 {b["high"]:,} / 저 {b["low"]:,}')
lines.append(f'- 시가총액: {b["market_cap"]:,} (단위 키움원본) · 상장주식수: {b["listed_shares"]:,}주')
lines.append(f'- 시가총액: {_fmt_market_cap(b["market_cap"])} · 상장주식수: {b["listed_shares"]:,}주')
lines.append(f'- 52주 고저: 고 {b["high_52w"]:,}원 ({_fmt_date(b["high_52w_dt"])}) / 저 {b["low_52w"]:,}원 ({_fmt_date(b["low_52w_dt"])})')
lines.append(f'- 회전율(오늘): {b["turnover_rate"]:.2f}% · 외국인 보유비율: {b["foreign_holding_pct"]:.2f}%')
lines.append(f'- PER {b["per"]} · PBR {b["pbr"]} · ROE {b["roe"]} · EPS {b["eps"]:,} · BPS {b["bps"]:,}')
@@ -1121,6 +1121,19 @@ def _fmt_pct_signed(v: float) -> str:
return f'{v:+.2f}%'
def _fmt_market_cap(eok) -> str:
"""키움 mac(억원) → 사람이 읽는 '2,084조 1,983억원'. 0/None은 '—'."""
if not isinstance(eok, (int, float)) or eok <= 0:
return '—'
eok = int(eok)
jo, rem = divmod(eok, 10000)
if jo and rem:
return f'{jo:,}조 {rem:,}억원'
if jo:
return f'{jo:,}조원'
return f'{rem:,}억원'
def _fmt_date(s) -> str:
"""20260519 → 2026-05-19. 형식 다르면 원본 반환."""
s = str(s or '').strip()
@@ -1477,6 +1490,12 @@ def build_report_html(snap: dict, parsed: dict) -> str:
v = p.get(field)
return f'{v:,}{suffix}' if isinstance(v, (int, float)) else '—'
def _peer_mac(p: dict) -> str:
if p.get('error'):
return '<span style="color:#ef4444;font-size:11px">조회 실패</span>'
v = p.get('market_cap')
return _fmt_market_cap(v) if isinstance(v, (int, float)) else '—'
def _peer_pct(p: dict, field: str) -> str:
if p.get('error'):
return '<span style="color:#ef4444;font-size:11px">조회 실패</span>'
@@ -1501,8 +1520,7 @@ def build_report_html(snap: dict, parsed: dict) -> str:
('오늘 시가', f'{b["open"]:,}원', lambda p: _peer_int(p, 'open', '원')),
('오늘 고가', f'{b["high"]:,}원', lambda p: _peer_int(p, 'high', '원')),
('오늘 저가', f'{b["low"]:,}원', lambda p: _peer_int(p, 'low', '원')),
('시가총액', f'{b["market_cap"]:,} <span style="color:#7a8493;font-size:11px">(키움 원본 단위)</span>',
lambda p: _peer_int(p, 'market_cap', '')),
('시가총액', _fmt_market_cap(b["market_cap"]), _peer_mac),
('상장주식수', f'{b["listed_shares"]:,}주', lambda p: _peer_int(p, 'listed_shares', '주')),
('52주 최고', f'{b["high_52w"]:,}원 <span style="color:#7a8493;font-size:11px">({_fmt_date(b["high_52w_dt"])})</span>',
lambda p: _peer_52w(p, 'high_52w', 'high_52w_dt')),