From 345e1c58b407cb5502bd04959b8e1a0cdfb732e8 Mon Sep 17 00:00:00 2001 From: easygap <103491329+easygap@users.noreply.github.com> Date: Tue, 22 Sep 2026 17:20:40 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EA=B3=84=EC=A2=8C=20=EB=82=A0=EC=A7=9C?= =?UTF-8?q?=C2=B7=EA=B0=B1=EC=8B=A0=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EA=B8=B4=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=86=8D=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/safety-regression.yml | 8 + docs/DASHBOARD_REVIEW_20260922.md | 60 ++++ docs/USER_GUIDE.md | 6 + docs/images/history-table-20260922.png | Bin 0 -> 79295 bytes monitoring/static/dashboard.css | 33 ++ monitoring/static/dashboard.js | 166 ++++++--- monitoring/templates/dashboard.html | 16 +- monitoring/web_dashboard.py | 76 ++-- .../research/dashboard_review_20260922.json | 197 ++++++++++ tests/js/dashboard.test.cjs | 340 ++++++++++++++++++ tests/test_dashboard_basket_evaluation.py | 7 - tests/test_dashboard_baskets.py | 30 +- tests/test_dashboard_concurrency.py | 120 +++++++ 13 files changed, 954 insertions(+), 105 deletions(-) create mode 100644 docs/DASHBOARD_REVIEW_20260922.md create mode 100644 docs/images/history-table-20260922.png create mode 100644 reports/research/dashboard_review_20260922.json create mode 100644 tests/js/dashboard.test.cjs create mode 100644 tests/test_dashboard_concurrency.py diff --git a/.github/workflows/safety-regression.yml b/.github/workflows/safety-regression.yml index 9f7c7fd7..a1acf712 100644 --- a/.github/workflows/safety-regression.yml +++ b/.github/workflows/safety-regression.yml @@ -38,6 +38,14 @@ jobs: run: | python -m py_compile main.py api/websocket_handler.py core/data_collector.py core/order_executor.py core/paper_preflight.py core/scheduler.py core/live_gate.py core/target_weight_rotation.py monitoring/liquidate_trigger.py tools/target_weight_rotation_pilot.py tools/paper_pilot_control.py + - name: Set up Node for dashboard tests + uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Check dashboard date calculations and refresh behavior + run: node --test tests/js/dashboard.test.cjs + - name: Run safety regression tests env: TMPDIR: ${{ runner.temp }} diff --git a/docs/DASHBOARD_REVIEW_20260922.md b/docs/DASHBOARD_REVIEW_20260922.md new file mode 100644 index 00000000..2736a09d --- /dev/null +++ b/docs/DASHBOARD_REVIEW_20260922.md @@ -0,0 +1,60 @@ +# 계좌 화면 오류·성능 점검 + +2026년 9월 22일, 날짜와 월별 수익률 표시, 새로고침, 많은 기록을 조회할 때의 속도를 점검했습니다. 기존 HTML·CSS·JavaScript와 Python 서버를 유지했습니다. + +## 날짜와 월별 수익률 + +브라우저 시간대를 미국으로 바꾸자 9월 22일 기록이 21일로 표시됐습니다. 월초 기록이 전월에 포함되면서 같은 계좌의 8월 수익률도 서울에서는 `+0.4%`, 미국에서는 `+1.4%`로 달라졌습니다. + +화면의 날짜, 조회 시작일, 월별 집계, 이번 달 적립 여부, 오늘 생성된 신호를 한국 시간으로 맞췄습니다. 장부에 시간대 없이 저장된 시각은 한국 시간으로 읽고, `Z`나 UTC 오프셋이 있는 시각은 해당 시각을 기준으로 변환합니다. 과거 기록을 나중에 복원했을 때 이후 입금이 과거 원금에 포함되지 않는지도 확인했습니다. + +서울·로스앤젤레스·런던 시간대에서 같은 계좌를 열어 날짜와 월별 수익률이 일치하는 것을 확인했습니다. + +## 새로고침과 조회 실패 + +- 새로고침 버튼과 자동 갱신이 겹치면 진행 중인 요청을 취소하던 동작을 고쳤습니다. 같은 조회가 끝날 때까지 결과를 함께 기다립니다. 실제 화면에서 버튼을 세 번 연달아 눌러도 계좌 요청은 한 번만 전송됐습니다. +- 적립금을 기록한 뒤에는 이전 조회가 끝난 다음 계좌를 다시 읽습니다. 적립 직전 금액으로 화면이 되돌아가지 않도록 했습니다. +- 차트 조회에 실패하면 이전 계좌의 표와 낙폭도 함께 숨깁니다. 내려받기와 날짜 선택 버튼도 비활성화합니다. + +## 기록이 많을 때 + +일별 표는 펼쳤을 때 만들고, 100개씩 넘겨 볼 수 있게 바꿨습니다. 표를 스크롤해도 열 제목은 위에 남습니다. 날짜 슬라이더와 CSV에는 조회 기간의 전체 기록을 유지합니다. + +아래는 합성 일별 기록 2,500개로 비교한 결과입니다. 창 크기 1280×900, Chromium CPU 4배 감속 조건에서 수정 전후를 번갈아 세 번 실행한 중앙값입니다. 수정 전 기준은 `3be97d0`입니다. + +| 항목 | 수정 전 | 수정 후 | +| --- | ---: | ---: | +| 표를 접은 첫 화면의 DOM 요소 | 15,665개 | 669개 | +| 접힌 표를 만드는 데 쓴 시간 | 112.2ms | 0.4ms | +| 화면에 준비하는 표 행 수 | 2,500개 | 펼친 페이지의 최대 100개 | +| 표 한 번 생성하는 시간 | 112.2ms | 7.3ms | +| 차트 데이터 반영 함수 실행 시간 | 235.4ms | 170.1ms | + +수정 전에는 표를 미리 만들었고, 수정 후에는 펼친 시점에 만듭니다. 위 시간은 해당 JavaScript 함수의 실행 시간이며, 전체 페이지 로딩 시간이나 실사용자의 INP를 뜻하지 않습니다. 합성 데이터와 감속 환경에서 측정한 값이라 기기·기록 수에 따라 달라집니다. 측정 중 긴 작업이 남아 있어 모든 상황에서 지연이 없다는 의미도 아닙니다. + +[측정값과 모바일 확인 결과](../reports/research/dashboard_review_20260922.json) + +## 서버 처리 + +계좌·성과·적립 기록을 읽는 동안 다른 요청이 기다리지 않도록 DB 조회를 별도 스레드에서 처리합니다. 운영 상태와 검증 자료는 서버 앱별로 캐시하고, 동시에 요청한 경우 한 번만 수집합니다. 동시 요청 6개를 보냈을 때 수집 횟수가 6회에서 1회로 줄었습니다. + +캐시는 수집을 마친 뒤 60초 동안 유지합니다. 모의투자·실전 모드가 바뀌면 다시 수집하고, 실패한 조회는 다음 요청에서 재시도합니다. 거래 중지 여부는 캐시와 별개로 매번 확인합니다. + +## 확인한 사용 흐름 + +- 320px·390px·1280px 화면에서 가로 넘침과 JavaScript 오류 없음 +- 모션 줄이기 설정, 입체·평면 전환, 날짜 슬라이더의 Home·End 키 조작 +- 2,500개 기록에서 이전·다음 페이지 이동, 화면에는 100행, CSV에는 2,500행 유지 +- 조회 실패 시 이전 표 숨김, 버튼 연속 클릭 시 중복 요청 방지 +- 표의 페이지 버튼 높이 40px, 스크롤 중 열 제목 고정 + +날짜·화면 로직은 `node --test tests/js/dashboard.test.cjs`, 서버와 매매 관련 회귀 테스트는 `python -m pytest tests -q`로 확인합니다. Node.js는 화면 테스트에만 사용하며 프로그램 실행에는 필요하지 않습니다. + +로컬 전체 테스트는 Python 2,091개와 JavaScript 19개가 통과했습니다. Python 테스트에서 나온 경고 2개는 기존 라이브러리의 지원 종료 예정 API에 관한 내용입니다. + +## 참고 자료 + +- [MDN Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date): 기기 시간대에 따라 달라지는 날짜 필드와 UTC 필드의 차이 +- [Python asyncio.to_thread](https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread): 동기 I/O가 이벤트 루프를 막지 않도록 실행하는 방법 +- [aiohttp의 앱별 상태 관리](https://docs.aiohttp.org/en/stable/web_advanced.html#data-sharing-aka-no-singletons-please): 전역 상태 대신 앱 안에서 조회 결과 관리 +- [Web Vitals](https://web.dev/articles/vitals): 실험실 측정과 실제 사용자 지표의 구분 diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 2eae33b7..4e44e364 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -20,6 +20,12 @@ **입체**와 **평면**은 같은 수익률을 다른 모양으로 보여 줍니다. 입체 차트의 띠 너비가 별도의 투자 지표를 뜻하지는 않습니다. +날짜와 시각은 모두 한국 시간 기준입니다. 해외에서 접속해도 거래 날짜와 월별 수익률은 같습니다. + +**낙폭과 일별 기록 자세히 보기**를 열면 날짜별 금액이 표로 나옵니다. 기록이 많으면 100개씩 나눠 보여 줍니다. **이전 기록**, **다음 기록**으로 넘길 수 있고, CSV에는 조회 기간의 모든 기록이 담깁니다. + +![날짜별 평가금액과 수익률](images/history-table-20260922.png) + [계좌 화면 크게 보기](images/readme-account-20260922.png) ## 내역 저장 diff --git a/docs/images/history-table-20260922.png b/docs/images/history-table-20260922.png new file mode 100644 index 0000000000000000000000000000000000000000..1997de3a407034184d1aa734a5003ad76b51afea GIT binary patch literal 79295 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

