diff --git a/.cursor/rules/agent-shell-python-safety.mdc b/.cursor/rules/agent-shell-python-safety.mdc index 32cdae7..0db1353 100644 --- a/.cursor/rules/agent-shell-python-safety.mdc +++ b/.cursor/rules/agent-shell-python-safety.mdc @@ -43,3 +43,5 @@ print({m: len(a) for m, a in fn().items()} if callable(fn) else "no _scalp_grids - “한 번에 여러 전략 import + 조건부 트릭”보다 **짧은 확정 코드 1회**. - 실패 로그를 사용자에게 길게 반복 붙여 넣지 말 것. 고치고 결과만 보고. +- ImportError/AttributeError 시 비슷한 심볼명 추측 재실행 금지 → Grep 1회 (`invalidate_merged_env_cache` 등). +- 상세 절대규칙: `.cursorrules` CRITICAL §4. diff --git a/.cursor/rules/db-adhoc-query-safety.mdc b/.cursor/rules/db-adhoc-query-safety.mdc index ed9e86b..cca0192 100644 --- a/.cursor/rules/db-adhoc-query-safety.mdc +++ b/.cursor/rules/db-adhoc-query-safety.mdc @@ -48,3 +48,9 @@ rows = db.conn.execute( ("20260712%",), ).fetchall() ``` + +## 5. env 신규 키 — “patch했다” ≠ DB에 남음 (`.cursorrules` CRITICAL §4-3) + +- `MOMENTUM_*` 등은 `config_momentum` 키 목록·컬럼에 없으면 `apply_env_patch` 후에도 `get_env_from_db`가 빈값일 수 있음. +- 신규 키: ENV_CONFIG_KEYS(또는 전략 키 튜플) + code default + apply 맵 + 저장 후 **재조회 검증**. +- 캐시 무효: `invalidate_merged_env_cache()` (이름 추측 금지). diff --git a/.cursor/rules/domestic-port-ui-parity.mdc b/.cursor/rules/domestic-port-ui-parity.mdc new file mode 100644 index 0000000..a4727fa --- /dev/null +++ b/.cursor/rules/domestic-port-ui-parity.mdc @@ -0,0 +1,81 @@ +--- +description: 해외=국장 UI 최대한 동일. 꼭 다를 때만 선보고·승인. 이식 시 summary·저장·브라우저 검증. +alwaysApply: true +--- + +# 국내 이식·해외 UI 정합 (절대) + +## A. 해외 전략 UI = 국장과 최대한 동일 (선보고) + +- **기본**: 해외(`US_*`) 웹·Optuna·결과카드·버튼·표(Top5「보기」/폼/적용)·가상거래·차트는 **대응 국장 탭과 같은 UX**. +- “해외라서 단축·생략” **임의 판단 금지.** +- **꼭 다르게 해야 할 때만** (USD 표시, HTS 없음, 해외 WS 세션, 종목핀 테이블 등): + 1. **무엇을·왜** 다르게 하는지 **구현 전 짧게 보고** + 2. **사용자 승인 후** 구현 + 3. UI에 차이(「전역/종목」「USD」) **문구로 명시** +- 승인 없이 Optuna Top5·보기·후보선택·PF/수익률·설정저장 흐름을 빼거나 다르게 만들면 **위반**. +- 엔진도 국장과 동일이 기본(T−1/T·freeze). 체결가·호가 등 실매 차이도 **선보고**. + +--- + +국내 탭/API를 “그대로 가져왔다”는 **복사만으로는 완료가 아니다.** +해외·신규 탭에서 아래를 **브라우저로 확인**하기 전에는 “정합 OK / 버그 없음”을 **단정하지 마라.** + +## 0. 정신 (이 대화에서 실제로 난 사고) + +- 원인 미확인 상태로 **엔진·cfg 불일치**로 넘기지 마라. + UI가 Optuna 폼을 **탭 재클릭/종목 리로드로 DB 핀으로 덮어쓴** 경우처럼, **표시·적용 경로**를 먼저 검증하라. +- “국내랑 같은 코드” ≠ “국내랑 같은 화면 숫자.” + **summary 키·저장 테이블·버튼 의미**가 하나라도 빠지면 사용자에게는 버그다. +- Optuna「보기」없이 1위 단축만 두는 것도 **국장 UI와 다름** → 선보고 없이 하면 위반. + +## 1. 웹 백테 summary ↔ 렌더 키 (필수 체크리스트) + +국내 `summarize_trades` / 웹 렌더는 키가 다를 수 있다. 이식 시 **응답 JSON과 DOM id를 대조**하라. + +| 화면 | 흔한 버그 | 올바른 소스 | +|------|-----------|-------------| +| Profit Factor | `stats.profit_factor` 없음 → **0** | `stats["pf"]` → summary `profit_factor` | +| 총 수익률(운용한도) | summary에 미포함 → **0%** | `stats["bot_pct"]` | +| 일평균 수익률 | summary에 미포함 → **0%** | `stats["daily_avg_pct"]` | +| 누적/일별/사유 차트 | `equity`/`daily`/`reasons` 미포함 → 빈 차트 | 국내 모멘텀/스캘프 웹과 **동일 필드** | +| 피크 누적 | context 미표시 | `peak_cum_pnl` / `peak_cum_at` | + +**완료 전:** 백테 1회 후 PF·bot%·일평균%·PnL·거래수가 **0이 아닌 기대값**(또는 의도적 0)인지 확인. +`curl 200` / “거래만 나옴”만으로는 **미완료**. + +## 2. 통화·절삭 (해외 USD) + +- USD PnL·MDD·누적은 **`int()` 절삭 금지.** 소수(센트) 유지. +- `summarize_trades`가 `total_pnl`을 int로 돌려도, **해외 응답의 표시용 total_pnl은 거래 합산 float**을 쓸 것. +- 운용한도 분모(`total_budget_*`) 단위(USD vs KRW)를 라벨·bot_pct 계산과 **혼동하지 말 것.** + +## 3. 저장 대상 분리 (버튼·테이블·문구) + +| UI | 저장처 | 금지 | +|----|--------|------| +| **전역 설정 저장** | `config_us_momentum` (`US_MOMENTUM_*`) + env 스냅샷 | 종목행 테이블에 쓰는 척 | +| **종목행 적용 / 선택종목 저장** | `us_momentum_stock_config` | 전역 `US_MOMENTUM_*`만 바꾸고 종목핀 됐다고 말하기 | +| Optuna 탭(전역) | 전역만 | 종목 cfg에 몰래 쓰기 | +| 해외탭 종목 Optuna | 종목 cfg만 | 전역에 적용했다고 말하기 | + +- 버튼 문구에 **「전역」/「종목」**을 명시하라. (`봇에 설정저장`처럼 모호한 이름 금지 — 이식·신규 시) +- help 문구·tooltip에 **테이블명**을 적어라. + +## 4. 폼 · Optuna · 종목핀 · 백테 소스 오브 트루스 + +- **웹 백테 = 폼 값.** DB 종목핀을 몰래 덮어쓰지 마라 (`use_stock_cfg` 명시 시에만). +- **폼에 넣기 / 종목행 적용**은 사용자가 고른 후보(보기→선택) 또는 명시된 단축(학습1위)과 **같은 소스**. +- Optuna UI는 국장처럼 **Top5 + 보기 + 골라 적용**이 기본. 단축만 두고 보기 생략 = §A 위반. +- **폼에 넣기 직후 탭 click / 종목 select**가 폼을 DB로 덮으면 **버그**. 이미 활성 탭이면 재클릭 금지. +- upsert 시 Optuna가 안 준 컬럼을 **NULL로 지우지 마라**. + +## 5. “고쳤다/같다” 보고 전 최소 검증 + +1. 해당 탭 **하드 새로고침** (`192.168.0.149:5050` — localhost 금지) +2. **국장 대응 탭과 UI 목록 대조** (빠진 버튼·표·카드가 있으면 미완료 또는 선보고 차이 목록) +3. Optuna → 보기/선택 → 폼/종목행 → 백테 숫자가 **한 세트**로 일치 +4. summary: `profit_factor`, `bot_pct`, `daily_avg_pct` + equity/daily/reasons +5. 콘솔 `Uncaught`/`ReferenceError` 없음 + +하나라도 미확인이면 **안전하다/국내와 동일하다**고 말하지 마라. 잔여 위험을 명시하라. diff --git a/.cursor/rules/no-ohlc-fallback-parity.mdc b/.cursor/rules/no-ohlc-fallback-parity.mdc new file mode 100644 index 0000000..24b0167 --- /dev/null +++ b/.cursor/rules/no-ohlc-fallback-parity.mdc @@ -0,0 +1,45 @@ +--- +description: 백테·Optuna·파람서치에서 OHLC 폴백으로 체결·고점·PnL 숫자 변조 절대 금지 +alwaysApply: true +--- + +# OHLC 폴백 숫자 변조 절대 금지 (실매↔백테 정합) + +## 한 줄 + +틱 청산/틱 진입 ON 이면 **봉 OHLC(high/low)로 체결가·max_price·손익을 채우거나 보정하지 않는다.** +체크박스·env `*_TICK_FALLBACK_OHLC=true` 여도 **엔진은 무시**한다. Optuna/파람은 **강제 False**. + +## 왜 + +실매는 WS 틱만으로 고점·청산을 본다. +백테가 틱 전에 3분봉 `high`를 `max_price`에 넣거나, 틱 없을 때 OHLC intrabar로 래칫/손절을 내면 **미래 고점 look-ahead·유령 청산**으로 실매와 다른 엔진이 된다. + +## 절대 금지 + +- 틱 청산 루프에서 `pos["max_price"] = max(..., bar.high / holding_peak)` **선반영** +- `tick_fallback_ohlc or is_eod` 로 EOD에 OHLC 폴백 **강제** +- Optuna / Grid 파람 / 웹백테가 OHLC 폴백 ON 으로 **랭킹·PnL 변조** +- “틱 없으면 OHLC로라도 거래 수 맞추기” + +## 허용 + +- 틱 청산 **OFF** 인 레거시 순수 OHLC 모드 (폴백이 아님 — 봉이 본체) +- EOD: 틱 미청산 시 **직전가/종가로 장마감 플랫만** (봉 고저로 래칫 재계산 금지) + +## 해외 모멘텀 — 국장과 “다르게 설계”한 게 아님 (오해 금지) + +- **실매**: 국장·해외 모두 WS 현재가(틱 캐시)로 청산 판정 (`check_sell_signal_momentum_live`). DB `ws_ticks` 테이블을 장중 리플레이하지 않음. +- **백테/Optuna**: 동일 `momentum_backtest_*` 경로. `market=US` 이면 **`ws_ticks_us`** (국장은 `ws_ticks`). 틱 ON 기본 · OHLC로 고점/체결 변조 금지(본 규칙). +- “해외=순수 분봉·틱청산 해당 없음”은 **잘못된 메모**다. 틱+OHLC 혼입 버그 점검에서 해외를 빼 말하면 **안 된다** — 국장과 같은 정합 규칙을 적용한다. + +## 구현 앵커 + +- `resolve_backtest_sell`: `use_tick_exit` → OHLC 폴백 무시 +- `tail_engine` Phase1: 틱 ON 시 봉 high 로 max 선반영 금지 +- Optuna/파람: `backtest_tick_fallback_ohlc = False` 강제 +- UI: `*_FALLBACK_OHLC` / 꼬리 체크 = **빨간 음영** (위험 표시) + +## 보고 + +고치기 전 “폴백으로 맞추자”고 제안하지 말 것. 잔여 갭은 **틱 공백·체결 슬리피지·미청산 보유**로 분류. diff --git a/.cursorrules b/.cursorrules index 8b4610b..5a0e296 100644 --- a/.cursorrules +++ b/.cursorrules @@ -83,7 +83,12 @@ - 21. 백테 웹페이지 탭 ui 수정시 다른 전략 탭도 모두 수정되어야 한다. - 가상거래내역, 실거래내역 ui 는 모두 동일해야한다. - 22. **봉 정합 다음 할 일**은 `docs/정합성.md` §8 (깨끗한 장일 검증 → 웹백테 1회 → §5 체크). 새 ±1 보정·freeze OFF로 “해결”하지 말 것. -# [CRITICAL SYSTEM DIRECTIVES: 절대 엄수 사항 - 위반 시 작동 중지] +- 23. **키움/KIS 인프라** — CRITICAL §3 (유량≠빈응답, approval 공유, REST/키발급 한도 준수, 시세실키≠매매모의, REST 웜업 1차→실패시만 증량). +- 24. **에이전트 테스트·adhoc 뻘짓 금지** — CRITICAL §4 (스키마 먼저, import/캐시 실명, env 키=config 테이블 등록, 실패 1회만). +- 25. **의심·불확실 시 근본원인 먼저** — CRITICAL §5 (MCP·`docs/`·공식 API 문서·기존 SafeRequest/세마포어부터. 추측 패치·한도 무시 재시도 금지). +- 26. **국내→해외/탭 이식 — 복붙 ≠ 완료** — CRITICAL §6 (summary 키·전역/종목 저장·폼 덮어쓰기·브라우저 검증). 상세: `.cursor/rules/domestic-port-ui-parity.mdc` +- 27. **해외 전략 UI = 국장과 최대한 동일** — CRITICAL §6-1. 꼭 다르게 해야 하면 **구현 전 선보고·승인**. 임의로 생략·단축 금지. +# [CRITICAL SYSTEM DIRECTIVES: 절대 엄수 사항 - 위반 시 작업 중지] ## 0. 🚨 봉 정합·진입 정렬 — 절대 금지 (위반 시 작업 중지) - **신호 = T−1 확정봉, 진입 = T (시가/첫 틱).** `live_backtest_align=True` 잠금. @@ -92,6 +97,9 @@ - **`WS_CANDLE_FREEZE_ON_CONFIRM` 끄기(false) 절대 금지.** 사용자 **명시 승인** 없이 DB/기본값을 false·레거시 덮어쓰기로 되돌리지 마라. - **`*_SKIP_HTS_SCAN_DUPES` 를 true로 바꾸기 절대 금지.** (TAIL/MOMENTUM/BREAKOUT/SCALP 포함) 사용자가 명시하기 전까지 **false / 0 유지**. “HTS와 맞추려고” true로 바꾸지 마라. (상세: 항목 16 · `hts-condition-grids.mdc`) +- **OHLC 폴백으로 숫자 변조 절대 금지.** 틱 청산/진입 ON 이면 봉 OHLC(high/low)로 체결·`max_price`·PnL을 채우지 마라. + EOD에 OHLC 폴백 강제·틱 전 봉 high 선반영 = 실매와 다른 엔진. Optuna/파람/웹백테는 `*_TICK_FALLBACK_OHLC` **강제 OFF**. + UI에 남은 폴백 체크는 **빨간 위험 표시**일 뿐, 켜서 정합 “맞추기” 금지. (상세: `no-ohlc-fallback-parity.mdc`) ## 1. 🚨 하드코딩 절대 금지 (NO HARDCODING) - 어떠한 경우에도 코드 내부에 임계값, 비율, 점수, 시간 등의 수치를 직접 하드코딩하지 마라. @@ -103,6 +111,97 @@ - 사용자가 "A를 매수 체크 로직으로 옮겨"라고 지시하면, 단순히 A만 옮기지 마라. 사용자의 의도는 **"스캔(Scan) 단계에서는 조건 필터링을 최소화하여 후보를 DB에 최대한 많이 올리고, 실제 매수 직전(Trigger)에 모든 엄격한 필터(보조지표, 호가, 수급 등)를 한 번에 검사하라"**는 뜻이다. - 무거운 연산(API 추가 호출, 분봉 분석 등)은 절대 5분 주기 스캔 함수에 넣지 말고, 매수 타점 체크 함수에 넣어라. +## 3. 🚨 키움/KIS 인프라 — 유량·approval·REST/키발급 한도 (위반 시 폭주/키 무효) +### 3-0. REST·키 발급 한도 — 서버·계정 부하 금지 (필수) +- 작성·수정하는 코드·진단·스모크·adhoc는 **키움/한투 REST 초당·일일 유량, WS 구독 한도, OAuth·approval·접근토큰 발급 횟수**를 어기지 않게 설계한다. +- REST는 가능하면 **WS/캐시/DB 재사용**. 루프·재큐·벌크·Optuna prepare에서 한도 무시 연타 금지. `SafeRequest`·기존 세마포어·sleep·쿨다운을 **우회하는 새 경로**를 만들지 마라. +- **토큰/approval을 “안 되면 다시 발급”으로 때우지 마라.** 공유 캐시·만료 전 재사용·응급 하드캡(§3-2)을 지킨다. 진단 스크립트도 실매와 동일하게 한도를 존중한다. +- 한도·에러코드·도메인(실전/모의 REST·WS URL)이 헷갈리면 **추측하지 말고** §5(문서·MCP)로 확인한 뒤 구현한다. (`docs/계정.md` 등) + +### 3-1. API 에러 ≠ 데이터 없음 +- 키움 `return_code=5`(유량/한도) 등을 “빈 봉·조회 실패”로 취급해 재큐·재시도를 무한히 돌리지 마라. +- 유량/429/한도는 **백오프·세마포어·실패 상한 후 재큐 정지**가 필수. “고쳤다” 보고 전 API 폭주 여부를 로그로 확인하라. + +### 3-2. approval 키는 단일 공유 +- KIS Websocket `approval_key`는 국내·해외·검증 경로가 **같은 KISApprovalManager(공유 캐시)** 를 쓴다. +- 모듈/재시작마다 REST로 approval을 새로 받아 타 연결을 무효화하는 코드 금지. +- 응급 재발급은 **하드캡(기본 6h 1회)** 없이 돌리지 마라. + +### 3-3. 시세 실키 vs 매매 모의 +- 진단·스모크가 `KIS_MOCK`만 보고 “전체가 모의”라고 단정하지 마라. +- 시세(키움/한투 WS) 실키 강제와 매매 모의는 분리해서 보고·검증하라. + +### 3-4. 백테 REST 웜업 — 평소 최소, 실패 시에만 증량 +- 전일 장시작 시가 보강 REST는 **짧은 1차(기본 700)** 만 기본으로 한다. +- **전일(직전 세션) 장시작이 안 잡힐 때만** 긴 2차(기본 1500)를 **해당 종목 1회** 재시도한다. 전 종목·전 trial에 장봉을 상시 올리지 마라. +- Optuna/그리드 trial 경로에서 prepare 이후 **캐시 hit·영구실패면 INFO 로그·REST 재호출 금지**. + +### 3-5. Optuna 새 mode / apply 누락 +- Grid에 없는 mode(`tpe` 등)를 추가하면 `grids[mode]` **재조회·가정**을 전부 제거하고, 실행 1회로 KeyError 없는지 확인하라. +- `--apply-best`/JSON 적용 시 Optuna가 탐색한 축(`setup_*` 등)이 apply 패치·`config_*` 키 목록에 **빠지지 않았는지** 확인. 신규 env 키는 code default + DB 컬럼/키 등록을 한 세트로 한다. + +## 4. 🚨 에이전트 테스트·adhoc·셸 — 뻘짓·토큰 낭비 금지 +대화에서 실제로 낭비된 패턴을 **반복하지 마라.** 상세: `.cursor/rules/db-adhoc-query-safety.mdc`, `agent-shell-python-safety.mdc`. + +### 4-1. DB — 추측 SELECT/컬럼 금지 +- SQL 전 **반드시** `SHOW COLUMNS FROM
| ${nameCell} | + ? `${fmtPnlCell(r.pnl, rowFx)} 평가` + : fmtPnlCell(r.pnl, rowFx); + const isParityEx = !!r.parityException; + const rowCls = [ + r.isOpen ? 'trade-row-open' : '', + isParityEx ? 'trade-row-parity-ex' : '', + ].filter(Boolean).join(' '); + const causeBadge = isParityEx + ? `${r.parityCause || '예외'}` + : ''; + const rowStyle = r.isOpen && !isParityEx ? ' style="background:rgba(210,153,34,.06)"' : ''; + const liveAttrs = r.isOpen && r.code && r.ep > 0 && r.qty > 0 + ? ` data-live-code="${String(r.code).replace(/"/g, '')}" data-live-buy="${r.ep}" data-live-qty="${r.qty}" data-live-usd="${r.isUsd ? '1' : '0'}"` + : ''; + let html = `||||||||||||||||||
| ${causeBadge}${nameCell} | ${fmtTradeTime(r.buyRaw)} | ${sellCell} | -${fmtWon(r.ep)} | -${exitPxCell} | +${fmtPx(r.ep, rowFx)} | +${exitPxCell} | ${r.qty} | -${pnlCell} | -${fmtPct(r.rateNum)} | `; +${pnlCell} | +${fmtPct(r.rateNum)} | `; if (opts.showCumulative) { html += `${cumPnlHtml} | ${cumPctHtml} | `; } @@ -253,6 +378,9 @@ function renderVirtualTrades(tbodyId, trades, opts) { tbody.insertAdjacentHTML('beforeend', html); }); _syncTradeSortButtons(tbodyId, mode); + if (rows.some(r => r.isOpen)) { + liveQuoteStartFromDom(tbody); + } } function setVirtualTradesSort(tbodyId, mode) { @@ -384,6 +512,8 @@ document.querySelectorAll('[data-tab]').forEach(el => { $('tab-breakout').style.display = tab === 'breakout' ? '' : 'none'; $('tab-range_break').style.display = tab === 'range_break' ? '' : 'none'; $('tab-momentum').style.display = tab === 'momentum' ? '' : 'none'; + $('tab-us_momentum').style.display = tab === 'us_momentum' ? '' : 'none'; + $('tab-optuna').style.display = tab === 'optuna' ? '' : 'none'; $('tab-holding').style.display = tab === 'holding' ? '' : 'none'; $('tab-updownbox').style.display = tab === 'updownbox'? '' : 'none'; $('tab-updow').style.display = tab === 'updow' ? '' : 'none'; @@ -394,10 +524,27 @@ document.querySelectorAll('[data-tab]').forEach(el => { if (tab === 'portfolio') pfLoad(true); if (tab === 'holding') hdLoadStocks(); if (tab === 'updow') permLoad(); + if (tab === 'us_momentum') { + updateUsMomFxHint(); + usmomLoadStocks(false); + usmomOptSyncDatesFromMain(); + if (_usmomOptJobId) usmomStockOptunaStartPolling(); + } if (tab === 'dart') dartOnTabShow(); if (tab === 'updownbox') ubxOnTabShow(); if (tab === 'dbband') dbLoadStocks(); if (tab === 'breakout' && typeof boSyncSlModeColors === 'function') boSyncSlModeColors(); + if (tab === 'optuna') optunaOnTabShow(); + // 보유 라이브 시세: 보유·실거래 탭에서만 SSE/폴링 (다른 탭 이탈 시 중지) + if (tab === 'portfolio') { + /* pfRenderTable 에서 start */ + } else if (tab === 'actual') { + const at = $('act_tbody'); + if (at && at.querySelector('[data-live-code]')) liveQuoteStartFromDom(at); + else liveQuoteStop(); + } else { + liveQuoteStop(); + } }); }); @@ -502,20 +649,90 @@ async function dartRunBacktest() { // ════════════════════════════════════════════════════════════════ // 영구구독 관리 (permanent_subscriptions: KR 국내WS / US 해외WS) // ════════════════════════════════════════════════════════════════ +let _permRefreshTimer = null; +let _permRefreshSec = 15; + function permOnMarket() { // 시장 선택 시 거래소 기본값 자동 보정 (KR→KRX, US→NASD) const mk = $('perm_market') ? $('perm_market').value : 'KR'; if ($('perm_exch')) $('perm_exch').value = (mk === 'US') ? 'NASD' : 'KRX'; } -async function permLoad() { +function permFmtQuoteTime(t) { + const s = String(t || ''); + if (s.length >= 12) { + return s.slice(4, 6) + '/' + s.slice(6, 8) + ' ' + s.slice(8, 10) + ':' + s.slice(10, 12); + } + if (s.length >= 8) return s.slice(0, 4) + '-' + s.slice(4, 6) + '-' + s.slice(6, 8); + return s; +} + +function permFmtPriceCell(x, fxRate) { + const px = Number(x.price); + if (!(px > 0)) { + return '—|||||
| 등록된 영구구독 없음 | ||||||||||||||||||
| 등록된 영구구독 없음 | ||||||||||||||||||
| ' + x.code + ' | ' + + '' + title + ' | ' + + '' + permFmtPriceCell(x, fx) + ' | ' + '' + mkBadge + ' | ' + '' + (x.exchange || '') + ' | ' - + '' + (x.symbol || '') + ' | ' - + '' + (x.tf_min || '') + ' | ' - + '' + (x.ws_tr_key || '') + ' | '
+ + '' + baseTf + '분 | ' + + '' + (x.ws_tr_key || '') + ' | '
+ '' + onBadge + ' | ' - + '' + (x.note || '') + ' | ' - + '' - + ' | ' + + '' + (x.note || '') + ' | ' + + '' + + ' | ' + '||||
| 로드 실패: ' + e + ' | ||||||||||||||||||
| 로드 실패: ' + e + ' | ||||||||||||||||||
| ${stratLbl} | ${it.name} ${it.code} |
${pfFmtBuyDate(it.buy_date)} | @@ -857,12 +1288,14 @@ function pfRenderTable() {${it.broker_avg_price != null && it.broker_avg_price > 0 ? fmtWon(it.broker_avg_price) : '—'} | -${fmtWon(it.current_price)} | -${it.pnl_pct >= 0 ? '+' : ''}${it.pnl_pct}% | -${fmtWon(it.eval_amt)} | +${fmtWon(it.current_price)} | +— | +${it.pnl_pct >= 0 ? '+' : ''}${it.pnl_pct}% | +${fmtWon(it.eval_amt)} | ${sellBtn} | |||||||
| 영구구독 US 없음 | ||||||||||||||||||
| ${s.code} | ${s.stock_group || ''} | ${s.exchange || ''} | +${s.has_stock_cfg ? '✅' : '—'} | +${s.enabled ? 'ON' : 'OFF'} | ||||||||||||||
| ${e} | ||||||||||||||||||
| ${source} 후보 없음 | ||||||||||||||||||
| ${rank} | +#${r.optuna_trial_number ?? '—'} | +${usmomOptFmtUsdOrNum(r.total_trades, 0)} | +${usmomOptFmtUsdOrNum(r.win_rate, 1)}% | +${usmomOptFmtUsdOrNum(r.pf, 2)} | +${usmomOptFmtUsdOrNum(r.total_pnl)} | ++ + + + | +||||||||||||
| ${msg || '백그라운드 실행 중…'} | ||||||||||||||||||
| 완료 후 표시 | ||||||||||||||||||
| ${r.label || r.source} | +${r.optuna_trial_number != null ? '#' + r.optuna_trial_number : '—'} | +${optunaFmtNum(r.total_trades, 0)} | +${optunaFmtNum(r.win_rate, 1)} | +${optunaFmtNum(r.pf, 2)} | +${optunaFmtNum(r.total_pnl)} | +${r.n_losing_days != null ? optunaFmtNum(r.n_losing_days, 0) : '—'} | +${r.worst_day_pnl != null ? optunaFmtNum(r.worst_day_pnl) : '—'} | +${r.stability_score != null ? optunaFmtNum(r.stability_score, 2) : '—'} | +||||||||||
| gated 후보 없음 / 미완료 | ||||||||||||||||||
| ${rank} | +#${r.optuna_trial_number ?? '—'} | +${optunaFmtNum(r.total_trades, 0)} | +${optunaFmtNum(r.win_rate, 1)}% | +${optunaFmtNum(r.pf, 2)} | +${optunaFmtNum(r.total_pnl)} | ++ + + | +||||||||||||
| stable 후보 없음 (구 JSON은 재실행 필요 / 게이트 미충족) | ||||||||||||||||||
| ${rank} | +#${r.optuna_trial_number ?? '—'} | +${optunaFmtNum(r.total_trades, 0)} | +${optunaFmtNum(r.win_rate, 1)}% | +${optunaFmtNum(r.total_pnl)} | +${r.n_losing_days != null ? optunaFmtNum(r.n_losing_days, 0) : '—'} | +${r.worst_day_pnl != null ? optunaFmtNum(r.worst_day_pnl) : '—'} | +${r.stability_score != null ? optunaFmtNum(r.stability_score, 2) : '—'} | ++ + + | +||||||||||
| 잡 없음 | ||||||||||||||||||
${jid} |
+ ${label} | +${job.start || ''}~${job.end || ''} | +${job.status || ''} | +${prog.trials_done ?? '—'}/${prog.trials_total ?? job.trials ?? '—'} | +${job.started_at || ''} | +${job.finished_at || '—'} | ++ | |||||||||||
| ${e} | ||||||||||||||||||
| 전략 | 종목 | 매수시각 | DB수량 | 실계좌 | 동기 | -DB매수가 | 실평단 | 현재가 | 수익률 | 평가금 | 매도 | +DB매수가 | 실평단 | 현재가 | +당일% | +매수대비% | +평가금 | 매도 |
|---|
target_candidates_history) — 체크: DB 슬롯·후보 / 해제: ws_candles 전 종목
+ 저장 후보 이력 — 체크: DB 슬롯·후보 / 해제: ws_candles 전 종목
ws_ticks) — 기본 ON
SHORT_DAILY_PROFIT_TARGET_ENABLED. 이번만 끄려면 체크 해제 후 실행(봇저장 X면 DB 유지).SHORT_DAILY_PROFIT_*.BREAKOUT_DAILY_PROFIT_TARGET_ENABLED. 이번만 끄려면 체크 해제 후 실행(봇저장 X면 DB 유지).BREAKOUT_DAILY_PROFIT_*.target_candidates_history) — 체크(기본) = DB 슬롯·후보 재현 (param_search_momentum 기본과 동일).
+ 저장 후보 이력 사용 — 체크(기본) = DB 슬롯·후보 재현 (param_search_momentum 기본과 동일).
체크 해제 = 모멘텀 시뮬 유니버스 (--fallback-universe).
MOMENTUM_DAILY_PROFIT_TARGET_ENABLED. 이번만 끄려면 체크 해제 후 실행(봇저장 X면 DB 유지).MOMENTUM_DAILY_PROFIT_*.