From 904ae5c4155f0e47e0f620f2edfed081d015a7f7 Mon Sep 17 00:00:00 2001 From: hyowons Date: Mon, 15 Jun 2026 11:34:26 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=ED=96=89=20NXT/KRX=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EB=A7=88?= =?UTF-8?q?=ED=81=AC=20=E2=86=92=20=EA=B1=B0=EB=9E=98=EA=B0=80=EB=8A=A5=20?= =?UTF-8?q?=EC=B4=88=EB=A1=9D=EC=A0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _render_row: 가격 출처(NXT/KRX) 텍스트 배지 대신 '지금 거래시간 여부'로 초록점(.px-dot.tradable) / 점 없음. 정규장=전부, NXT시간대=NXT거래종목만 (_nxt_price>0), 장마감·휴장·주말=점 없음. 가격 None은 기존대로 점 없음 - 보유종목 상세행(_render_holding_row)의 NXT/KRX 텍스트는 그대로 유지 - CSS .px-dot 추가 (px-mark.px-nxt/krx는 보유행이 계속 사용) Co-Authored-By: Claude Opus 4.8 --- agents/stock/workspace/scripts/behive_web.py | 39 ++++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/agents/stock/workspace/scripts/behive_web.py b/agents/stock/workspace/scripts/behive_web.py index 8a85b6f..5945506 100644 --- a/agents/stock/workspace/scripts/behive_web.py +++ b/agents/stock/workspace/scripts/behive_web.py @@ -596,6 +596,16 @@ def _market_phase_state() -> dict: return {'phase': 'closed', 'label': '장외 시간', 'active': False, 'time': time_str} +def _is_preopen_market_day() -> bool: + """거래일 08:00 전. 장전 가격으로 당일 손익을 만들지 않는다.""" + now = datetime.now(KST) + return ( + now.weekday() < 5 + and now.hour < 8 + and now.strftime('%Y-%m-%d') not in _load_holidays() + ) + + def _load_watchlist() -> list[dict]: if not WATCHLIST.exists(): return [] @@ -975,10 +985,15 @@ def _build_owner_data(positions_by_acc: dict, balances: dict, journal_by_label: net_cash_flow = cash_in - cash_out day_pl_total = (total_net - prev_net - net_cash_flow) if prev_net is not None else None + day_pl_reason = None + if _is_preopen_market_day(): + day_pl_total = None + day_pl_reason = 'preopen' # 휴장일/주말은 시장 변동이 없어야 자연. 단 NXT 가격 보정으로 어제 스냅샷과 차이가 생겨 # day_pl_total이 0이 아닌 값을 갖게 됨 → 사용자 혼란 차단 위해 None으로 강제(미표시). if _market_phase_state().get('phase') in ('holiday', 'weekend'): day_pl_total = None + day_pl_reason = 'closed' owner_data[owner] = { 'labels': lbls, @@ -993,6 +1008,7 @@ def _build_owner_data(positions_by_acc: dict, balances: dict, journal_by_label: 'traded_today': traded_today, 'prev_net': prev_net, 'day_pl_total': day_pl_total, + 'day_pl_reason': day_pl_reason, 'realized_pl_total': realized_pl_total, 'realized_fees_total': realized_fees_total, 'cash_in': cash_in, @@ -1863,14 +1879,19 @@ def _render_row(c: dict, source: str = 'watchlist') -> str: price_cell = '-' diff_cell = f'{html.escape(err) if err else "조회 불가"}' else: - # 가격 출처 마크 — _krx_price/_nxt_price 비교. NXT 가격이 살아있고 KRX와 다르면 NXT. - krx_p = c.get('_krx_price', 0) or 0 + # 거래가능 표시 — 지금 거래시간이면 초록점, 아니면 점 없음. + # 정규장: KRX+NXT 모두 거래 → 초록점 + # NXT 시간대: NXT 거래되는 종목(_nxt_price 살아있음)만 초록점 + # 장마감·휴장·주말: 점 없음 nxt_p = c.get('_nxt_price', 0) or 0 - if nxt_p > 0 and (krx_p == 0 or krx_p != nxt_p): - mark = 'NXT' + _phase = _market_phase_state()['phase'] + if _phase == 'regular': + _tradable = True + elif _phase == 'nxt': + _tradable = nxt_p > 0 else: - mark = 'KRX' - mark_html = f'{mark}' + _tradable = False + mark_html = '' if _tradable else '' # 미리보기 등락은 KRX 전일종가 대비 — ka10095의 change_pct가 이미 KRX 기준이라 그대로 사용. day_pct = c.get('day_change_pct') if c.get('_show_day_change') and isinstance(day_pct, (int, float)): @@ -2131,8 +2152,10 @@ def _render_owner_kpi(owner: str, d: dict, owner_label_text: str, compact: bool day_pl_total = d.get('day_pl_total') if day_pl_total is None: + if d.get('day_pl_reason') == 'preopen': + day_pl_html = '장 시작 전' # 휴장일/주말은 시장 변동이 없는 정상 상태 — "전날 스냅샷 없음"과 구분된 문구. - if _market_phase_state().get('phase') in ('holiday', 'weekend'): + elif _market_phase_state().get('phase') in ('holiday', 'weekend'): day_pl_html = '휴장 — 변동 없음' else: day_pl_html = '전날 스냅샷 없음' @@ -4235,6 +4258,8 @@ details.row + .section-label { margin-top: 14px; padding-top: 24px; border-top-c .ohlc-mini.ohlc-dual .ohlc-cell.ohlc-dim .ohlc-pct { color: #5a5e69 !important; } .ohlc-mini.ohlc-dual .ohlc-cell-empty.ohlc-dim { color: #3a3e48; } /* 미리보기 현재가 왼쪽 출처 마크 — NXT(파랑톤) / KRX(회색톤). 어두운 채도로 보조 정보 강조 X. */ +.px-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 5px; vertical-align: 1px; } +.px-dot.tradable { background: #2ecc71; box-shadow: 0 0 4px rgba(46,204,113,0.6); } .px-mark { font-size: 8px; font-weight: 600; margin-right: 4px; padding: 1px 3px; border-radius: 2px; background: #14171f; vertical-align: 2px; letter-spacing: 0.3px; } .px-mark.px-nxt { color: #2d5a8a; } .px-mark.px-krx { color: #4a4e58; }