>pB+XxaGUApY;?BJ!b(x2k_Y(<50pXIA-stHM z^3XvDrvP?AjolAPz@2cO;$7Tlo%$g^y4ELit$~EO@(a}qI{M?*vc00_7!_H1o-jaQ|u&b(}leWGZT_xJEmRxX^2Q?OHK-jgm9m0**5cwQD*(}ocU5$cQi$uya9$5I=j*Sh%bwiICJvt4)!_w#*|EW?_Z0P&S79ZtBx;X}VOV4& z7CO44yX0`&xSGf-erd07=db7|>>x5VTQX4BIQUprJNaF5ouh%R5PnC`r_O*X$FgpF zkz&=Jo%b>;r13F>PjVZ9VbRn%gb~!F{%vXCIbu!5!dHPa* zpfjR6B?!B;;c_EI^u4oJDs}h*X6{D1H^_nB~htb7qc$| z8Tm5!fpk9=GxHmZflMgenbclI&;?Y<%D13W1srY7>SkS|W{7#pWMEn&y$?LMe|I_Tf zzrG%t70IuEOBEnMg+=rGm)fTPpMKE`kUA88|7KLhlzDd(Yj*v;fTCmj<9k(kgVWjI zxTz*t@&BzK>HpF1`BkC?Ox~HouFR%q-~ZGC*m-zHek$lLu3iM91;Sh4v^{)eEmA=z z9YrN1B*f4E1z)miCq1Z1zPY&>SWu9fH@uJ|)L!35#OOos*uT6TC*e}Pa3-A$v<*~V zUm{-T<-yGnD3PoHt*tBM{sPqGoVopjl1ozr7H_~zo#N?s6O>RNY{ncxpQ2C_z#XkB zcXD%b7qbUfFC;yFf@Li*DS*m@fBxY;#sFL)WRYcvSUuR9R${fM=pZ7vKDwAgK2cxD zvsvoj+1%8BKQozrViZM~!5VWehthokZ{i8uq3$M_b9p6S>K}ze~waI*YRgT zRl<)5#pqgoiW>U`-LeyXS;6qjy*&v(df&>;(+r$ZJVK=zLbHm{8EehD&?wdw0`DGZ ze!%E04P;UFRIk2DFDZmvx>&u+DGwHNA>e2ru+uOSTHg##BT2YVUDrod0O<)OV7SJp z?CakVNrs57K}JC_sIs5+L!od35&?GZytt&ZxSN~LE)N&P*r11mLi_Ks03Sz`1AvuB+xV>bOQV84y13}y}h(hsB zsT-hmQdKEtiliC__`1VIJ>`9}L1tGsgg>X`0l01{%Gdx9!Xatw1wu21kZ7?rbY<|LO*HbJb@^T=s;;v(%2VTZmn zhU9)tp7UV<8qH%Rus-Cro2uK)@;mhPB^7bAA1gEK3+Xr{prD|@$Cq?(85oF;45su> zNeiQcr;22lBNs^(Vp>1yKx88ekiZZHIK1;>g$MZD=1YOuAUl_WM4yk^GppBi%09p0 z-gWaeuCO4qH3keWglyVBz)4Np81_zrgcI3CL6gc{B0!3~hȅ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 literal 0 HcmV?d00001 diff --git a/monitoring/static/dashboard.css b/monitoring/static/dashboard.css index 09c1422b..43ab9e46 100644 --- a/monitoring/static/dashboard.css +++ b/monitoring/static/dashboard.css @@ -762,6 +762,39 @@ h1 { .history-details > summary { color: var(--ink-soft); } +.history-pagination { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin: 16px 0 8px; + font-size: 12px; + font-variant-numeric: tabular-nums; +} +.history-pagination button { + min-height: 40px; + padding: 0 12px; + border: 1px solid var(--rule-strong); + color: var(--ink); + background: transparent; + cursor: pointer; +} +.history-pagination button:disabled { + opacity: 0.4; + cursor: default; +} +.history-pagination button:not(:disabled):hover { + background: var(--surface); +} +#historyTableScroll { + max-height: min(560px, 65vh); + scrollbar-gutter: stable; +} +.history-details th { + position: sticky; + top: 0; + background: var(--surface); +} .chart-title { display: flex; justify-content: space-between; diff --git a/monitoring/static/dashboard.js b/monitoring/static/dashboard.js index f1121391..1fed31ae 100644 --- a/monitoring/static/dashboard.js +++ b/monitoring/static/dashboard.js @@ -46,34 +46,48 @@ } function parseDate(value) { if (!value) return null; - const text = String(value); + if (value instanceof Date) + return Number.isNaN(value.getTime()) ? null : new Date(value.getTime()); + const text = String(value).trim().replace(" ", "T"); + // 장부의 시간대 없는 시각은 한국 시간이다. Z·UTC 오프셋이 있으면 그대로 읽는다. const normalized = /^\d{4}-\d{2}-\d{2}$/.test(text) ? `${text}T00:00:00+09:00` - : /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(text) - ? `${text.replace(" ", "T")}+09:00` + : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/.test(text) + ? `${text}+09:00` : text; const parsed = new Date(normalized); return Number.isNaN(parsed.getTime()) ? null : parsed; } const pad2 = (n) => String(n).padStart(2, "0"); + const DAY_MS = 86_400_000; + const KST_OFFSET_MS = 9 * 60 * 60 * 1000; + // 표시용 날짜만 +9시간 옮겨 UTC 필드로 읽는다. 시간 비교에는 원래 시각을 쓴다. + function koreaClock(value) { + const instant = parseDate(value); + return instant ? new Date(instant.getTime() + KST_OFFSET_MS) : null; + } + function koreaDate(value) { + return koreaClock(value)?.toISOString().slice(0, 10) || ""; + } function fmtLong(value) { - const d = parseDate(value); + const d = koreaClock(value); return d - ? `${d.getFullYear()}년 ${d.getMonth() + 1}월 ${d.getDate()}일` + ? `${d.getUTCFullYear()}년 ${d.getUTCMonth() + 1}월 ${d.getUTCDate()}일` : "—"; } function fmtMD(value) { - const d = parseDate(value); - return d ? `${d.getMonth() + 1}월 ${d.getDate()}일` : "—"; + const d = koreaClock(value); + return d ? `${d.getUTCMonth() + 1}월 ${d.getUTCDate()}일` : "—"; } function fmtDT(value) { - const d = parseDate(value); + const d = koreaClock(value); return d - ? `${d.getMonth() + 1}월 ${d.getDate()}일 ${pad2(d.getHours())}:${pad2(d.getMinutes())}` + ? `${d.getUTCMonth() + 1}월 ${d.getUTCDate()}일 ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}` : "—"; } - function fmtHM(d) { - return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`; + function fmtHM(value) { + const d = koreaClock(value); + return d ? `${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}` : "—"; } function formatAge(minutes) { if (minutes == null) return "기록 없음"; @@ -82,20 +96,13 @@ if (minutes < 1440) return `${Math.round(minutes / 60)}시간 전`; return `${Math.round(minutes / 1440)}일 전`; } - function localIsoDate(value = new Date()) { - return `${value.getFullYear()}-${pad2(value.getMonth() + 1)}-${pad2(value.getDate())}`; - } function calendarAgeDays(value) { - const parsed = parseDate(value); - if (!parsed) return null; - const now = new Date(); - const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - const target = new Date( - parsed.getFullYear(), - parsed.getMonth(), - parsed.getDate(), + const date = koreaDate(value); + if (!date) return null; + return Math.max( + 0, + Math.round((parseDate(koreaDate(new Date())) - parseDate(date)) / DAY_MS), ); - return Math.max(0, Math.floor((today - target) / 86_400_000)); } function escapeHtml(value) { return String(value == null ? "" : value) @@ -317,9 +324,8 @@ if (!d) return null; const created = parseDate(row.created_at); // 과거 날짜를 나중에 복원한 행의 저장 시각으로 미래 입금을 당겨 넣지 않는다. - if (created && localIsoDate(created) === localIsoDate(d)) return created; - d.setHours(23, 59, 59, 999); - return d; + if (created && koreaDate(created) === koreaDate(d)) return created; + return new Date(`${koreaDate(d)}T23:59:59.999+09:00`); } /** 스냅샷·입금 기록으로 평가금액, 투자원금(계단), 시간가중 자산, 낙폭 시계열을 만든다. */ @@ -412,13 +418,14 @@ const out = []; let prevIndex = null; let current = null; + const currentYear = koreaDate(new Date()).slice(0, 4); series.dates.forEach((d, i) => { - const key = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}`; + const key = koreaDate(d).slice(0, 7); if (!current || current.key !== key) { if (current) out.push(current); current = { key, - label: `${d.getFullYear() !== new Date().getFullYear() ? `${String(d.getFullYear()).slice(2)}년 ` : ""}${d.getMonth() + 1}월`, + label: `${key.slice(0, 4) !== currentYear ? `${key.slice(2, 4)}년 ` : ""}${Number(key.slice(5, 7))}월`, startIndex: prevIndex, endIndex: i, }; @@ -604,14 +611,9 @@ function currentMonthContributionState(basket) { if (!basket || !basket.contribution_plan?.enabled) return "not-planned"; if (state.flowStatus.get(basket.basket) !== "ready") return "unknown"; - const now = new Date(); + const month = koreaDate(new Date()).slice(0, 7); const recorded = (state.flows.get(basket.basket) || []).some((flow) => { - const when = parseDate(flow.occurred_at); - return ( - when && - when.getFullYear() === now.getFullYear() && - when.getMonth() === now.getMonth() - ); + return koreaDate(flow.occurred_at).slice(0, 7) === month; }); return recorded ? "recorded" : "empty"; } @@ -1003,6 +1005,9 @@ perspective: 1, selected: null, paintFrame: null, + tableSeries: null, + tablePage: null, + tableRenderedPage: null, }; function ensureChartAccountOptions() { @@ -1076,21 +1081,53 @@ } function renderChartTable(series) { + if (!$("historyDetails").open || !series.rows.length) return; + const lastPage = Math.ceil(series.rows.length / 100) - 1; + const page = clamp(chart.tablePage ?? lastPage, 0, lastPage); + if (chart.tableSeries === series && chart.tableRenderedPage === page) + return; + const start = page * 100; + const end = Math.min(start + 100, series.rows.length); el.chartRows.innerHTML = series.rows + .slice(start, end) .map( - (row, i) => ` + (row, offset) => ` ${won(row.total_value)} - ${won(series.principal[i])} + ${won(series.principal[start + offset])} ${pct(row.cumulative_return)} `, ) .join(""); + chart.tableSeries = series; + chart.tableRenderedPage = page; + $("historyTablePosition").textContent = + `${start + 1}–${end} / ${series.rows.length}개 기록`; + $("historyTablePrev").disabled = page === 0; + $("historyTableNext").disabled = page === lastPage; + } + + function moveHistoryTable(direction) { + if (!chart.series || $("historyDetails").hidden) return; + const lastPage = Math.ceil(chart.series.rows.length / 100) - 1; + const currentPage = clamp(chart.tablePage ?? lastPage, 0, lastPage); + chart.tablePage = clamp(currentPage + direction, 0, lastPage); + // 마지막 페이지는 새 기록이 추가돼도 최근 날짜를 계속 보여 준다. + if (chart.tablePage === lastPage) chart.tablePage = null; + renderChartTable(chart.series); + $("historyTableScroll").scrollTop = 0; + } + + function clearChartDetails() { + $("historyDetails").hidden = true; + el.chartRows.innerHTML = ""; + chart.tableSeries = null; + chart.tableRenderedPage = null; } function renderMonthStrip(series, history) { const visibleMonths = new Set( - series.dates.map((d) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}`), + series.dates.map((d) => koreaDate(d).slice(0, 7)), ); // 조회 기간이 월 중간에서 시작해도 전월 말 기준 수익률을 사용한다. const months = monthlyReturns(history).filter((month) => @@ -1489,6 +1526,7 @@ chart.selected === chart.series?.rows.length - 1; const previousDate = chart.series?.rows[chart.selected]?.date; const sameAccount = state.chartDataAccount === state.chartAccount; + if (!sameAccount) chart.tablePage = null; chart.series = series; state.chartDataAccount = state.chartAccount; state.chartRows = series.rows; @@ -1502,6 +1540,8 @@ $("historyLatest").disabled = !series.rows.length; $("historyStart").textContent = fmtMD(series.rows[0]?.date); $("historyEnd").textContent = fmtMD(series.rows.at(-1)?.date); + // 접힌 표의 수천 개 행을 매 갱신마다 만들지 않는다. + clearChartDetails(); if (!series.rows.length) { el.chartWrap.hidden = true; @@ -1521,6 +1561,7 @@ el.chartSummary.textContent = `${fmtMD(first.date)} ~ ${fmtMD(last.date)} · 평가금액 ${signedWon(change)}${deposits ? ` (적립 ${won(deposits)} 포함)` : ""} · 누적 수익률 ${pct(last.cumulative_return)}`; el.chartWrap.hidden = false; el.chartEmpty.hidden = true; + $("historyDetails").hidden = false; renderChartTable(series); renderMonthStrip(series, history); const previousIndex = @@ -1543,8 +1584,10 @@ $("performance").setAttribute("aria-busy", "true"); $("exportHistory").disabled = true; $("historyCursor").disabled = true; + $("historyLatest").disabled = true; // 계좌가 바뀌면 이전 숫자를 남기지 않는다. 오래 걸린 이전 응답도 폐기한다. if (state.chartDataAccount !== account) { + clearChartDetails(); el.chartWrap.hidden = true; el.chartEmpty.hidden = false; el.chartEmpty.innerHTML = @@ -1562,9 +1605,9 @@ ); const cached = state.series.get(account); // 전체 기록은 갱신 주기에 한 번 읽는다. 기간·계좌 변경 때 같은 데이터를 다시 받지 않는다. - const cutoff = new Date(); - cutoff.setDate(cutoff.getDate() - days); - cutoff.setHours(0, 0, 0, 0); + const cutoff = new Date( + parseDate(koreaDate(new Date())).getTime() - days * DAY_MS, + ); const data = cached ? { snapshots: cached.filter((row) => parseDate(row.date) >= cutoff) } : await fetchJson( @@ -1580,6 +1623,7 @@ updateChart(data?.snapshots || []); } catch { if (request !== state.chartRequest) return; + clearChartDetails(); el.chartSummary.textContent = "성과 기록을 읽지 못했습니다. 운영 영역의 다시 확인을 눌러주세요."; el.chartWrap.hidden = true; @@ -1821,7 +1865,7 @@ count.textContent = "확인 불가"; return; } - const isToday = !signalsDate || signalsDate === localIsoDate(); + const isToday = !signalsDate || signalsDate === koreaDate(new Date()); const rows = isToday && Array.isArray(signals) ? signals : []; count.textContent = `${rows.length}건`; table.hidden = !rows.length; @@ -2055,7 +2099,9 @@ showToast( `${state.mode === "live" ? "실전 입금" : "모의투자 적립"} ${won(data.amount)}을 기록했습니다.`, ); - await refreshCore(); + // 적립 전에 시작한 조회가 있으면 끝낸 뒤 새 금액을 다시 읽는다. + await coreRefresh; + await refreshCore(true); } catch (error) { showDepositError( `기록하지 못했습니다. ${error.message || "연결을 확인한 뒤 다시 시도하세요."}`, @@ -2079,8 +2125,19 @@ // 주기 갱신은 숨은 탭에서 쉬지만, 첫 로드는 탭이 뒤에 있어도 반드시 한 번 채운다 // (백그라운드로 연 탭이 빈 화면으로 남던 문제). - async function refreshCore(force = false) { + let coreRefresh = null; + let slowRefresh = null; + function refreshCore(force = false) { if (!force && document.visibilityState === "hidden") return; + // 타이머·탭 복귀·버튼 클릭이 겹쳐도 진행 중인 조회를 취소하지 않는다. + if (!coreRefresh) + coreRefresh = loadCore().finally(() => { + coreRefresh = null; + }); + return coreRefresh; + } + + async function loadCore() { state.coreStatus = "loading"; updateSyncIndicator(); const basketTask = fetchJson("/api/baskets", { @@ -2122,8 +2179,16 @@ renderDecision(); } - async function refreshSlow(force = false) { + function refreshSlow(force = false) { if (!force && document.visibilityState === "hidden") return; + if (!slowRefresh) + slowRefresh = loadSlow().finally(() => { + slowRefresh = null; + }); + return slowRefresh; + } + + async function loadSlow() { if (!state.runtime) { state.runtimeStatus = "loading"; updateSyncIndicator(); @@ -2254,8 +2319,17 @@ ); $("exportHistory").addEventListener("click", exportHistory); $("historyDetails").addEventListener("toggle", () => { - if ($("historyDetails").open && chart.series) drawDrawdown(chart.series); + if ($("historyDetails").hidden || !chart.series) return; + if ($("historyDetails").open) { + renderChartTable(chart.series); + drawDrawdown(chart.series); + } else { + el.chartRows.innerHTML = ""; + chart.tableSeries = null; + } }); + $("historyTablePrev").addEventListener("click", () => moveHistoryTable(-1)); + $("historyTableNext").addEventListener("click", () => moveHistoryTable(1)); $("chartView").addEventListener("click", (event) => { const button = event.target.closest("button[data-view]"); if (button) setChartView(button.dataset.view); diff --git a/monitoring/templates/dashboard.html b/monitoring/templates/dashboard.html index cb13e4fe..7c7f1baf 100644 --- a/monitoring/templates/dashboard.html +++ b/monitoring/templates/dashboard.html @@ -265,7 +265,7 @@

수익의 흐름

-
+