From d837ae1ca63fd44ba998bc9b64baa4304b74e3a0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 28 Aug 2026 16:46:49 +0900 Subject: [PATCH] =?UTF-8?q?feat(=EC=9A=B4=EC=98=81=EC=84=A4=EC=A0=95):=20S?= =?UTF-8?q?CALP=20=EB=85=B8=EC=B6=9C=C2=B7=EC=A0=84=EB=9E=B5=20ON/OFF=20?= =?UTF-8?q?=EC=B5=9C=EC=83=81=EB=8B=A8=C2=B7=EC=86=90=EC=9D=B5=20=ED=95=9C?= =?UTF-8?q?=EC=A4=84=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LIVE_STRATEGY_IDS/SWITCH_IDS에 SCALP를 추가하고 EOD·env 필드를 정리한다. 운영설정 CSS로 손익 칩·전략 토글 가로 한 줄 레이아웃을 지원한다. Co-authored-by: Cursor --- kis_trader/web/live_config_schema.py | 53 +++++++++++----------- static/css/backtest.css | 67 ++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/kis_trader/web/live_config_schema.py b/kis_trader/web/live_config_schema.py index f0c360d..cf8eebb 100644 --- a/kis_trader/web/live_config_schema.py +++ b/kis_trader/web/live_config_schema.py @@ -8,11 +8,11 @@ from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple -# 웹 탭에 노출할 전략 ID (kis_trader 봇) -# ※ SCALP·RANGE_BREAK·DBBAND 는 사용자 지정으로 숨김 (strategy_ids.HIDDEN_STRATEGY_IDS) -# → 운영설정 ON/OFF·운용한도·일일익절·휩쏘 폼에서 제외. 실거래 중인 SHORT·BREAKOUT·MOMENTUM + UPDOW 만 노출. +# 웹 탭에 노출할 전략 ID (kis_trader 봇) — 오늘 운영 탭·대시보드 순서와 동일 +# ※ RANGE_BREAK·DBBAND 는 strategy_ids.HIDDEN_STRATEGY_IDS 로 숨김 유지 # ※ US_MOMENTUM 은 전략 ON/OFF 에만 노출 (유니버스/KRW 한도 폼은 국내와 축이 다름). LIVE_STRATEGY_IDS: Tuple[str, ...] = ( + "SCALP", "SHORT", "MOMENTUM", "BREAKOUT", @@ -22,6 +22,7 @@ LIVE_STRATEGY_IDS: Tuple[str, ...] = ( # 전략 ON/OFF 전용 — 해외를 맨 위 LIVE_STRATEGY_SWITCH_IDS: Tuple[str, ...] = ( "US_MOMENTUM", + "SCALP", "SHORT", "MOMENTUM", "BREAKOUT", @@ -218,6 +219,12 @@ def build_live_config_groups() -> List[GroupDef]: "RAM 미반영·읽기 체인 실패(필터 max_age=0=마지막 RAM 은 예외). " "1·2·3차 같음. 0이면 나이 컷 OFF. 기본 3. 적재(공책)와 분리." )), + _f("FEED_STATS_DISCONNECT_SOFT_SEC", "📡 수집통계 끊김(소프트) 초", "int", default=10, + hint="수집통계 탭: 종목별 recv_ts 간격 ≥이 값이면 끊김 카운트. 기본 10"), + _f("FEED_STATS_DISCONNECT_HARD_SEC", "📡 수집통계 끊김(하드) 초", "int", default=60, + hint="수집통계 탭: ≥이 값이면 긴 끊김. 기본 60 (소켓 알람 체감과 유사)"), + _f("FEED_STATS_DISCONNECT_CAP_SEC", "📡 수집통계 끊김 상한(초)", "int", default=1800, + hint="이보다 긴 공백은 장외·미구독으로 보고 끊김 집계에서 제외. 기본 1800"), _f("TRIGGER_FEED_DETAIL_LOG", "📝 트리거 로그에 틱·호가·벤더 상세", "bool", default=True, hint=( "ON=매수체크(🔍) 로그 끝에 틱1차설정/실제(kis|kiwoom|ls)·현재가·틱타임·" @@ -346,6 +353,12 @@ def build_live_config_groups() -> List[GroupDef]: hint="키움 ka10080 → ls_ws_candles INSERT IGNORE. ws_candles 에 넣지 않음."), _f("PERM_LS_FILL_SLEEP_MIN", "확정봉 가져오기 종목간 sleep 최소(초)", "float", default=1.0), _f("PERM_LS_FILL_SLEEP_MAX", "확정봉 가져오기 종목간 sleep 최대(초)", "float", default=3.0), + _f("PERM_ALERT_ENABLED", "영구구독 목표가 MM알람", "bool", default=True, + hint="ON=목표가 도달 시 Mattermost(permanent 채널). 봇 루프 폴링."), + _f("PERM_ALERT_POLL_SEC", "목표가 검사 주기(초)", "int", default=3, + hint="heartbeat 에서 검사 간격. 최소 1."), + _f("KIS_PERM_SUB_MM_CHANNEL", "영구구독 MM alias", "text", default="permanent", + hint="mm_config.json channels 키. 채널 만든 뒤 ID 등록."), _f("KIS_APP_KEY_OB_REAL", "🔑 KIS 실전 호가 전용 앱키 (선택)", "text", hint="호가 전용 2번째 실전 앱키. 미입력 시 메인에 호가 TR 안 붙임(합산 금지). spill 틱용 아님."), _f("KIS_APP_SECRET_OB_REAL", "🔑 KIS 실전 호가 전용 시크릿 (선택)", "text", @@ -599,7 +612,6 @@ def build_live_config_groups() -> List[GroupDef]: ] # 호가필터 — 전략별 (글로벌 ORDERBOOK_* 수치 폐기) - # SCALP 는 LIVE_STRATEGY_IDS 에서 숨김이어도 config_scalp 키는 노출 from kis_trader.engine.orderbook_env import ( OB_DEFAULT_ASK_WALL_MAX_QTY, OB_DEFAULT_ENTRY_ASK_MAX_MULT, @@ -711,7 +723,7 @@ def build_live_config_groups() -> List[GroupDef]: "해외 WS(HDFSCNT0)", "bool", default=True, - hint="기동 시만 · false=해외 틱 소켓 미기동 · 전략 ON/OFF 와 별개 · 변경 후 재시작", + hint="기동 시만 · false=해외 틱 소켓 미기동 · 영구구독 US 시세/알람용(전략 OFF여도 ON 가능) · 변경 후 재시작", ), *[ _f( @@ -894,11 +906,6 @@ def build_live_config_groups() -> List[GroupDef]: ws_fields.append( _f(f"{pfx}_WHIPSAW_FILTER_ENABLED", f"{lbl} 휩쏘 필터", "bool"), ) - # SCALP 는 LIVE_STRATEGY_IDS 에서 숨겨도 휩쏘는 Optuna·엔진 필수 → 노출 - if not any(f.get("key") == "SCALP_WHIPSAW_FILTER_ENABLED" for f in ws_fields): - ws_fields.append( - _f("SCALP_WHIPSAW_FILTER_ENABLED", "스캘핑 휩쏘 필터", "bool"), - ) groups.append({ "id": "whipsaw_strategy", "title": "휩쏘 — 전략별 ON/OFF (진입 차단)", @@ -1368,21 +1375,6 @@ def build_live_config_groups() -> List[GroupDef]: ], }) - groups.append({ - "id": "scalp_exit", - "title": "스캘핑(SCALP) — EOD", - "hint": ( - "config_scalp · 저장 후 다음 매도 루프부터 반영(재시작 권장). " - "실매 기존 하드코딩 15:25 장마감청산과 동일 기본값." - ), - "fields": [ - _f("SCALP_EOD_ENABLED", "EOD 당일청산 사용", "bool", default=True, - hint="ON=설정 시각 이후 보유 전량 장마감청산 · OFF=익절·손절·어깨만"), - _f("SCALP_EOD_HM", "EOD 시각 (HH:MM)", "text", default="15:25", - hint="실매 기존 15:25 · 정규장 15:30 마감 직전 순차매도"), - ], - }) - groups.append({ "id": "breakout_exit", "title": "📉 매도 호가익절·손절 (EXIT_OB/STOP_OB) — 돌파", @@ -1457,6 +1449,10 @@ def build_live_config_groups() -> List[GroupDef]: "title": "🌅 EOD 당일청산 설정 (전략 통합)", "hint": "각 전략별로 정해진 시각이 되면 보유 물량을 전량 청산(퇴근)합니다.", "fields": [ + _f("SCALP_EOD_ENABLED", "스캘핑 EOD 사용", "bool", default=True, + hint="ON=설정 시각 이후 보유 전량 장마감청산 · OFF=익절·손절·어깨만"), + _f("SCALP_EOD_HM", "스캘핑 EOD 시각 (HH:MM)", "text", default="15:25", + hint="실매 기존 15:25 · 정규장 15:30 마감 직전 순차매도"), _f("BREAKOUT_EOD_ENABLED", "돌파 EOD 사용", "bool", default=True), _f("BREAKOUT_EOD_HM", "돌파 EOD 시각 (HH:MM)", "text", default="15:15", hint="예: 15:15 (빠르게 정리)"), @@ -1551,7 +1547,7 @@ def build_live_config_groups() -> List[GroupDef]: _f("ORPHAN_PURGE_ACTIVE_AFTER_SELL_HISTORY", "청산후 유령 active 정리", "bool", default=True, hint="ON=active에 남았는데 매수 이후 청산 히스토리면 DB 행 삭제"), _f("GHOST_PURGE_ON_RECONCILE", "고아복구 시 유령잔고 삭제", "bool", default=True, - hint="ON=브로커 보유 0·매도가능 0·정정취소가능 매도 없을 때만 active_trades 삭제(수동보호 제외) · Pre/Post EOD"), + hint="ON=브로커 0주·매도가능0·정정취소가능 없을 때 active 삭제 · **현재 KIS_MOCK=is_mock 행만** (모의↔실전 교차삭제 금지) · Pre/Post/기동"), _f( "GHOST_PURGE_RECORD_HISTORY", "유령정리 시 거래내역 기록", @@ -1571,6 +1567,11 @@ def build_live_config_groups() -> List[GroupDef]: ], }) + # 전략 ON/OFF — 운영설정 탭 최상단 + sw_idx = next((i for i, g in enumerate(groups) if g.get("id") == "strategy_switch"), None) + if sw_idx is not None: + sw = groups.pop(sw_idx) + groups.insert(0, sw) return groups diff --git a/static/css/backtest.css b/static/css/backtest.css index 133733d..62941a1 100644 --- a/static/css/backtest.css +++ b/static/css/backtest.css @@ -122,6 +122,73 @@ .live-quote-stale { opacity: 0.55; } /* 파라미터 설명 툴팁 */ .param-help { font-size: 10px; color: var(--muted); margin-top: 2px; } + /* 운영 설정 — 당일 손익 칩 한 줄 */ + .lc-status-row { + display: flex; + flex-wrap: nowrap; + align-items: stretch; + gap: 10px; + overflow-x: auto; + padding-bottom: 2px; + } + .lc-status-chip { + flex: 0 0 auto; + min-width: 148px; + max-width: 220px; + border: 1px solid var(--border); + border-radius: 8px; + padding: 10px 12px; + background: rgba(22, 27, 34, 0.55); + font-size: 13px; + } + .lc-status-chip .lc-status-label { + font-size: 11px; + color: var(--muted); + margin-bottom: 2px; + white-space: nowrap; + } + .lc-status-chip .lc-status-value { + font-size: 18px; + font-weight: 650; + line-height: 1.2; + } + .lc-status-chip .lc-status-meta { + font-size: 11px; + color: var(--muted); + margin-top: 2px; + white-space: nowrap; + } + /* 전략 ON/OFF — 토글 한 줄 */ + .lc-switch-row { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 10px 14px; + overflow-x: auto; + padding: 4px 0 2px; + } + .lc-switch-chip { + display: inline-flex; + align-items: center; + gap: 6px; + white-space: nowrap; + font-size: 13px; + margin: 0; + cursor: pointer; + user-select: none; + } + .lc-switch-chip input[type="checkbox"] { + margin: 0; + flex-shrink: 0; + } + .lc-switch-chip .lc-switch-sid { + font-size: 10px; + color: var(--muted); + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + } + .lc-switch-chip.lc-switch-warn { + color: #d29922; + } /* 운영 설정 탭 — 최대 2열, 위에서 아래로 읽기 */ .lc-field-grid { display: grid;