feat(운영설정): SCALP 노출·전략 ON/OFF 최상단·손익 한줄 UI

LIVE_STRATEGY_IDS/SWITCH_IDS에 SCALP를 추가하고 EOD·env 필드를 정리한다.
운영설정 CSS로 손익 칩·전략 토글 가로 한 줄 레이아웃을 지원한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name
2026-08-28 16:46:49 +09:00
parent d737deb47c
commit d837ae1ca6
2 changed files with 94 additions and 26 deletions

View File

@@ -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

View File

@@ -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;