refactor: enhance Optuna backtesting framework, optimize orderbook filtering, and update database management utilities.

This commit is contained in:
Your Name
2026-08-12 10:19:19 +09:00
parent cb7e5037a0
commit c6bd62a25f
218 changed files with 31613 additions and 759 deletions

View File

@@ -11,6 +11,7 @@ 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 만 노출.
# ※ US_MOMENTUM 은 전략 ON/OFF 에만 노출 (유니버스/KRW 한도 폼은 국내와 축이 다름).
LIVE_STRATEGY_IDS: Tuple[str, ...] = (
"SHORT",
"MOMENTUM",
@@ -18,10 +19,20 @@ LIVE_STRATEGY_IDS: Tuple[str, ...] = (
"UPDOW",
)
# 전략 ON/OFF 전용 — 해외를 맨 위
LIVE_STRATEGY_SWITCH_IDS: Tuple[str, ...] = (
"US_MOMENTUM",
"SHORT",
"MOMENTUM",
"BREAKOUT",
"UPDOW",
)
LIVE_STRATEGY_LABELS: Dict[str, str] = {
"SCALP": "스캘핑",
"SHORT": "꼬리잡기",
"MOMENTUM": "모멘텀",
"US_MOMENTUM": "해외 모멘텀",
"BREAKOUT": "돌파",
"RANGE_BREAK": "박스권돌파",
"UPDOW": "UPDOWN 박스",
@@ -29,6 +40,19 @@ LIVE_STRATEGY_LABELS: Dict[str, str] = {
"DART": "DART 수주",
}
# 기동 시 _register_strategies 기본값과 동일 (핫게이트 폴백)
STRATEGY_ENABLED_DEFAULTS: Dict[str, bool] = {
"SCALP": True,
"SHORT": True,
"MOMENTUM": False,
"US_MOMENTUM": True,
"BREAKOUT": False,
"RANGE_BREAK": False,
"UPDOW": False,
"DBBAND": False,
"DART": False,
}
# 전략 ID → 실제 env 키 (SHORT 는 TAIL_* 가 canonical — tail_engine·config_short)
STRATEGY_BUDGET_ENV_KEY: Dict[str, Dict[str, str]] = {
"SHORT": {
@@ -156,68 +180,227 @@ def build_live_config_groups() -> List[GroupDef]:
],
},
{
"id": "ws_trigger_global",
"title": "TRIGGER 필터 — WS 공통",
"id": "ws_feed_settings",
"title": "📡 시세 소스 설정 (봇 재시작 필요)",
"hint": (
"호가(0D)·프로그램(0w) · env_config(컬럼 없으면 env_config_ext) · "
"COLLECT=판정순간1장(filter_eval) / SAVE=후보~3초시계열(kiwoom_0d) — 둘은 다름"
"키움과 KIS 중 어느 쪽 시세·호가로 매매할지 선택. "
"어느 쪽을 선택하든 틱·캔들 데이터는 키움+KIS 양쪽 모두 DB에 저장됨. "
"시세/호가만 선택한 증권사를 사용."
),
"fields": [
_f("LIVE_TICK_PROVIDER", "📊 실매매 틱(시세) 소스 (재시작 필요)", "text", default="kiwoom",
hint=(
"kiwoom (권장) = 키움증권 실시간 체결가 사용 | "
"kis = 한국투자증권 실시간 체결가 사용"
)),
_f("LIVE_OB_PROVIDER", "📊 실매매 호가(OB) 소스 (재시작 필요)", "text", default="kiwoom",
hint=(
"kiwoom (권장) = 키움증권 10단계 호가 사용 | "
"kis = 한국투자증권 10단계 호가 사용 (kis 선택 시 아래 'KIS 실시간 호가 수집' 스위치도 ON이어야 합니다)"
)),
_f("WS_TICK_SAVE_KIWOOM", "📈 키움 체결 틱 수집", "bool", default=True,
hint="ON = 키움 체결 틱을 ws_ticks 테이블에 저장 (기본값: ON)"),
_f("WS_ORDERBOOK_SAVE_KIWOOM", "📋 키움 실시간 호가 수집", "bool", default=True,
hint="ON = 키움 호가를 ws_orderbook 테이블에 저장 (기본값: ON)"),
_f("WS_TICK_SAVE_KIS", "📈 KIS 체결 틱 수집", "bool", default=True,
hint="ON = KIS 체결 틱을 ws_ticks 테이블에 저장 (기본값: ON)"),
_f("WS_ORDERBOOK_SAVE_KIS", "📋 KIS 실시간 호가 수집", "bool", default=False,
hint=(
"OFF(기본) = KIS 호가는 수집 안 함 (한도 40개 보호) | "
"ON = KIS 호가도 kis_ws_orderbook 테이블에 저장. "
"단, KIS는 시세+호가 합산 40개 한도라서 ON 시 최대 20종목만 추적 가능"
)),
_f("WS_SUBSCRIBE_KIS_MINIMAL", "📉 KIS 최소 구독 모드", "bool", default=False,
hint=(
"OFF(기본) = KIS가 키움과 동일한 후보 40종목을 전부 수신하여 DB에 저장 | "
"ON = KIS가 현재 내가 사고 있는 종목만 수신 (KIS ID 한도 아낄 때)"
)),
_f("KIS_APP_KEY_OB_REAL", "🔑 KIS 실전 호가 전용 앱키 (선택)", "text",
hint="한투 40종목 구독 한도 우회를 위한 2번째 실전 앱키. 미입력 시 기존 앱키로 호가까지 공용 구독합니다."),
_f("KIS_APP_SECRET_OB_REAL", "🔑 KIS 실전 호가 전용 시크릿 (선택)", "text",
hint="호가 전용 2번째 실전 시크릿키."),
_f("KIS_APP_KEY_OB_MOCK", "🔑 KIS 모의 호가 전용 앱키 (선택)", "text",
hint="모의투자용 호가 전용 2번째 앱키."),
_f("KIS_APP_SECRET_OB_MOCK", "🔑 KIS 모의 호가 전용 시크릿 (선택)", "text",
hint="모의투자용 호가 전용 2번째 시크릿키."),
_f("LIVE_VALIDATOR_ENABLED", "🔬 시세 무결성 검증기 기동 (재시작 필요)", "bool", default=False,
hint="ON = 실매매 중 키움/KIS 시세 교차 검증 데몬을 백그라운드에 함께 기동 (기본값: OFF)"),
],
},
{
"id": "ws_trigger_global",
"title": "⚡ 매수 판단 필터 설정 (실시간 WS 기반)",
"hint": (
"매수 타이밍을 결정할 때 호가·프로그램 데이터를 어떻게 사용할지 설정. "
"모든 데이터는 실시간 메모리(RAM)에서 직접 읽으므로 딜레이 없음."
),
"fields": [
_f("KIWOOM_WS_ORDERBOOK_ENABLED", "호가 WS(0D) 구독", "bool", default=True,
hint="키움 실시간 호가 수신(RAM) · 꺼면 필터·수집·시계열 모두 불가"),
_f("KIWOOM_WS_PROGRAM_ENABLED", "프로그램 WS(0w) 구독", "bool", default=True,
hint="키움 프로그램매매 수신(RAM)"),
_f(
"WS_TRIGGER_EVAL_SAVE_ENABLED",
"TRIGGER 판정 DB 저장(마스터)",
"KIWOOM_WS_PROGRAM_ENABLED",
"🤖 키움 프로그램 매매 데이터 수신",
"bool",
default=True,
hint="OFF면 아래 COLLECT(판정 스냅) 전부 안 씀 · SAVE(시계열)와 무관",
hint=(
"ON = 기관·외국인 프로그램 매매 흐름을 메모리에 저장 | "
"OFF = 프로그램 매매 필터 사용 불가"
),
),
_f(
"WS_TRIGGER_EVAL_SAVE_ENABLED",
"💾 매수 판단 결과 DB 저장 (마스터 스위치)",
"bool",
default=True,
hint=(
"ON = 매수 검토할 때 호가·프로그램 스냅샷을 DB에 기록 (분석·디버깅용) | "
"OFF = 아래 모든 저장 비활성 (DB 용량 아낄 때)"
),
),
_f(
"WS_ORDERBOOK_TICK_MAX_AGE_SEC",
"⏱ 호가 유효 시간(초)",
"float",
default=3.0,
hint=(
"틱이 들어왔을 때 호가 데이터가 이 시간(초)보다 오래됐으면 저장 안 함 | "
"→ 오래된 호가로 가짜 데이터 채우는 것 방지. 기본 3초"
),
),
_f(
"WS_ORDERBOOK_COLLECT_ENABLED",
"호가 판정스냅(FILTER_EVAL) — 매수체크 순간 1",
"📸 매수 검토 순간 호가창 1장 저",
"bool",
default=True,
hint="≠ 연속시계열. TRIGGER 판정 때만 ws_orderbook source=filter_eval · 필터 OFF여도 저장 가능",
hint=(
"ON = 매수 검토 순간 호가창(10호가 잔량 등) 스냅샷도 DB에 저장 | "
"→ 매수/거절 당시의 호가 상태 정밀 분석용 (용량 주의)"
),
),
_f(
"WS_ORDERBOOK_SAVE_ENABLED",
"호가 시계열(KIWOOM_0D) — 구독후보 ~3초 덤프",
"bool",
default=False,
hint="틱처럼 후보·보유만 연속 저장(source=kiwoom_0d) · 파람서치 호가축용 · 봇 재시작 필요 · 필터와 무관",
),
_f(
"WS_TRIGGER_SNAPSHOT_DB_INTERVAL_SEC",
"호가/프로그램 시계열 간격(초)",
"float",
default=3.0,
hint="SAVE ON일 때만 · 종목당 최소 저장 간격 · 봇 재시작 필요",
),
# 글로벌 ORDERBOOK_FILTER / MAX_SPREAD 폐기 → 아래「호가필터 — 전략별」
_f(
"WS_PROGRAM_COLLECT_ENABLED",
"프로그램 판정스냅 — 매수체크 순간 1",
"📸 매수 검토 순간 프로그램 매매 1장 저",
"bool",
default=True,
hint="≠ 연속시계열. 판정 때만 ws_program · 필터 OFF여도 가능",
hint=(
"ON = 매수 검토 순간 프로그램 매매 스냅샷도 DB에 저장 | "
"→ 호가 저장과 동일 개념, 프로그램 필터 분석용"
),
),
_f(
"WS_PROGRAM_SAVE_ENABLED",
"프로그램 시계열 — 구독후보 ~3초 덤프",
"📼 프로그램 매매 연속 시계열 저장",
"bool",
default=False,
hint="SAVE와 동일 Recorder · 봇 재시작 필요 · 필터와 무관",
hint=(
"ON = 프로그램 매매 데이터를 계속 DB에 쌓음 | "
"평소엔 OFF 권장, 변경 후 봇 재시작 필요"
),
),
_f(
"PROGRAM_FILTER_ENABLED",
"🚫 프로그램 매매 필터 (매수 차단)",
"bool",
default=True,
hint=(
"ON = 프로그램 매매 흐름이 나쁠 때 매수를 차단 (기본 ON) | "
"OFF = 프로그램 매매 무시하고 그냥 매수"
),
),
],
},
{
"id": "ws_whipsaw_global",
"title": "🌊 휩쏘 필터 (가짜 상승 차단)",
"hint": (
"매수 신호가 떴는데 직전에 급락이 있었으면 가짜 신호(휩쏘)로 판단해서 차단. "
"예: 30초 안에 0.3% 이상 뚝 떨어졌다가 올라온 종목은 패스."
),
"fields": [
_f(
"WHIPSAW_FILTER_ENABLED",
"🚫 휩쏘 필터 ON/OFF (전략 공통)",
"bool",
default=True,
hint="ON = 가짜 상승 종목 매수 차단 | OFF = 필터 끄고 다 매수",
),
_f(
"WHIPSAW_SUBBAR_SEC",
"⏱ 관찰 단위 시간(초)",
"int",
default=30,
hint=(
"틱을 몇 초 단위로 묶어서 미니봉을 만들지 | "
"기본 30초 = 30초짜리 막대기로 패턴 분석"
),
),
_f(
"WHIPSAW_LOOKBACK_SEC",
"⏳ 몇 초 전까지 볼지",
"int",
default=90,
hint=(
"매수 신호 직전 몇 초를 돌아볼지 | "
"기본 90초 = 1분 30초 전까지 확인"
),
),
_f(
"WHIPSAW_DIP_PCT",
"📉 이 이상 떨어졌으면 가짜 신호로 판단 (분율)",
"float",
default=0.003,
hint=(
"0.003 = 0.3% | 직전에 0.3% 이상 하락했다가 올라온 종목은 차단 | "
"더 큰 값 = 필터가 느슨해짐"
),
),
_f(
"WHIPSAW_RECOVERY_TOL_PCT",
"📈 이 정도는 회복 허용 (분율)",
"float",
default=0.001,
hint=(
"0.001 = 0.1% | 하락 후 0.1% 이내로 빠르게 회복하면 정상으로 봄 | "
"노이즈성 미세 출렁임 허용"
),
),
],
},
{
"id": "ws_universe_alert",
"title": "🔔 유니버스·알림 임계값",
"hint": "봇이 이상한 상황을 감지해서 알람을 보내기 위한 임계값 설정.",
"fields": [
_f(
"LIVE_UNIVERSE_SLOT_ALIGN",
"🔗 유니버스 히스토리 교집합 필터",
"bool",
default=False,
hint=(
"OFF(기본권장) = 실시간 후보 그대로 사용 | "
"ON = 실시간 후보와 과거 히스토리 스냅이 겹치는 종목만 매수. "
"히스토리가 늦게 올라오면 매수 기회 놓칠 수 있음"
),
),
_f(
"OPS_ALERT_HISTORY_OPEN_GRACE_SEC",
"🕐 개장 직후 히스토리 0건 허용 시간(초)",
"int",
default=180,
hint=(
"개장 후 N초 안에는 히스토리가 0건이어도 알람 안 보냄 | "
"기본 180초(3분) = 개장 3분까지는 정상으로 취급"
),
),
_f(
"OPS_ALERT_WS_DOWN_HOLD_SEC",
"📡 소켓 끊김 알람 기준 시간(초)",
"int",
default=60,
hint=(
"키움/LS 소켓이 이 시간(초) 이상 끊겨 있을 때만 위험 알람 발송 | "
"기본 60초 = 1분 이상 끊겨야 알람 (순간 재연결은 무시)"
),
),
_f("PROGRAM_FILTER_ENABLED", "프로그램 필터(TRIGGER) — 실매 탈락", "bool", default=True),
_f("WHIPSAW_FILTER_ENABLED", "휩쏘 필터(글로벌)", "bool", default=True),
_f("WHIPSAW_SUBBAR_SEC", "휩쏘 미니봉(초)", "int", default=30,
hint="틱을 N초 봉으로 묶음 · 실매 기본 30 · Optuna 꼬리 TPE 탐색"),
_f("WHIPSAW_LOOKBACK_SEC", "휩쏘 룩백(초)", "int", default=90,
hint="신호 직전 몇 초를 볼지 · 실매 기본 90"),
_f("WHIPSAW_DIP_PCT", "휩쏘 하락폭(분율, 0.003=0.3%)", "float", default=0.003),
_f("WHIPSAW_RECOVERY_TOL_PCT", "휩쏘 회복 허용(분율, 0.001=0.1%)", "float", default=0.001),
],
},
]
@@ -309,10 +492,38 @@ def build_live_config_groups() -> List[GroupDef]:
groups.append({
"id": "strategy_switch",
"title": "전략 ON/OFF",
"hint": "config_{strategy} · 봇 재시작 없이 다음 루프 반영",
"hint": (
"config_{strategy} · STRATEGY_*_ENABLED 는 다음 루프부터 신규매수 중단/재개 "
"(보유 청산은 유지 · WS 구독 해제는 안 함). "
"기동 시 OFF 였던 전략을 켜려면 봇 재시작. "
"해외 WS 소켓은 OVERSEAS_WS_ENABLED(기동 시) · permanent US 구독과 별개."
),
"fields": [
_f(f"STRATEGY_{sid}_ENABLED", LIVE_STRATEGY_LABELS.get(sid, sid), "bool")
for sid in LIVE_STRATEGY_IDS
_f(
"STRATEGY_US_MOMENTUM_ENABLED",
"해외 모멘텀",
"bool",
default=True,
hint="config_us_momentum · permanent US 유니버스 · 소켓 해제 없음",
),
_f(
"OVERSEAS_WS_ENABLED",
"해외 WS(HDFSCNT0)",
"bool",
default=True,
hint="기동 시만 · false=해외 틱 소켓 미기동 · 전략 ON/OFF 와 별개 · 변경 후 재시작",
),
*[
_f(
f"STRATEGY_{sid}_ENABLED",
LIVE_STRATEGY_LABELS.get(sid, sid),
"bool",
default=bool(STRATEGY_ENABLED_DEFAULTS.get(sid, False)),
hint=f"config_{sid.lower()}",
)
for sid in LIVE_STRATEGY_SWITCH_IDS
if sid != "US_MOMENTUM"
],
],
})
@@ -346,7 +557,39 @@ def build_live_config_groups() -> List[GroupDef]:
"hint": "{SID}_UNIVERSE_SOURCE · 기본 키움 · LS는 opt-in",
"fields": univ_fields,
})
groups.append({
"id": "kiwoom_condition_config",
"title": "키움 조건검색 (안정화·회복)",
"hint": "KIWOOM_CNSR* · 유니버스 0종목 공백(Blind Spot) 차단 및 3초 고정 회복",
"fields": [
_f("KIWOOM_CNSRCLR_BEFORE_REQ", "재등록 전 선행 해제(CNSRCLR)", "bool", default=True,
hint="Bye 후 유령 등록(900003) 방지용 선행 클리어"),
_f("KIWOOM_CNSRCLR_SETTLE_SEC", "CNSRCLR 해제 응답 ACK 대기(초)", "float", default=2.0,
hint="해제 응답(rc=0) 완료 대기 최대 타임아웃 (조기 ACK 수신 시 즉시 통과)"),
_f("KIWOOM_CNSRREQ_RETRY_SEC", "미응답/900003 고정 재시도 주기(초)", "float", default=3.0,
hint="지수 백오프 대신 3초 고정 주기로 빠른 회복 시도"),
_f("KIWOOM_CNSRREQ_MAX_RETRIES", "최대 재시도 상한", "int", default=30,
hint="30회(약 90초) 동안 RAM 후보를 보존하며 지속 회복 시도"),
_f("KIWOOM_CNSRCLR_GAP_MIN_SEC", "CNSRCLR 연속 발송 최소 간격(초)", "float", default=0.3),
_f("KIWOOM_CNSRCLR_GAP_MAX_SEC", "CNSRCLR 연속 발송 최대 간격(초)", "float", default=0.6),
_f("KIWOOM_CNSRREQ_GAP_MIN_SEC", "CNSRREQ 연속 발송 최소 간격(초)", "float", default=0.8),
],
})
groups.append({
"id": "ls_condition_config",
"title": "LS 조건검색 (안정화·Diff 보정)",
"hint": "LS_T1859* · 매매 전용이 아닌 조건·시세 전용 이점을 극대화한 1분 주기 0.15초 소생술",
"fields": [
_f("LS_T1859_STALE_RESYNC_SEC", "AFR 침묵 시 소켓 재동기화 주기(초)", "int", default=60,
hint="기본 60초(1분). 매매 주문을 처리하지 않으므로 429 차단 위험 0%! 하루 총 눈감는 시간 약 58초 동안 Delta Diff 1:1 보정"),
_f("LS_T1859_EMPTY_RETRY_SEC", "RAM 0종목 시 t1859 재시드 주기(초)", "int", default=1,
hint="기동 초기 0종목 시 1초마다 재시드 (영구 0종목 공백 록인 방지)"),
_f("LS_T1859_ALLOW_SHRINK", "t1859 축소 전체교체 허용", "bool", default=False,
hint="기본 false (장중 1종목 오류 응답이 20종목 덮어 쓰는 자살골 사고 차단)"),
_f("LS_CONDITION_TR_GAP_SEC", "t1859 REST 호출 최소 간격(초)", "float", default=1.1,
hint="LS 증권 공식 1초 1건 상한 보호 쿨다운"),
],
})
# 전략별 운용한도
budget_fields: List[FieldDef] = []
@@ -453,149 +696,287 @@ def build_live_config_groups() -> List[GroupDef]:
ws_fields.append(
_f(f"{pfx}_WHIPSAW_FILTER_ENABLED", f"{lbl} 휩쏘 필터", "bool"),
)
ws_fields.extend([
_f("TAIL_WHIPSAW_SUBBAR_SEC", "꼬리 휩쏘 미니봉(초)", "int", default=30,
hint="비우면 글로벌 WHIPSAW_SUBBAR_SEC · Optuna 꼬리 TPE 축"),
_f("TAIL_WHIPSAW_LOOKBACK_SEC", "꼬리 휩쏘 룩백(초)", "int", default=90,
hint="비우면 글로벌 WHIPSAW_LOOKBACK_SEC"),
_f("TAIL_WHIPSAW_DIP_PCT", "꼬리 휩쏘 하락폭(분율)", "float", default=0.003,
hint="0.003=0.3% · 비우면 글로벌"),
_f("TAIL_WHIPSAW_RECOVERY_TOL_PCT", "꼬리 휩쏘 회복허용(분율)", "float", default=0.001,
hint="0.001=0.1% · 비우면 글로벌"),
])
groups.append({
"id": "whipsaw_strategy",
"title": "휩쏘 — 전략별 ON/OFF · 꼬리 초/%",
"title": "휩쏘 — 전략별 ON/OFF",
"hint": (
"ON/OFF와 초·%는 별개. 꼬리 Optuna --mode tpe 가 이 축을 탐색. "
"실매 현재값(enabled=false, 30초, 90초, 0.003, 0.001)이 그리드에 포함됨."
"각 전략별로 휩쏘 필터를 켤지 끌지 결정합니다. (글로벌 휩쏘 필터 수치 상속)"
),
"fields": ws_fields,
})
groups.append({
"id": "execution_fill",
"title": "체결 검증 (실전·모의 훈련)",
"hint": "실전은 항상 fill 확인. 모의는 STRICT_FILL_VERIFY=true 시 동일",
"title": "🏦 주문·체결 설정 (실전+모의 공통)",
"hint": (
"⚠️ 현재 모의(mock) 모드로 운영 중. "
"실전 전환 시 STRICT_FILL_VERIFY 는 무관 (실전은 항상 체결 확인). "
"나머지 설정은 모의·실전 모두 동일하게 적용됨."
),
"fields": [
_f("STRICT_FILL_VERIFY", "모의 엄격 체결 검증", "bool", default=False,
hint="모의 실매매만 영향 · ON=체결 API 필수(모의 API 한계로 매수 0건 가능) · 실전은 항상 엄격"),
_f("BUY_DEDUP_PENDING", "매수 미체결 중복주문 방지", "bool", default=True,
hint="동일 전략·종목 PENDING_FILL 있으면 재주문 스킵"),
_f("BACKTEST_SLIP_PCT", "백테 체결 슬리피지(편도 %)", "float", default=0.0,
hint="STRICT_FILL_VERIFY ON 일 때만 백테/파람서치에 적용 · 전 전략 공통 · 0=순수 백테 · 0.2=0.2%"),
_f("BACKTEST_VOL_FILL_CAP_PCT", "백테 체결량 상한(진입봉 거래량×%)", "float", default=0.0,
hint="STRICT_FILL_VERIFY ON 일 때만 · 전 전략 공통 · 0=무제한 · 소형주 IOC 미체결 근사"),
_f("TAIL_BACKTEST_TICK_FALLBACK_OHLC", "⚠ 꼬리 OHLC 폴백 (정합위험·금지)", "bool", default=False,
danger=True,
hint="절대금지: 틱 모드에서 OHLC로 체결·고점 변조. 엔진은 틱 청산 ON 시 무시. Optuna/파람 강제 OFF"),
_f("TAIL_BACKTEST_USE_TICK_EXIT", "꼬리 틱 청산", "bool", default=True,
hint="ON=ws_ticks 시간순 청산(실매 폴링 정합) · OFF=OHLC intrabar"),
_f("TAIL_BACKTEST_POLL_MS", "꼬리 틱청산 폴링(ms)", "int", default=100,
hint="실매 STRATEGY_LOOP_SLEEP≈0.1초 정합 · 하한 50ms"),
_f("MOMENTUM_BACKTEST_TICK_FALLBACK_OHLC", "⚠ 모멘텀 OHLC 폴백 (정합위험·금지)", "bool", default=False,
danger=True,
hint="절대금지 — 틱 청산 ON 시 엔진 무시 · Optuna 강제 OFF"),
_f("MOMENTUM_BACKTEST_POLL_MS", "모멘텀 틱청산 폴링(ms)", "int", default=100,
hint="실매 STRATEGY_LOOP_SLEEP≈0.1초 정합 · 틱 사이 청산 검사 간격 · 하한 50ms"),
_f("BREAKOUT_BACKTEST_USE_TICK_EXIT", "돌파 틱 청산", "bool", default=True,
hint="ON=ws_ticks 시간순 청산 · OFF=OHLC intrabar"),
_f("BREAKOUT_BACKTEST_TICK_FALLBACK_OHLC", "⚠ 돌파 OHLC 폴백 (정합위험·금지)", "bool", default=False,
danger=True,
hint="절대금지 — 틱 진입 ON 시 봉 high 폴백 안 함"),
_f("BREAKOUT_BACKTEST_POLL_MS", "돌파 틱청산 폴링(ms)", "int", default=100,
hint="실매 폴링 정합 · 하한 50ms"),
_f("SCALP_BACKTEST_USE_TICK_EXIT", "스캘핑 틱 청산", "bool", default=True,
hint="ON=ws_ticks 시간순 청산(실매 폴링 정합) · OFF=OHLC intrabar"),
_f("SCALP_BACKTEST_USE_TICK_ENTRY", "스캘핑 틱 진입", "bool", default=True,
hint="ON=예약 체결 시 해당 분 첫 틱가 · OFF=분봉 시가"),
_f("SCALP_BACKTEST_TICK_FALLBACK_OHLC", "⚠ 스캘핑 OHLC 폴백 (정합위험·금지)", "bool", default=False,
danger=True,
hint="절대금지 — 틱 청산 ON 시 엔진 무시"),
_f("SCALP_BACKTEST_POLL_MS", "스캘핑 틱청산 폴링(ms)", "int", default=100,
hint="실매 STRATEGY_LOOP_SLEEP≈0.1초 정합 · 하한 50ms"),
_f("RANGE_BREAK_BACKTEST_USE_TICK_EXIT", "박스돌파 틱 청산", "bool", default=True,
hint="ON=ws_ticks 시간순 청산 · OFF=OHLC intrabar"),
_f("RANGE_BREAK_BACKTEST_TICK_FALLBACK_OHLC", "⚠ 박스돌파 OHLC 폴백 (정합위험·금지)", "bool", default=False,
danger=True,
hint="절대금지 — 틱 청산 ON 시 엔진 무시"),
_f("RANGE_BREAK_BACKTEST_POLL_MS", "박스돌파 틱청산 폴링(ms)", "int", default=100,
hint="실매 폴링 정합 · 하한 50ms"),
_f("DBBAND_BACKTEST_USE_TICK_EXIT", "더블BB 틱 청산", "bool", default=True,
hint="ON=ws_ticks 시간순 청산 · OFF=봉 OHLC 1회"),
_f("DBBAND_BACKTEST_TICK_FALLBACK_OHLC", "⚠ 더블BB OHLC 폴백 (정합위험·금지)", "bool", default=False,
danger=True,
hint="절대금지 — 틱 청산 ON 시 엔진 무시"),
_f("DBBAND_BACKTEST_POLL_MS", "더블BB 틱청산 폴링(ms)", "int", default=100,
hint="실매 폴링 정합 · 하한 50ms"),
_f("TAIL_PARAM_SEARCH_MIN_START", "꼬리 파라서치 최소 시작일", "str", default="2026-07-07",
hint="조건식 변경일 이후만 탐색 (YYYY-MM-DD) · CLI --start 와 max 적용"),
_f("USE_MARKET_IOC", "매수 시장가 IOC", "bool", default=True,
hint="실전 매수 ORD_DVSN=13(IOC) · OFF=01 · 모의는 01 고정"),
_f("USE_MARKET_IOC_SELL", "매도 시장가 IOC", "bool", default=True,
hint="실전 매도 ORD_DVSN=13(IOC) · OFF=01 · 모의는 01 고정 · 거절 많으면 OFF"),
_f("ORDER_FILL_WAIT_SEC", "시장가 체결 대기(초)", "int", default=2),
_f("DUPLICATE_ORDER_FILL_RECOVERY_ENABLED", "주문DB중복 체결복구", "bool", default=True,
hint="insert 실패(주문DB중복) 시 inquire-daily-ccld 1회 → active_trades 복구 · 실패 시에만 REST"),
_f("DUPLICATE_ORDER_RECOVERY_WAIT_SEC", "중복복구 체결대기(초)", "int", default=2,
hint="주문DB중복 복구 전용 · 미설정 시 ORDER_FILL_WAIT_SEC 사용"),
_f("LIMIT_ORDER_FILL_WAIT_SEC", "지정가 체결 대기(초)", "int", default=1),
_f("PENDING_FILL_POLL_INTERVAL_SEC", "미체결 재조회 주기(초)", "int", default=10),
_f("PENDING_FILL_POLL_SEC", "재조회 1회 대기(초)", "int", default=1),
_f("PENDING_POLL_BATCH_FETCH", "미체결 체결 일괄조회", "bool", default=True,
hint="실매 heartbeat 전용 · ON=poll 1회당 inquire-daily-ccld 1건(미체결 N건) · OFF=건별 조회 · 백테 무관"),
_f("PENDING_BUY_MAX_AGE_SEC", "매수 미체결 만료(초)", "int", default=120),
_f("PENDING_SELL_MAX_AGE_SEC", "익절 매도 미체결 만료(초)", "int", default=60),
_f("PENDING_SELL_STOP_MAX_AGE_SEC", "손절 매도 미체결 만료(초)", "int", default=15),
_f("SELL_PENDING_REORDER_ON_EXPIRE", "손절 만료 후 즉시 재매도", "bool", default=True),
_f("AUTO_CANCEL_PARTIAL_BUY_REMAINDER", "부분체결 잔량 자동취소", "bool", default=True),
_f(
"SELL_LIMIT_CANCEL_BEFORE_MARKET_RETRY",
"익절지정가→시장가 전 취소",
"USE_MARKET_IOC",
"⚡ 매수 시장가 IOC (즉시체결 후 미체결 취소)",
"bool",
default=True,
hint=(
"ON=지정가 미확인/부분체결 시 잔여 취소 후 ODNO 재조회, "
"취소실패·미확정이면 시장가 금지(PENDING) · "
"OFF=레거시 즉시 시장가(이중매도 위험) · 전전략 OrderManager 공통"
"ON(권장) = 매수 주문을 시장가로 내고, 즉시 안 채워진 나머지는 자동 취소 (실전만 해당, 모의는 항상 지정가) | "
"OFF = 시장가 보통주문 (미체결이 그냥 남아있음)"
),
),
_f(
"SELL_LIMIT_RECHECK_WAIT_SEC",
"지정가 취소 후 재조회(초)",
"int",
default=1,
hint="SELL_LIMIT_CANCEL_BEFORE_MARKET_RETRY=ON 일 때 ODNO 재조회 대기",
),
_f(
"SELL_LIMIT_CANCEL_FAIL_BALANCE_CONFIRM",
"취소실패+잔고0→매도확정",
"USE_MARKET_IOC_SELL",
"⚡ 매도 시장가 IOC",
"bool",
default=True,
hint=(
"ON=익절지정가 취소 실패·ODNO 미확인이어도 브로커 0주면 "
"지정가 체결로 finalize(유령금지) · OFF=PENDING만"
"ON = 매도도 IOC 시장가 (실전만 해당, 모의는 항상 지정가) | "
"OFF = 시장가 보통주문. 거절(주문거부) 많이 나오면 OFF로"
),
),
_f(
"STRICT_FILL_VERIFY",
"🔍 모의 체결 강제 확인 (모의 전용)",
"bool",
default=False,
hint=(
"★ 모의 모드에서만 의미 있음 (실전은 항상 체결 확인) ★ | "
"ON = 모의에서도 매수 후 체결 API 로 실제 체결 됐는지 확인. "
"모의 API 특성상 확인 실패해서 매수 0건으로 집계될 수 있음 | "
"OFF(기본) = 모의는 주문만 내고 체결됐다고 가정"
),
),
_f(
"BUY_DEDUP_PENDING",
"🚫 동일 종목 매수 중복 방지",
"bool",
default=True,
hint=(
"ON(권장) = 같은 전략·종목의 주문이 아직 체결 대기 중이면 추가 주문 안 냄 | "
"OFF = 중복 주문 허용 (같은 종목을 두 번 살 수 있음)"
),
),
_f(
"ORDER_FILL_WAIT_SEC",
"⏱ 시장가 주문 체결 대기 시간(초)",
"int",
default=2,
hint="주문 낸 후 체결됐는지 확인하기까지 기다리는 시간. 기본 2초.",
),
_f(
"PENDING_BUY_MAX_AGE_SEC",
"⏳ 매수 미체결 주문 포기 시간(초)",
"int",
default=120,
hint=(
"매수 주문을 냈는데 이 시간(초) 안에 체결이 안 되면 포기하고 주문 취소 | "
"기본 120초(2분)"
),
),
_f(
"PENDING_SELL_MAX_AGE_SEC",
"⏳ 익절 매도 미체결 주문 포기 시간(초)",
"int",
default=60,
hint="익절 매도 주문이 이 시간(초) 안에 안 채워지면 포기. 기본 60초.",
),
_f(
"PENDING_SELL_STOP_MAX_AGE_SEC",
"⏳ 손절 매도 미체결 주문 포기 시간(초)",
"int",
default=15,
hint=(
"손절은 빨리 안 팔리면 위험! 이 시간(초) 안에 체결 안 되면 재주문 | "
"기본 15초 (손절은 짧게)"
),
),
_f(
"SELL_PENDING_REORDER_ON_EXPIRE",
"🔄 손절 포기 후 즉시 재주문",
"bool",
default=True,
hint=(
"ON(권장) = 손절 주문이 시간 초과되면 즉시 새로 손절 주문 | "
"OFF = 그냥 포기"
),
),
_f(
"AUTO_CANCEL_PARTIAL_BUY_REMAINDER",
"✂ 매수 일부 체결 후 나머지 자동 취소",
"bool",
default=True,
hint=(
"ON(권장) = 100주 주문했는데 50주만 체결됐으면 나머지 50주 주문 자동 취소 | "
"OFF = 나머지 50주 주문이 그대로 남아있음"
),
),
_f(
"SELL_LIMIT_CANCEL_BEFORE_MARKET_RETRY",
"🔒 익절 지정가 취소 후 시장가 전환",
"bool",
default=True,
hint=(
"ON(권장) = 익절 지정가 매도가 안 팔리면 → 취소 확인 후 → 시장가로 바꿔서 팜. "
"안전하게 이중매도 방지 | "
"OFF = 레거시 방식 (이중매도 위험, 비권장)"
),
),
_f(
"SELL_LIMIT_CANCEL_FAIL_BALANCE_CONFIRM",
"🛡 취소 실패해도 잔고 0이면 매도 완료 처리",
"bool",
default=True,
hint=(
"ON(권장) = 취소 요청이 실패해도 실제 잔고를 보니 0주면 지정가에 팔린 걸로 확정 | "
"→ 유령 포지션(DB엔 있는데 실제론 없는 것) 방지"
),
),
_f(
"GHOST_PURGE_BLOCK_WHILE_PENDING_SELL",
"PENDING매도 시 유령정리 금지",
"👻 매도 주문 대기 중 유령 정리 금지",
"bool",
default=True,
hint=(
"ON=미확정 매도 ODNO 있으면 ghost_purge 대신 체결재조회/잔고확정 청산"
"ON(권장) = 아직 체결 확인 안 된 매도 주문이 있으면 유령(DB만 있는 포지션) 정리 안 함 | "
"'팔리는 중인데 유령이라고 지워버리는' 사고 방지"
),
),
_f(
"DAILY_CCLD_TR_ID",
"일별체결조회 TR_ID",
"BACKTEST_SLIP_PCT",
"📊 백테스트 슬리피지 (백테 전용)",
"float",
default=0.0,
hint=(
"★ 백테스트·파람서치에만 적용, 실제 매매 무관 ★ | "
"0.2 = 매수가에 0.2%, 매도가에서 0.2% 차감해서 현실적인 수익 계산 | "
"0 = 이상적인 체결 가정 (실제보다 성과 좋게 나옴)"
),
),
_f(
"BACKTEST_VOL_FILL_CAP_PCT",
"📊 백테스트 체결량 상한 (백테 전용)",
"float",
default=0.0,
hint=(
"★ 백테스트·파람서치에만 적용 ★ | "
"0 = 무제한 (다 체결됐다고 가정) | "
"예: 20 = 해당 봉 거래량의 20%까지만 체결 가정 (소형주 현실 반영)"
),
),
_f(
"TAIL_BACKTEST_USE_TICK_EXIT",
"📊 꼬리잡기 백테 틱 청산 (백테 전용)",
"bool",
default=True,
hint=(
"★ 백테스트에서만 사용 ★ | "
"ON(권장) = 실제 틱 데이터 시간순으로 손절/익절 판단 (실매매와 동일 방식) | "
"OFF = 봉 OHLC로 판단 (덜 정확)"
),
),
_f(
"TAIL_BACKTEST_TICK_FALLBACK_OHLC",
"⛔ 꼬리잡기 OHLC 폴백 (절대 금지)",
"bool",
default=False,
danger=True,
hint=(
"⛔ 절대 ON 하지 말 것! ⛔ | "
"ON = 틱 데이터로 청산하는 대신 봉 고점/저점으로 체결가 계산 → 실매매와 완전 다른 결과"
),
),
_f(
"TAIL_BACKTEST_POLL_MS",
"📊 꼬리잡기 틱 청산 검사 간격(ms) (백테 전용)",
"int",
default=100,
hint="백테에서 틱을 몇 ms마다 확인할지. 100ms = 0.1초 = 실매매 루프와 동일.",
),
_f(
"TAIL_PARAM_SEARCH_MIN_START",
"📅 꼬리잡기 파라미터 서치 시작일 하한",
"str",
default="",
hint="비우면 모의 VTTC0081R / 실전 TTTC0081R (레거시 *8001R 사용 금지)",
default="2026-07-07",
hint=(
"꼬리잡기 Optuna 파람서치를 이 날짜 이전 데이터로는 탐색 안 함 | "
"조건식 바꾼 날 이후만 탐색하도록. YYYY-MM-DD 형식."
),
),
_f(
"BREAKOUT_BACKTEST_USE_TICK_EXIT",
"📊 돌파 백테 틱 청산 (백테 전용)",
"bool",
default=True,
hint=(
"★ 백테스트에서만 사용 ★ | "
"ON(권장) = 실제 틱 순서로 청산 판단 (실매매 정합)"
),
),
_f(
"BREAKOUT_BACKTEST_TICK_FALLBACK_OHLC",
"⛔ 돌파 OHLC 폴백 (절대 금지)",
"bool",
default=False,
danger=True,
hint="⛔ 절대 ON 하지 말 것! ⛔ | 봉 high/low로 체결가 계산 → 실매와 다른 가짜 결과",
),
_f(
"BREAKOUT_BACKTEST_POLL_MS",
"📊 돌파 틱 청산 검사 간격(ms) (백테 전용)",
"int",
default=100,
hint="백테에서 틱을 몇 ms마다 확인할지. 기본 100ms.",
),
_f(
"MOMENTUM_BACKTEST_TICK_FALLBACK_OHLC",
"⛔ 모멘텀 OHLC 폴백 (절대 금지)",
"bool",
default=False,
danger=True,
hint="⛔ 절대 ON 하지 말 것! ⛔",
),
_f(
"MOMENTUM_BACKTEST_POLL_MS",
"📊 모멘텀 틱 청산 검사 간격(ms) (백테 전용)",
"int",
default=100,
),
_f(
"SCALP_BACKTEST_USE_TICK_EXIT",
"📊 스캘핑 백테 틱 청산 (백테 전용)",
"bool",
default=True,
hint="ON = 실제 틱 순서로 청산 (실매매 정합)",
),
_f(
"SCALP_BACKTEST_USE_TICK_ENTRY",
"📊 스캘핑 백테 틱 진입 (백테 전용)",
"bool",
default=True,
hint=(
"ON = 예약 체결 시 해당 분봉의 첫 틱 가격으로 진입 | "
"OFF = 분봉 시가로 진입"
),
),
_f(
"SCALP_BACKTEST_TICK_FALLBACK_OHLC",
"⛔ 스캘핑 OHLC 폴백 (절대 금지)",
"bool",
default=False,
danger=True,
hint="⛔ 절대 ON 하지 말 것! ⛔",
),
_f(
"SCALP_BACKTEST_POLL_MS",
"📊 스캘핑 틱 청산 검사 간격(ms) (백테 전용)",
"int",
default=100,
),
],
})
groups.append({
"id": "rest_throttle",
"title": "REST 유량 제한 (한투 API)",
@@ -752,42 +1133,79 @@ def build_live_config_groups() -> List[GroupDef]:
groups.append({
"id": "breakout_exit",
"title": "돌파(BREAKOUT) — EOD·시간컷",
"hint": (
"env_config · 저장 후 다음 매도 루프부터 반영(재시작 불필요). "
"EOD OFF 시 당일 15:15 강제청산 없음 — 손절·익절·어깨·시간컷만."
),
"title": "Optuna 청산 파라미터 (돌파)",
"hint": "AI(Optuna)가 최적값을 찾아내는 수익/손실구간 호가매도 파라미터 모음입니다.",
"fields": [
_f("BREAKOUT_EOD_ENABLED", "EOD 당일청산 사용", "bool", default=True,
hint="ON=설정 시각 이후 보유 전량 eod 청산 · OFF=익절·손절·시간컷만"),
_f("BREAKOUT_EOD_HM", "EOD 시각 (HH:MM)", "text", default="15:15",
hint="이 시각 이후 보유 전량 청산 · 예: 15:15"),
_f("BREAKOUT_MAX_HOLD_BARS", "최대보유(분)", "int", default=0,
hint="0=OFF · 진입 후 N분 경과 시 max_hold 청산"),
_f("BREAKOUT_EXIT_OB_ENABLED", "수익구간 호가매도 ON", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=이득+매수잔량 붕괴 시 즉시 탈출"),
_f("BREAKOUT_EXIT_OB_RATIO_MIN", "수익구간 OR_MA 임계", "float", default=0.4,
hint="매수잔량/매도잔량 MA 미만이면 붕괴 · 예: 0.4"),
_f("BREAKOUT_EXIT_OB_MA_WINDOW", "수익구간 OR MA 창", "int", default=5,
hint="OR 이동평균에 쓰는 최근 스냅 수"),
_f("BREAKOUT_EXIT_OB_MIN_PROFIT_PCT", "수익구간 최소이익(분율)", "float", default=0.005,
hint="0.005=+0.5% · 미달이면 호가컷 미발동"),
_f("BREAKOUT_EXIT_OB_MIN_HOLD_BARS", "수익구간 최소보유(분)", "int", default=3,
hint="보유 분(1분봉≈1) 미달이면 미발동"),
_f("BREAKOUT_STOP_OB_ENABLED", "손절호가 ON", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=손해+매수잔량 붕괴 시 하드손절 전 미리 탈출"),
_f("BREAKOUT_STOP_OB_RATIO_MIN", "손절호가 OR_MA 임계", "float", default=0.4,
hint="손실구간 OR_MA 임계"),
_f("BREAKOUT_STOP_OB_MA_WINDOW", "손절호가 OR MA 창", "int", default=5,
hint="손절호가 OR 이동평균 스냅 수"),
_f("BREAKOUT_STOP_OB_MIN_LOSS_PCT", "손절호가 최소손실(분율)", "float", default=0.003,
hint="0.003=0.3% · 이만큼 이상 손해일 때만"),
_f("BREAKOUT_STOP_OB_MIN_HOLD_BARS", "손절호가 최소보유(분)", "int", default=2,
hint="보유 분 미달이면 손절호가 미발동"),
],
})
groups.append({
"id": "momentum_exit",
"title": "모멘텀(MOMENTUM) — EOD",
"hint": "env_config · 저장 후 다음 매도 루프부터 반영(재시작 불필요).",
"title": "Optuna 청산 파라미터 (모멘텀)",
"hint": "AI(Optuna)가 최적값을 찾아내는 수익/손실구간 호가매도 파라미터 모음입니다.",
"fields": [
_f("MOMENTUM_EOD_ENABLED", "EOD 당일청산 사용", "bool", default=True,
hint="ON=설정 시각 이후 보유 전량 장마감청산 · OFF=익절·손절·시간컷만"),
_f("MOMENTUM_EOD_HM", "EOD 시각 (HH:MM)", "text", default="15:20",
hint="정규장 15:30 마감 기준 최소 10분 전 · 순차매도 여유"),
_f("MOMENTUM_EXIT_OB_ENABLED", "수익구간 호가매도 ON", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=이득+매수잔량 붕괴 시 즉시 탈출"),
_f("MOMENTUM_EXIT_OB_RATIO_MIN", "수익구간 OR_MA 임계", "float", default=0.4,
hint="매수잔량/매도잔량 MA 미만이면 붕괴 · 예: 0.4"),
_f("MOMENTUM_EXIT_OB_MA_WINDOW", "수익구간 OR MA 창", "int", default=5,
hint="OR 이동평균에 쓰는 최근 스냅 수"),
_f("MOMENTUM_EXIT_OB_MIN_PROFIT_PCT", "수익구간 최소이익(분율)", "float", default=0.005,
hint="0.005=+0.5% · 미달이면 호가컷 미발동"),
_f("MOMENTUM_EXIT_OB_MIN_HOLD_BARS", "수익구간 최소보유(분)", "int", default=3,
hint="보유 분(1분봉≈1) 미달이면 미발동"),
_f("MOMENTUM_STOP_OB_ENABLED", "손절호가 ON", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=손해+매수잔량 붕괴 시 하드손절 전 미리 탈출"),
_f("MOMENTUM_STOP_OB_RATIO_MIN", "손절호가 OR_MA 임계", "float", default=0.4,
hint="총사÷총팔 MA 미만이면 붕괴 · 예: 0.4"),
_f("MOMENTUM_STOP_OB_MA_WINDOW", "손절호가 OR MA 창", "int", default=5,
hint="OR 이동평균 스냅 수"),
_f("MOMENTUM_STOP_OB_MIN_LOSS_PCT", "손절호가 최소손실(분율)", "float", default=0.003,
hint="0.003=0.3% · 이만큼 이상 손해일 때만"),
_f("MOMENTUM_STOP_OB_MIN_HOLD_BARS", "손절호가 최소보유(분)", "int", default=2,
hint="보유 분 미달이면 미발동"),
],
})
groups.append({
"id": "tail_exit",
"title": "꼬리(SHORT/TAIL) — EOD",
"hint": "env_config · 저장 후 다음 매도 루프부터 반영(재시작 불필요).",
"id": "eod_global",
"title": "🌅 EOD 당일청산 설정 (전략 통합)",
"hint": "각 전략별로 정해진 시각이 되면 보유 물량을 전량 청산(퇴근)합니다.",
"fields": [
_f("TAIL_EOD_ENABLED", "EOD 당일청산 사용", "bool", default=True,
hint="ON=설정 시각 이후 보유 전량 장마감 청산 · OFF=익절·손절·시간컷만"),
_f("TAIL_EOD_HM", "EOD 시각 (HH:MM)", "text", default="15:20",
hint="정규장 15:30 마감 기준 최소 10분 전 · 순차매도 여유"),
_f("BREAKOUT_EOD_ENABLED", "돌파 EOD 사용", "bool", default=True),
_f("BREAKOUT_EOD_HM", "돌파 EOD 시각 (HH:MM)", "text", default="15:15",
hint="예: 15:15 (빠르게 정리)"),
_f("MOMENTUM_EOD_ENABLED", "모멘텀 EOD 사용", "bool", default=True),
_f("MOMENTUM_EOD_HM", "모멘텀 EOD 시각 (HH:MM)", "text", default="15:20",
hint="순차매도 여유분 감안"),
_f("TAIL_EOD_ENABLED", "꼬리잡기 EOD 사용", "bool", default=True),
_f("TAIL_EOD_HM", "꼬리잡기 EOD 시각 (HH:MM)", "text", default="15:20"),
],
})
@@ -819,6 +1237,33 @@ def build_live_config_groups() -> List[GroupDef]:
],
})
groups.append({
"id": "ops_alert",
"title": "운영치명 알림 (봇 내부)",
"hint": (
"실매 프로세스 안에서 토큰/WS/유니버스0/주문거부/유량/VI/MarketGuard PANIC 을 "
"시스템 MM(+TG)로 보냄. journal 오류감시와 별개·보완."
),
"fields": [
_f("OPS_ALERT_ENABLED", "운영알림 ON", "bool", default=True),
_f("OPS_ALERT_MM_CHANNEL", "MM alias", "text", default="",
hint="비우면 KIS_SYSTEM_MM_CHANNEL"),
_f("OPS_ALERT_TG_ON_CRITICAL", "critical 시 TG", "bool", default=True),
_f("OPS_ALERT_COOLDOWN_SEC", "코드별 쿨다운(초)", "int", default=300),
_f("OPS_ALERT_GLOBAL_MIN_GAP_SEC", "전역 최소간격(초)", "int", default=20),
_f("OPS_ALERT_SESSION_START_HM", "장중게이트 시작 HHMM", "int", default=900),
_f("OPS_ALERT_SESSION_END_HM", "장중게이트 종료 HHMM", "int", default=1535),
_f("OPS_ALERT_UNIVERSE_ZERO_SEC", "유니버스0 지속(초)", "int", default=180,
hint="장중 유니버스 0 이 이 시간 이상이면 알림"),
_f("OPS_ALERT_HISTORY_STALE_SEC", "히스토리 공백(초)", "int", default=600,
hint="키움 history 당일 최신 스냅샷이 이보다 오래면 알림"),
_f("OPS_ALERT_ORDER_REJECT_STREAK", "주문거부 연속 N회", "int", default=3),
_f("OPS_ALERT_RATE_LIMIT_STREAK", "유량 연속 N회", "int", default=5),
_f("OPS_ALERT_VI_ENABLED", "VI 발동 알림", "bool", default=True),
_f("OPS_ALERT_MARKET_GUARD_ENABLED", "MarketGuard PANIC 알림", "bool", default=True),
],
})
groups.append({
"id": "portfolio_hold",
"title": "보유·매도 / 고아복구",