feat: 미리보기 행 NXT/KRX 텍스트 마크 → 거래가능 초록점
- _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 <noreply@anthropic.com>
This commit is contained in:
@@ -596,6 +596,16 @@ def _market_phase_state() -> dict:
|
|||||||
return {'phase': 'closed', 'label': '장외 시간', 'active': False, 'time': time_str}
|
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]:
|
def _load_watchlist() -> list[dict]:
|
||||||
if not WATCHLIST.exists():
|
if not WATCHLIST.exists():
|
||||||
return []
|
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
|
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_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 가격 보정으로 어제 스냅샷과 차이가 생겨
|
# 휴장일/주말은 시장 변동이 없어야 자연. 단 NXT 가격 보정으로 어제 스냅샷과 차이가 생겨
|
||||||
# day_pl_total이 0이 아닌 값을 갖게 됨 → 사용자 혼란 차단 위해 None으로 강제(미표시).
|
# day_pl_total이 0이 아닌 값을 갖게 됨 → 사용자 혼란 차단 위해 None으로 강제(미표시).
|
||||||
if _market_phase_state().get('phase') in ('holiday', 'weekend'):
|
if _market_phase_state().get('phase') in ('holiday', 'weekend'):
|
||||||
day_pl_total = None
|
day_pl_total = None
|
||||||
|
day_pl_reason = 'closed'
|
||||||
|
|
||||||
owner_data[owner] = {
|
owner_data[owner] = {
|
||||||
'labels': lbls,
|
'labels': lbls,
|
||||||
@@ -993,6 +1008,7 @@ def _build_owner_data(positions_by_acc: dict, balances: dict, journal_by_label:
|
|||||||
'traded_today': traded_today,
|
'traded_today': traded_today,
|
||||||
'prev_net': prev_net,
|
'prev_net': prev_net,
|
||||||
'day_pl_total': day_pl_total,
|
'day_pl_total': day_pl_total,
|
||||||
|
'day_pl_reason': day_pl_reason,
|
||||||
'realized_pl_total': realized_pl_total,
|
'realized_pl_total': realized_pl_total,
|
||||||
'realized_fees_total': realized_fees_total,
|
'realized_fees_total': realized_fees_total,
|
||||||
'cash_in': cash_in,
|
'cash_in': cash_in,
|
||||||
@@ -1863,14 +1879,19 @@ def _render_row(c: dict, source: str = 'watchlist') -> str:
|
|||||||
price_cell = '<span class="muted">-</span>'
|
price_cell = '<span class="muted">-</span>'
|
||||||
diff_cell = f'<span class="muted">{html.escape(err) if err else "조회 불가"}</span>'
|
diff_cell = f'<span class="muted">{html.escape(err) if err else "조회 불가"}</span>'
|
||||||
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
|
nxt_p = c.get('_nxt_price', 0) or 0
|
||||||
if nxt_p > 0 and (krx_p == 0 or krx_p != nxt_p):
|
_phase = _market_phase_state()['phase']
|
||||||
mark = 'NXT'
|
if _phase == 'regular':
|
||||||
|
_tradable = True
|
||||||
|
elif _phase == 'nxt':
|
||||||
|
_tradable = nxt_p > 0
|
||||||
else:
|
else:
|
||||||
mark = 'KRX'
|
_tradable = False
|
||||||
mark_html = f'<span class="px-mark px-{mark.lower()}">{mark}</span>'
|
mark_html = '<span class="px-dot tradable" title="지금 거래 가능"></span>' if _tradable else ''
|
||||||
# 미리보기 등락은 KRX 전일종가 대비 — ka10095의 change_pct가 이미 KRX 기준이라 그대로 사용.
|
# 미리보기 등락은 KRX 전일종가 대비 — ka10095의 change_pct가 이미 KRX 기준이라 그대로 사용.
|
||||||
day_pct = c.get('day_change_pct')
|
day_pct = c.get('day_change_pct')
|
||||||
if c.get('_show_day_change') and isinstance(day_pct, (int, float)):
|
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')
|
day_pl_total = d.get('day_pl_total')
|
||||||
if day_pl_total is None:
|
if day_pl_total is None:
|
||||||
|
if d.get('day_pl_reason') == 'preopen':
|
||||||
|
day_pl_html = '<span class="muted small">장 시작 전</span>'
|
||||||
# 휴장일/주말은 시장 변동이 없는 정상 상태 — "전날 스냅샷 없음"과 구분된 문구.
|
# 휴장일/주말은 시장 변동이 없는 정상 상태 — "전날 스냅샷 없음"과 구분된 문구.
|
||||||
if _market_phase_state().get('phase') in ('holiday', 'weekend'):
|
elif _market_phase_state().get('phase') in ('holiday', 'weekend'):
|
||||||
day_pl_html = '<span class="muted small">휴장 — 변동 없음</span>'
|
day_pl_html = '<span class="muted small">휴장 — 변동 없음</span>'
|
||||||
else:
|
else:
|
||||||
day_pl_html = '<span class="muted small">전날 스냅샷 없음</span>'
|
day_pl_html = '<span class="muted small">전날 스냅샷 없음</span>'
|
||||||
@@ -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.ohlc-dim .ohlc-pct { color: #5a5e69 !important; }
|
||||||
.ohlc-mini.ohlc-dual .ohlc-cell-empty.ohlc-dim { color: #3a3e48; }
|
.ohlc-mini.ohlc-dual .ohlc-cell-empty.ohlc-dim { color: #3a3e48; }
|
||||||
/* 미리보기 현재가 왼쪽 출처 마크 — NXT(파랑톤) / KRX(회색톤). 어두운 채도로 보조 정보 강조 X. */
|
/* 미리보기 현재가 왼쪽 출처 마크 — 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 { 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-nxt { color: #2d5a8a; }
|
||||||
.px-mark.px-krx { color: #4a4e58; }
|
.px-mark.px-krx { color: #4a4e58; }
|
||||||
|
|||||||
Reference in New Issue
Block a user