auto: 일일 백업 2026-08-12 02:00

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-08-12 02:00:02 +09:00
parent 49a0651f05
commit c93d1ac385
117 changed files with 1714 additions and 1366 deletions
+35 -2
View File
@@ -5151,6 +5151,7 @@ def _render_net_worth_chart(series: list[tuple[str, int]], current_net: int | No
# 호버/터치 인터랙션용 메타. 각 포인트의 viewBox 좌표·날짜·순자산·그날 손익·누적 손익·입출금.
base_v = values[0]
prev_v: int | None = None
cum_cf_net = 0 # baseline 이후 누적 외부 입출금 (net 모드 누적손익 보정용)
pts_meta: list[dict] = []
today_kst_str = today_kst # 위에서 산출됨
for i, (d_str, v_i) in enumerate(full):
@@ -5169,13 +5170,19 @@ def _render_net_worth_chart(series: list[tuple[str, int]], current_net: int | No
day_val = int(plot_values[i]) if per_period else (int(v_i - prev_v) if prev_v is not None else int(v_i))
total_val = int(v_i)
else:
# 외부 입출금은 손익이 아니므로 day/total 에서 제거 (KPI '당일 평가손익'·pnl 모드와 동일 정책).
# full[0]은 항상 baseline 점이라 그 점의 입출금은 이미 자기 순자산 값에 녹아 있다 → 누적은 i>0부터.
cf_net_i = (int(cf.get('cash_in', 0) or 0) - int(cf.get('cash_out', 0) or 0)) if cf else 0
if i > 0:
cum_cf_net += cf_net_i
if prev_v is not None:
day_val = int(v_i - prev_v)
day_val = int(v_i - prev_v - cf_net_i)
elif prev_baseline_nw is not None:
day_val = int(v_i - prev_baseline_nw)
else:
day_val = 0
total_val = int(v_i - prev_baseline_nw) if prev_baseline_nw is not None else int(v_i - base_v)
base_nw = prev_baseline_nw if prev_baseline_nw is not None else base_v
total_val = int(v_i - base_nw - cum_cf_net)
meta = {
'd': d_str,
'v': int(v_i),
@@ -5344,6 +5351,8 @@ def _render_net_worth_chart(series: list[tuple[str, int]], current_net: int | No
f'<span class="net-chart-last"'
+ (f' data-rt-nw-owner="{html.escape(rt_owner, quote=True)}" data-rt-nw-basenet="{rt_base_net}" data-rt-nw-baseval="{last_v}" data-rt-nw-pnl="{1 if is_pnl else 0}" data-rt-nw-live="{1 if has_live else 0}"'
f' data-rt-nw-ylo="{y_lo}" data-rt-nw-span="{span}" data-rt-nw-top="{T}" data-rt-nw-ih="{ih}" data-rt-nw-x="{last_x:.2f}"'
# 헤더 값도 라벨과 같은 델타로 움직인다 — hdr=렌더 시점 헤더 수치, hdr0=net 모드 % 분모(pnl은 0=% 없음).
f' data-rt-nw-hdr="{_hdr_val if is_pnl else delta}" data-rt-nw-hdr0="{0 if is_pnl else values[0]}"'
if (rt_owner is not None and rt_base_net is not None and has_live) else '')
+ f'>{html.escape(last_label_v)}</span>'
'</span>'
@@ -9119,6 +9128,11 @@ def render_html() -> str:
'var line=hg.querySelector("line"),dot=hg.querySelector("circle");'
'var pts;try{pts=JSON.parse(root.getAttribute("data-pts")||"[]");}catch(e){pts=[];}'
'if(!pts.length)return;'
# 1초 실시간 틱(setOwnerKpis)이 라이브 점을 제자리 갱신할 수 있게 배열·기준값 노출.
'root.__nw_pts=pts;'
'var lel=root.querySelector(".net-chart-last");'
'root.__nw_liveIdx=(lel&&lel.getAttribute("data-rt-nw-live")==="1")?pts.length-1:-1;'
'if(root.__nw_liveIdx>=0){var _lb=pts[root.__nw_liveIdx];root.__nw_liveBase={v:_lb.v,day:_lb.day,total:_lb.total};}'
'var dEl=tip.querySelector(".tip-date");'
'var dayEl=tip.querySelector(".tip-day");'
'var totEl=tip.querySelector(".tip-total");'
@@ -13441,6 +13455,25 @@ window.openPinModal = openPinModal;
var v=Math.round(baseval+(T.net-basenet)); // 순자산 변동분 = 손익 변동분(baseline·cf는 세션 고정)
var s=(el.getAttribute('data-rt-nw-pnl')==='1') ? ((v>=0?'+':'')+Math.abs(v).toLocaleString()+'') : (v.toLocaleString()+'');
el.textContent=s+' (장중)';
// 차트 헤더 ·오늘 툴팁도 같은 델타로 이동 둘은 서버 렌더 산출물이라
// 여기서 옮기면 15:30~20:00(NXT)처럼 패널 swap 트리거가 없는 구간에서 렌더 시점 값에 멈춘다.
var ch=el.closest('.net-chart');
if(ch){
var dv=Math.round(T.net-basenet);
var hd=ch.querySelector('.net-chart-delta'), hb=parseFloat(el.getAttribute('data-rt-nw-hdr'));
if(hd&&!isNaN(hb)){
var hv=Math.round(hb+dv), h0=parseFloat(el.getAttribute('data-rt-nw-hdr0'))||0;
var ht=(hv>=0?'+':'')+Math.abs(hv).toLocaleString()+'';
if(h0) ht+=' ('+(hv>=0?'+':'')+Math.abs(hv/h0*100).toFixed(2)+'%)';
hd.textContent=ht;
hd.classList.remove('up','down'); hd.classList.add(hv>=0?'up':'down');
}
// netchart_script init 캐시한 pts 배열의 라이브 점을 제자리 갱신(툴팁이 배열을 읽는다).
if(ch.__nw_pts&&ch.__nw_liveIdx>=0&&ch.__nw_liveBase){
var lp=ch.__nw_pts[ch.__nw_liveIdx], bs=ch.__nw_liveBase;
lp.v=bs.v+dv; lp.day=bs.day+dv; lp.total=bs.total+dv;
}
}
// 위치도 같은 값으로 재계산 서버 렌더와 동일 스케일 y = top + (1 (vylo)/span)·ih.
var ylo=parseFloat(el.getAttribute('data-rt-nw-ylo'));
var span=parseFloat(el.getAttribute('data-rt-nw-span'));