Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/safety-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v7.0.0
with:
python-version: "3.12"
cache: pip
Expand All @@ -38,6 +38,15 @@ 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@v7.0.0
with:
node-version: "22"
package-manager-cache: false

- 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 }}
Expand Down
63 changes: 63 additions & 0 deletions docs/DASHBOARD_REVIEW_20260922.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 계좌 화면 오류·성능 점검

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에 관한 내용입니다.

GitHub 자동 검사에서 지원이 끝난 Node 20 기반 액션 경고도 확인했습니다. 공식 릴리스를 확인해 checkout은 7.0.1, setup-python과 setup-node는 7.0.0으로 업데이트했습니다. 프로그램의 Python 버전이나 설치 방법은 바꾸지 않았습니다.

## 참고 자료

- [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): 실험실 측정과 실제 사용자 지표의 구분
- [checkout 7.0.1](https://github.com/actions/checkout/releases/tag/v7.0.1), [setup-python 7.0.0](https://github.com/actions/setup-python/releases/tag/v7.0.0), [setup-node 7.0.0](https://github.com/actions/setup-node/releases/tag/v7.0.0): 자동 검사 도구의 공식 릴리스
6 changes: 6 additions & 0 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@

**입체**와 **평면**은 같은 수익률을 다른 모양으로 보여 줍니다. 입체 차트의 띠 너비가 별도의 투자 지표를 뜻하지는 않습니다.

날짜와 시각은 모두 한국 시간 기준입니다. 해외에서 접속해도 거래 날짜와 월별 수익률은 같습니다.

**낙폭과 일별 기록 자세히 보기**를 열면 날짜별 금액이 표로 나옵니다. 기록이 많으면 100개씩 나눠 보여 줍니다. **이전 기록**, **다음 기록**으로 넘길 수 있고, CSV에는 조회 기간의 모든 기록이 담깁니다.

![날짜별 평가금액과 수익률](images/history-table-20260922.png)

[계좌 화면 크게 보기](images/readme-account-20260922.png)

## 내역 저장
Expand Down
Binary file added docs/images/history-table-20260922.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions monitoring/static/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading