- feed_fallback.bar_is_garbage: 봉끝 기준 → 각 틱의 recv_ts wall-clock 기준으로 정정 실매 RAM 3초컷과 동일 논리 → 유동성 낮은 종목 부당 스킵 해소 - candle_garbage_fallback_enabled: 기본 True 복원 (wall-clock 정정 후 안전) - param_search_optuna·run_tail_backtest_cli: CANDLE_GARBAGE_FALLBACK·BACKTEST_USE_RUST 강제 os.environ 세팅 제거 → DB env·CLI 플래그로만 관리 (UI 존중) - WS_TICK_DB_SAVE_LAG_CUT_ENABLED 신설 (bool, 기본 false, 3벤더 공통) OFF=키움/KIS/LS 모든 틱 lag 무관 전부 저장 (벤더 통계·재현·백테 정합) ON=lag > LIVE_FEED_FALLBACK_MAX_AGE_SEC 이면 미저장 (미래 A안) - KIWOOM_TICK_LIVE_MAX_LAG_SEC 완전 폐기 → 위 스위치로 통일 - kiwoom_ws: _skip_persist 로직 새 스위치로 교체 - kis_ws·ls_ws: _skip_persist_kis/_ls 신규 (벤더별 상이했던 정책 통일) - docs/정합성.md §9 신설 (문제·결정·시나리오·향후 A안 전환법) - docs/rust_engine_parity_port_plan.md (신규 설계) 실매 스모크: logs/test_live_execution_validation_20260906_191845.log → 최종: 통과 · 👑 완결 브라우저 검증: http://192.168.0.149:5050/#liveconfig → 새 스위치 노출, JS 오류 없음 영향: 실매(DB 저장 정책 통일, RAM 컷 변경 없음) + 백테/Optuna(실매 정합) Co-authored-by: Cursor <cursoragent@cursor.com>
1657 lines
86 KiB
Python
1657 lines
86 KiB
Python
"""
|
||
kis_trader/web/live_config_schema.py — 실매 운영 설정 웹 탭 필드 정의
|
||
======================================================================
|
||
backtest_web ``/api/live_config`` 가 이 스키마로 폼을 생성·저장한다.
|
||
키는 env/DB 와 1:1 — 하드코딩 임계값 없음.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Dict, List, Optional, Tuple
|
||
|
||
# 웹 탭에 노출할 전략 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",
|
||
"UPDOW",
|
||
)
|
||
|
||
# 전략 ON/OFF 전용 — 해외를 맨 위
|
||
LIVE_STRATEGY_SWITCH_IDS: Tuple[str, ...] = (
|
||
"US_MOMENTUM",
|
||
"SCALP",
|
||
"SHORT",
|
||
"MOMENTUM",
|
||
"BREAKOUT",
|
||
"UPDOW",
|
||
)
|
||
|
||
LIVE_STRATEGY_LABELS: Dict[str, str] = {
|
||
"SCALP": "스캘핑",
|
||
"SHORT": "꼬리잡기",
|
||
"MOMENTUM": "모멘텀",
|
||
"US_MOMENTUM": "해외 모멘텀",
|
||
"BREAKOUT": "돌파",
|
||
"RANGE_BREAK": "박스권돌파",
|
||
"UPDOW": "UPDOWN 박스",
|
||
"DBBAND": "더블BB",
|
||
"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": {
|
||
"TOTAL_BUDGET_KRW": "TAIL_TOTAL_BUDGET_KRW",
|
||
"SLOT_MONEY": "TAIL_SLOT_MONEY",
|
||
"MAX_STOCKS": "TAIL_MAX_STOCKS",
|
||
},
|
||
}
|
||
|
||
# UI 조회 시 이전 키·별칭 폴백 (표시만 — 저장은 canonical)
|
||
BUDGET_READ_FALLBACKS: Dict[str, Tuple[str, ...]] = {
|
||
"TAIL_SLOT_MONEY": (
|
||
"SLOT_MONEY_DEFAULT",
|
||
"SHORT_SLOT_MONEY",
|
||
"MAX_BUY_AMOUNT_PER_STOCK",
|
||
),
|
||
"TAIL_TOTAL_BUDGET_KRW": ("SHORT_TOTAL_BUDGET_KRW",),
|
||
"TAIL_MAX_STOCKS": ("SHORT_MAX_STOCKS", "MAX_STOCKS"),
|
||
}
|
||
|
||
# TAIL canonical 저장 시 실매 루프가 읽는 별칭에 동기화
|
||
TAIL_BUDGET_SAVE_MIRROR: Dict[str, str] = {
|
||
"TAIL_SLOT_MONEY": "SLOT_MONEY_DEFAULT",
|
||
"TAIL_MAX_STOCKS": "SHORT_MAX_STOCKS",
|
||
"TAIL_TOTAL_BUDGET_KRW": "SHORT_TOTAL_BUDGET_KRW",
|
||
}
|
||
|
||
# type: bool | int | float | pct | text
|
||
FieldDef = Dict[str, Any]
|
||
GroupDef = Dict[str, Any]
|
||
|
||
|
||
def _f(key: str, label: str, ftype: str = "float", **extra: Any) -> FieldDef:
|
||
d: FieldDef = {"key": key, "label": label, "type": ftype}
|
||
d.update(extra)
|
||
return d
|
||
|
||
|
||
def _sid_key(sid: str, suffix: str) -> str:
|
||
aliases = STRATEGY_BUDGET_ENV_KEY.get(sid.upper(), {})
|
||
if suffix in aliases:
|
||
return aliases[suffix]
|
||
return f"{sid}_{suffix}"
|
||
|
||
|
||
def read_snap_value(snap: Dict[str, Any], key: str, ftype: str) -> Any:
|
||
"""canonical 키 + 폴백 키 순으로 UI 값 해석."""
|
||
keys = (key,) + BUDGET_READ_FALLBACKS.get(key, ())
|
||
for k in keys:
|
||
v = snap_value_to_ui(snap.get(k), ftype)
|
||
if v is not None:
|
||
return v
|
||
return None
|
||
|
||
|
||
def expand_live_config_save_patch(patch: Dict[str, str]) -> Dict[str, str]:
|
||
"""TAIL_* 저장 시 tail_catch·BaseStrategy 가 읽는 별칭 키 동기화."""
|
||
out = dict(patch)
|
||
for src, dst in TAIL_BUDGET_SAVE_MIRROR.items():
|
||
if src in out and dst not in out:
|
||
out[dst] = out[src]
|
||
return out
|
||
|
||
|
||
def build_live_config_groups() -> List[GroupDef]:
|
||
"""정적 그룹 + 전략 반복 그룹 메타."""
|
||
groups: List[GroupDef] = [
|
||
{
|
||
"id": "daily_profit_global",
|
||
"title": "일일 익절 — 총합 (규칙·수동중단)",
|
||
"hint": (
|
||
"손익 감시 ON/OFF 는 전략별(*_DAILY_PROFIT_TARGET_ENABLED)만 사용. "
|
||
"여기 마스터는 총합 목표금액/%·수동 매수잠금·알림·트레일 규칙. "
|
||
"① 신규매수 수동 중단 = 목표·손익과 무관 매수만 잠금(긴급용, 기본 OFF). "
|
||
"매도·손절은 항상 유지"
|
||
),
|
||
"fields": [
|
||
_f("DAILY_PROFIT_TARGET_KRW", "목표 금액(원)", "int", default=0),
|
||
_f("DAILY_PROFIT_TARGET_PCT", "목표 수익률(%)", "float", default=0,
|
||
hint="퍼센트 그대로 (예: 2.5 = 2.5%) · 0=미사용"),
|
||
_f(
|
||
"DAILY_PROFIT_TARGET_BUDGET_KRW",
|
||
"분모 운용한도(원)",
|
||
"int",
|
||
default=0,
|
||
hint="0 = ON 전략 TOTAL_BUDGET 합 자동",
|
||
),
|
||
_f(
|
||
"DAILY_PROFIT_HALT_NEW_BUYS",
|
||
"신규매수 수동 중단",
|
||
"bool",
|
||
default=False,
|
||
hint=(
|
||
"목표·손익과 무관한 수동 매수 잠금. "
|
||
"ON=즉시 신규매수 전면 차단 / OFF=정상 (기본)"
|
||
),
|
||
),
|
||
_f("DAILY_PROFIT_NOTIFY_MM", "달성 시 MM 알림", "bool", default=True),
|
||
_f("DAILY_PROFIT_MODE", "익절 방식", "text", default="fixed",
|
||
hint="fixed=고정목표 / trailing=고점대비 되돌림 / both=둘 중 먼저"),
|
||
_f("DAILY_PROFIT_TRAIL_TIERS", "다단 트레일(수익원:컷%,…)", "text", default="",
|
||
hint="벌수록 타이트하게 사수 · 예: 30000:50,100000:35,300000:25 · 비우면 단일 되돌림 사용"),
|
||
_f("DAILY_PROFIT_TRAIL_DROP_PCT", "단일 트레일 되돌림(%, 고점대비)", "float", default=0,
|
||
hint="다단 비었을 때만 · 예: 35 = 고점 대비 -35% 꺾이면 중단 · 0=미사용"),
|
||
_f("DAILY_PROFIT_TRAIL_ARM_KRW", "트레일 발동 최소수익(원)", "int", default=0,
|
||
hint="이 이상 벌어야 트레일 감시 시작 · 0=트레일 비활성(과민발동 방지)"),
|
||
_f("DAILY_PROFIT_TRAIL_ARM_PCT", "트레일 발동 최소수익(%)", "float", default=0,
|
||
hint="운용한도 대비 % · KRW와 둘 중 큰 값"),
|
||
],
|
||
},
|
||
{
|
||
"id": "risk_global",
|
||
"title": "리스크 — 계정 공통",
|
||
"hint": "env_config",
|
||
"fields": [
|
||
_f("DAILY_STOP_LOSS_PCT", "일일 손실 한도(분율, -0.05=-5%)", "float", default=0),
|
||
_f("CONSECUTIVE_LOSS_LIMIT", "연속 손실 제한(회)", "int", default=0),
|
||
_f("USE_RISK_CHECK", "리스크 체크 사용", "bool"),
|
||
_f("EXCLUDE_NON_STOCK", "비본주(우선주·ETF·ETN) 매수 제외", "bool", default=True,
|
||
hint="후보 name==code 여도 코드 끝 5/7/9·K/L… 로 우선주 차단"),
|
||
_f("NON_STOCK_PREF_CODE_SUFFIXES", "우선주 코드 끝자리(숫자)", "str", default="5,7,9",
|
||
hint="KRX 구형 우선주. 콤마 구분"),
|
||
_f("NON_STOCK_EXCLUDE_ALPHA_SUFFIX", "코드 끝 알파벳=우선주 제외", "bool", default=True,
|
||
hint="00088K·00781K 등"),
|
||
],
|
||
},
|
||
{
|
||
"id": "ws_feed_settings",
|
||
"title": "📡 시세 소스 설정 (봇 재시작 필요)",
|
||
"hint": (
|
||
"키움과 KIS 중 어느 쪽 시세·호가로 매매할지 선택. "
|
||
"분봉 저장은 증권사별(source=kiwoom|kis, channel=ws|rest|rollup)로 분리. "
|
||
"갭보정은 키움 REST만. 영구구독 KR은 LS WS."
|
||
),
|
||
"fields": [
|
||
_f("LIVE_TICK_PROVIDER", "📊 실매매 틱(시세) 소스 (재시작 필요)", "select", default="kiwoom",
|
||
choices=[
|
||
{"value": "kiwoom", "label": "키움 (kiwoom)"},
|
||
{"value": "kis", "label": "한투 (kis)"},
|
||
],
|
||
hint=(
|
||
"1차 RAM. 키움=kiwoom / 한투=kis. LS는 여기 없음(3차 spill). "
|
||
"폴백 나이 넘으면 실패로 보고 2차(상대 체결소켓)→3차(LS)→매도는 4차 키움 REST(ka10007). "
|
||
"한투 60초 캐시는 안 씀. MINIMAL 이면 한투 RAM 은 보유만. "
|
||
"저장 후 kis_trader_main 재시작"
|
||
)),
|
||
_f("LIVE_OB_PROVIDER", "📊 실매매 호가(OB) 소스 (재시작 필요)", "select", default="kis",
|
||
choices=[
|
||
{"value": "kis", "label": "한투 (kis)"},
|
||
{"value": "kiwoom", "label": "키움 (kiwoom)"},
|
||
],
|
||
hint=(
|
||
"호가 1차. 한투=kis(2키 OB 세션 41) / 키움=kiwoom. LS는 여기 없음(3차 UH1). "
|
||
"폴백 나이 넘으면 그 벤더는 없는 것과 같고 2차→3차. "
|
||
"만료로 None 만 만들어서 필터 ON 통과 구멍을 만들지 않음. "
|
||
"kis 선택 시 아래 'KIS 실시간 호가 수집' ON + 2번째 앱키(KIS_APP_KEY_OB_REAL). "
|
||
"기본 체인: kis→kiwoom→ls. 저장 후 kis_trader_main 재시작"
|
||
)),
|
||
_f("LIVE_FEED_FALLBACK_MAX_AGE_SEC", "⏱ 시세·호가 읽기 폴백 나이(초)", "float", default=3.0,
|
||
hint=(
|
||
"틱: 체결시각(키움 FID20 / 한투·LS chetime) vs 서버 지금 → 초과면 RAM 미반영. "
|
||
"호가: snap_time(한투 BSOP_HOUR / LS hotime / 키움 FID20)도 동일 초로 "
|
||
"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)·현재가·틱타임·"
|
||
"호가 bid/ask/잔량비·스냅시각을 붙임. OFF=접두 T/O/R 만. 재시작 없이 반영(수 초)"
|
||
)),
|
||
_f("BT_FEED_DETAIL_LOG", "📝 옵투나/백테에도 틱·호가 벤더 추적", "bool", default=True,
|
||
hint=(
|
||
"ON=옵투나·백테 로그에 틱/호가 로드 벤더 비율 + TRIGGER 샘플 "
|
||
"(실매 TRIGGER_FEED_DETAIL_LOG 과 같은 축). OFF=샘플만 끔(비율 INFO는 유지)"
|
||
)),
|
||
_f("BT_FEED_DETAIL_LOG_MAX", "옵투나 TRIGGER 샘플 상한(건)", "int", default=40,
|
||
hint="프로세스당 매수 후보 상세 샘플 최대. 0=샘플 없음. 기본 40"),
|
||
_f("CANDLE_GARBAGE_FALLBACK", "🕯 분봉 쓰레기→2차·3차 봉 통째", "bool", default=True,
|
||
hint=(
|
||
"ON(기본)=그 분 메인 틱이 0건이거나 전부 봉끝 대비 읽기나이 초과면 "
|
||
"그 봉을 구멍으로 보고 2차 WS→LS 봉 통째. 한 봉 안 혼합 롤업 없음. "
|
||
"실매 링에 그 분이 안 들어오면 판정 불가→기존 봉 유지"
|
||
)),
|
||
_f("WS_TICK_SAVE_KIWOOM", "📈 키움 체결 틱 수집", "bool", default=True,
|
||
hint="ON = 키움 체결 틱을 ws_ticks 테이블에 저장 (기본값: ON)"),
|
||
_f("KIWOOM_TICK_TIME_MAX_LAG_SEC", "⏱ 키움 체결시간(FID20) 동결 한도(초)", "int", default=120,
|
||
hint=(
|
||
"같은 FID20 가 이 초 이상 안 움직일 때만 아침 시계멈춤으로 보고 "
|
||
"RAM 유지·tick_time 을 수신시각으로 보정. "
|
||
"밀린 줄의 같은 초 연속 체결(수십 ms)은 동결이 아님"
|
||
)),
|
||
_f("WS_TICK_DB_SAVE_LAG_CUT_ENABLED", "⏱ DB 적재 시 지연 틱 컷 (3벤더 공통)", "bool", default=False,
|
||
danger=True,
|
||
hint=(
|
||
"OFF(기본)=키움/KIS/LS 모든 벤더의 틱을 lag 무관 전부 ws_ticks 에 저장. "
|
||
"벤더별 지연 통계·재현·백테 정합에 필수. "
|
||
"ON=lag(체결시각 vs 지금) > LIVE_FEED_FALLBACK_MAX_AGE_SEC(기본 3초) 인 틱은 미저장. "
|
||
"통계를 보고 확신 서면 ON — 매매 RAM 컷과 저장 컷 정합. "
|
||
"이전 KIWOOM_TICK_LIVE_MAX_LAG_SEC 대체 (2026-09-06)"
|
||
)),
|
||
_f("KIWOOM_TICK_STALE_LOG_GAP_SEC", "⏱ 키움 stale skip 로그 간격(초)", "float", default=30.0,
|
||
hint="종목당 stale skip 경고 최소 간격"),
|
||
_f("KIWOOM_WS_REG_REFRESH", "🧪 키움 REG refresh (테스트)", "int", default=1,
|
||
hint=(
|
||
"1(기본)=기존 구독 유지. 0=LOGIN 직후 일괄 REG 첫 청크만 그룹 구독 해지 후 재등록. "
|
||
"장중 종목 추가 REG는 항상 1. 장중 상시 0 비추(catch-up 재점화). "
|
||
"실매 반영은 kis_trader_main 재시작"
|
||
)),
|
||
_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=True,
|
||
danger=True,
|
||
hint=(
|
||
"한투 호가는 2번째 앱키(KIS_APP_KEY_OB_REAL) 전용 세션만. "
|
||
"메인 시세 41과 합산 금지. 2키 없으면 ON 해도 호가 구독·적재 안 함. "
|
||
"2키 세션 한도는 호가 전용 41. LIVE_OB_PROVIDER=kis 일 때 ON 권장."
|
||
)),
|
||
_f("WS_SUBSCRIBE_KIS_MINIMAL", "📉 KIS 최소 구독 모드", "bool", default=False,
|
||
hint=(
|
||
"한투 소켓에 누구를 넣을지. LIVE_TICK_PROVIDER 와 무관. "
|
||
"OFF(기본)=한투도 후보·보유를 키움과 같이 구독(세션 41). "
|
||
"ON=한투는 보유만, 후보는 키움. 영구구독 KR은 LS"
|
||
)),
|
||
_f("WS_SUBSCRIBE_SPILL", "📡 구독초과/실패 즉시 벤더 spill", "bool", default=True,
|
||
hint=(
|
||
"ON(기본)=한도초과·구독실패·해당 WS 미연결 시 대기 없이 다음 벤더로 구독 "
|
||
"(틱/호가 체인 기본: LIVE_* 1차 → 상대 → ls 3차 RAM). "
|
||
"OFF=예전처럼 거절/패스. 기존 LIVE_TICK/OB_PROVIDER 값은 바꾸지 않음."
|
||
)),
|
||
_f("WS_TICK_SUBSCRIBE_CHAIN", "📡 틱 구독 spill 체인(csv)", "text", default="",
|
||
hint="비우면 LIVE_TICK_PROVIDER→나머지→ls. 예: kiwoom,kis,ls"),
|
||
_f("WS_OB_SUBSCRIBE_CHAIN", "📡 호가 구독 spill 체인(csv)", "text", default="kis,kiwoom,ls",
|
||
hint="기본 kis,kiwoom,ls. 비우면 LIVE_OB_PROVIDER→나머지→ls(3차 RAM)."),
|
||
_f("LS_WS_ENABLED", "📡 LS WS 기동 (재시작 필요)", "bool", default=False,
|
||
hint=(
|
||
"ON = LS증권 실시간 WS 기동 — 구독 spill 3차·영구구독·읽기 폴백 RAM. "
|
||
"OFF(기본)=VALIDATION/조건이력/ls_condition 전략이 있을 때만 기동. "
|
||
"키: env_config LS_APP_KEY_REAL / LS_APP_SECRET_REAL 필요."
|
||
)),
|
||
_f("LS_FEED_FALLBACK_SUBSCRIBE", "📡 LS 유니버스 RAM 미러 (레거시)", "bool", default=False,
|
||
hint=(
|
||
"OFF(기본)=KIS·키움과 동일 spill 3차만. ON=후보∪보유∪grace 전체를 LS에 "
|
||
"벌크 미러(_feed_fallback) — 200+ REG·07:00 Bye 위험. 영구는 별도(_permanent)"
|
||
)),
|
||
_f("LS_WS_HOLD_RAM_KEEP_HOLDINGS", "📡 LS hold RAM 보유 유지", "bool", default=True,
|
||
hint=(
|
||
"ON(기본)=장마감 hold 시 영구+보유 종목만 LS RAM 유지(07:00 replay 축소). "
|
||
"OFF=영구(_permanent)만 유지"
|
||
)),
|
||
_f("LS_WS_MAX_SUBSCRIPTIONS", "📡 LS 로컬 구독 한도", "int", default=100,
|
||
hint="spill 3차 LS 종목 상한. 0=무제한(비권장). 키움 100과 유사."),
|
||
_f("LS_WS_TICK_SAVE", "📈 LS 체결 틱 DB 적재", "bool", default=False,
|
||
hint=(
|
||
"ON=구독 중 종목(영구∪후보∪보유∪grace) US3 → ls_ws_ticks. "
|
||
"호가 DB 게이트와 동일. OFF=테이블 미적재(RAM·3차 폴백은 구독 ON이면 유지). "
|
||
"봉·VI 는 영구만. 옵투나 틱 3차는 BT_TICK_LS_THIRD_FALLBACK"
|
||
)),
|
||
_f("BT_TICK_LS_THIRD_FALLBACK", "📈 백테/옵투나 틱 3차 LS", "bool", default=True,
|
||
hint=(
|
||
"ON(기본)=ws_ticks 로드 후 같은 초에 1·2차 없으면 ls_ws_ticks 채움. "
|
||
"나이=LIVE_FEED_FALLBACK_MAX_AGE_SEC(기본3). history_source=ls 본체와 별개"
|
||
)),
|
||
_f("LS_WS_ALSO_HOGA", "📋 LS 호가(UH1) 구독 (재시작 필요)", "bool", default=True,
|
||
hint=(
|
||
"ON(기본)=LS 종목 구독 시 체결과 같이 UH1 호가도 받음. "
|
||
"OFF면 호가 RAM·ls_ws_orderbook 이 비어 3차 호가가 없음. "
|
||
"적재 스위치와 별개 — 둘 다 ON"
|
||
)),
|
||
_f("LS_WS_ORDERBOOK_SAVE", "📋 LS 호가 DB 적재", "bool", default=True,
|
||
hint=(
|
||
"ON(기본)=LS 구독 종목 호가를 ls_ws_orderbook 에 쌓음(영구만이 아님). "
|
||
"옵투나 호가 3차. tick 모드=체결 1건당 1장. "
|
||
"LS_WS_TICK_SAVE OFF 여도 이 스위치만 ON 이면 호가는 쌓임. "
|
||
"위 UH1 구독이 OFF 면 쌓을 사진이 없음"
|
||
)),
|
||
_f("LS_GAP_FILL_CANDIDATES", "⛔ 후보 LS 갭 REST (t8412)", "bool", default=False,
|
||
hint=(
|
||
"OFF(기본)=폴백 구독만, 후보 전 종목 t8412 금지. "
|
||
"영구 갭은 LS_GAP_FILL_ENABLED"
|
||
)),
|
||
_f("PERMANENT_SUBSCRIBE_ENABLED", "📡 영구구독 마스터 (재시작 필요)", "bool", default=True,
|
||
hint=(
|
||
"ON(기본)=테이블 enabled 종목을 KR=LS WS / US=해외 WS 로 구독. "
|
||
"OFF=행은 유지하고 구독만 안 함. LS 틱/봉 DB는 enabled 코드만."
|
||
)),
|
||
_f("WS_GAP_FILL_KIS_FALLBACK", "⛔ KIS REST 갭보정 (쓰지 말 것)", "bool", default=False,
|
||
hint=(
|
||
"페이지 REST라 느림. 코드가 ON이어도 호출하지 않음. "
|
||
"후보 갭보정은 키움 ka10080 → ws_candles(source=kiwoom,channel=rest)만."
|
||
)),
|
||
_f("PERM_LS_FILL_BARS", "영구구독 확정봉 가져오기 봉수", "int", default=500,
|
||
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",
|
||
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": "💾 TRIGGER 스냅샷 저장 (분석용 · 매수 차단 아님)",
|
||
"hint": (
|
||
"매수 검토 순간을 DB에 기록할지 설정. "
|
||
"여기 ON/OFF는 주문을 막지 않습니다. "
|
||
"진입 차단은 아래 「진입 호가필터」「프로그램 매매 필터」「휩쏘」를 보세요."
|
||
),
|
||
"fields": [
|
||
_f(
|
||
"KIWOOM_WS_PROGRAM_ENABLED",
|
||
"🤖 키움 프로그램 매매 데이터 수신",
|
||
"bool",
|
||
default=True,
|
||
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=30.0,
|
||
hint=(
|
||
"0D 덤프·시계열 저장용. 이 초보다 오래된 호가는 저장 안 함. "
|
||
"진입 필터 판정 나이와 무관(아래 FILTER_MAX_AGE). 코드 기본 30초"
|
||
),
|
||
),
|
||
_f(
|
||
"WS_ORDERBOOK_FILTER_MAX_AGE_SEC",
|
||
"⏱ 진입 호가필터 RAM 나이(초)",
|
||
"float",
|
||
default=0.0,
|
||
hint=(
|
||
"0(기본)=마지막 호가로 검사하되, 벤더가 폴백 나이 넘으면 그 사진만 버리고 2차·3차를 봄. "
|
||
"만료=None 으로 필터 ON 통과 구멍을 만들지 않음. REST 없음"
|
||
),
|
||
),
|
||
_f(
|
||
"WS_PRICE_MAX_AGE_SEC",
|
||
"⏱ WS 현재가 조회 나이(초)",
|
||
"float",
|
||
default=0.0,
|
||
hint=(
|
||
"0(기본)=읽기 폴백 나이(LIVE_FEED_FALLBACK, 기본 3초)로 메인→2차→3차. "
|
||
"양수=그 초와 폴백 나이 중 짧은 쪽. None 인자만 마지막 RAM. "
|
||
"매도 4차 REST 는 1·2·3 실패 시에만(키움 ka10007, 쿨다운)"
|
||
),
|
||
),
|
||
_f(
|
||
"SELL_WS_STALE_REST_SEC",
|
||
"⏱ 매도 죽은선 REST(초)",
|
||
"float",
|
||
default=1.0,
|
||
hint=(
|
||
"1·2·3차 WS 가 모두 없거나 2초 넘을 때 키움 ka10007 1회. "
|
||
"한투 inquire_price(60초 캐시) 안 씀. 0=REST 안 함. "
|
||
"종목당 쿨다운은 아래 간격. 매수 루프 전 종목 REST 금지"
|
||
),
|
||
),
|
||
_f(
|
||
"SELL_WS_STALE_REST_COOLDOWN_SEC",
|
||
"⏱ 매도 REST 확인 간격(초)",
|
||
"float",
|
||
default=30.0,
|
||
hint="죽은선 REST를 종목당 이 초마다 1회. 유량 보호. 코드 기본 30",
|
||
),
|
||
_f(
|
||
"SELL_WS_LAST_RAM_MAX_AGE_SEC",
|
||
"⏱ 매도 last-RAM 허용나이(초)",
|
||
"float",
|
||
default=30.0,
|
||
hint=(
|
||
"3초 체인 실패 시 마지막 WS RAM을 이 초까지 매도에 사용. "
|
||
"FID20 동결(패킷나이 초과)은 거부. 0=last-RAM 안 씀. "
|
||
"그 다음 직전가 캐시 → 키움 ka10007"
|
||
),
|
||
),
|
||
_f(
|
||
"WS_ORDERBOOK_FILTER_REJECT_IF_EMPTY",
|
||
"⛔ 호가 사진 없으면 매수 안 함",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"ON(기본)=사기 직전 호가창 사진이 한 장도 없으면 이번엔 안 삼 "
|
||
"(다음 검사에서 사진 있으면 다시 봄) | "
|
||
"OFF=예전처럼 사진 없어도 통과(눈 가리고 사기). REST 없음"
|
||
),
|
||
),
|
||
_f(
|
||
"WS_ORDERBOOK_COLLECT_ENABLED",
|
||
"📸 매수 검토 순간 호가창 1장 저장",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"ON = 매수 검토 순간 호가창(10호가 잔량 등) 스냅샷도 DB에 저장 | "
|
||
"→ 분석용 (용량 주의) · 진입 호가필터 ON/OFF와 별개"
|
||
),
|
||
),
|
||
_f(
|
||
"WS_PROGRAM_COLLECT_ENABLED",
|
||
"📸 매수 검토 순간 프로그램 매매 1장 저장",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"ON = 매수 검토 순간 프로그램 매매 스냅샷도 DB에 저장 | "
|
||
"→ 분석용 · 프로그램 필터 차단 스위치와 별개"
|
||
),
|
||
),
|
||
_f(
|
||
"WS_PROGRAM_SAVE_ENABLED",
|
||
"📼 프로그램 매매 연속 시계열 저장",
|
||
"bool",
|
||
default=False,
|
||
hint=(
|
||
"ON = 프로그램 매매 데이터를 계속 DB에 쌓음 | "
|
||
"평소엔 OFF 권장, 변경 후 봇 재시작 필요"
|
||
),
|
||
),
|
||
],
|
||
},
|
||
{
|
||
"id": "program_entry_filter",
|
||
"title": "🚫 프로그램 매매 필터 (진입 차단)",
|
||
"hint": (
|
||
"기관·외국인 프로그램 수급이 나쁠 때 매수를 막는 스위치. "
|
||
"진입 호가필터·휩쏘·EXIT_OB(매도 호가)와 전부 별개."
|
||
),
|
||
"fields": [
|
||
_f(
|
||
"PROGRAM_FILTER_ENABLED",
|
||
"🚫 프로그램 매매 필터 (매수 차단)",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"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(
|
||
"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분 이상 끊겨야 알람 (순간 재연결은 무시)"
|
||
),
|
||
),
|
||
],
|
||
},
|
||
]
|
||
|
||
# 호가필터 — 전략별 (글로벌 ORDERBOOK_* 수치 폐기)
|
||
from kis_trader.engine.orderbook_env import (
|
||
OB_DEFAULT_ASK_WALL_MAX_QTY,
|
||
OB_DEFAULT_ENTRY_ASK_MAX_MULT,
|
||
OB_DEFAULT_ENTRY_BID_DEPTH_MULT,
|
||
OB_DEFAULT_ENTRY_BID_LEVELS,
|
||
OB_DEFAULT_FILTER_ENABLED,
|
||
OB_DEFAULT_MAX_SPREAD_PCT,
|
||
OB_DEFAULT_MIN_BID_ASK_RATIO,
|
||
OB_STRATEGY_PREFIXES,
|
||
)
|
||
ob_fields: List[FieldDef] = []
|
||
_ob_lbl = {
|
||
"SCALP": "스캘핑",
|
||
"TAIL": "꼬리잡기",
|
||
"MOMENTUM": "모멘텀",
|
||
"BREAKOUT": "돌파",
|
||
}
|
||
for pfx in OB_STRATEGY_PREFIXES:
|
||
lbl = _ob_lbl.get(pfx, pfx)
|
||
ob_fields.extend([
|
||
_f(
|
||
f"{pfx}_ORDERBOOK_FILTER_ENABLED",
|
||
f"{lbl} 진입 호가필터 ON (매수 차단)",
|
||
"bool",
|
||
default=OB_DEFAULT_FILTER_ENABLED,
|
||
hint=(
|
||
"ON=스프레드·잔량비·얇은호가·매도벽으로 매수 차단 | "
|
||
"OFF=진입은 안 막음(수치 보관만). "
|
||
"EXIT_OB/STOP_OB(매도 호가익절·손절)와 무관 · 휩쏘·프로그램 필터와도 무관"
|
||
),
|
||
),
|
||
_f(
|
||
f"{pfx}_ORDERBOOK_MAX_SPREAD_PCT",
|
||
f"{lbl} 스프레드상한(%)",
|
||
"float",
|
||
default=OB_DEFAULT_MAX_SPREAD_PCT,
|
||
hint="진입 필터용 · 0.45=0.45% · 그리드 max_spread_pct 앵커",
|
||
),
|
||
_f(
|
||
f"{pfx}_ORDERBOOK_MIN_BID_ASK_RATIO",
|
||
f"{lbl} 매수/매도잔량비 하한",
|
||
"float",
|
||
default=OB_DEFAULT_MIN_BID_ASK_RATIO,
|
||
hint="진입 필터용 · 그리드 min_bid_ask_ratio",
|
||
),
|
||
_f(
|
||
f"{pfx}_ORDERBOOK_ENTRY_ASK_MAX_MULT",
|
||
f"{lbl} 매도벽 허용배수",
|
||
"float",
|
||
default=OB_DEFAULT_ENTRY_ASK_MAX_MULT,
|
||
hint="진입 필터용 · 필요수량×N · 그리드 ask_max_mult",
|
||
),
|
||
_f(
|
||
f"{pfx}_ORDERBOOK_ENTRY_BID_DEPTH_MULT",
|
||
f"{lbl} 매수호가깊이 배수",
|
||
"float",
|
||
default=OB_DEFAULT_ENTRY_BID_DEPTH_MULT,
|
||
hint="진입 필터용 · 얇은호가 탈락 · 필요수량×N",
|
||
),
|
||
_f(
|
||
f"{pfx}_ORDERBOOK_ENTRY_BID_LEVELS",
|
||
f"{lbl} 호가단계(N호가)",
|
||
"int",
|
||
default=OB_DEFAULT_ENTRY_BID_LEVELS,
|
||
hint="진입 필터용 · N호가 잔량 합산",
|
||
),
|
||
])
|
||
if pfx == "BREAKOUT":
|
||
ob_fields.append(
|
||
_f(
|
||
"BREAKOUT_ORDERBOOK_ASK_WALL_MAX_QTY",
|
||
"돌파 매도벽 상한(주)",
|
||
"int",
|
||
default=OB_DEFAULT_ASK_WALL_MAX_QTY,
|
||
hint="진입 필터용 · 그리드 ask_wall_max_qty",
|
||
),
|
||
)
|
||
groups.append({
|
||
"id": "orderbook_strategy",
|
||
"title": "🚫 진입 호가필터 (매수 차단) — 전략별",
|
||
"hint": (
|
||
"호가창 스프레드·잔량·벽으로 매수만 막음. "
|
||
"끄면(OFF) 진입 차단만 해제 · 매도 쪽 EXIT_OB/STOP_OB는 아래 "
|
||
"「매도 호가익절·손절」카드에서 따로 킴/끔. "
|
||
"휩쏘(틱 패턴)·프로그램 필터와도 별개."
|
||
),
|
||
"fields": ob_fields,
|
||
})
|
||
|
||
groups.append({
|
||
"id": "strategy_switch",
|
||
"title": "전략 ON/OFF",
|
||
"hint": (
|
||
"config_{strategy} · STRATEGY_*_ENABLED 는 다음 루프부터 신규매수 중단/재개 "
|
||
"(보유 청산은 유지 · WS 구독 해제는 안 함). "
|
||
"기동 시 OFF 였던 전략을 켜려면 봇 재시작. "
|
||
"해외 WS 소켓은 OVERSEAS_WS_ENABLED(기동 시) · permanent US 구독과 별개."
|
||
),
|
||
"fields": [
|
||
_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=해외 틱 소켓 미기동 · 영구구독 US 시세/알람용(전략 OFF여도 ON 가능) · 변경 후 재시작",
|
||
),
|
||
*[
|
||
_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"
|
||
],
|
||
],
|
||
})
|
||
|
||
# 유니버스 소스 — ranking | condition | kiwoom_condition | ls_condition
|
||
_univ_choices = [
|
||
{"value": "kiwoom_condition", "label": "키움 조건(WS)"},
|
||
{"value": "ls_condition", "label": "LS 조건(AFR)"},
|
||
{"value": "condition", "label": "한투 조건(REST)"},
|
||
{"value": "ranking", "label": "거래량순위"},
|
||
]
|
||
univ_fields: List[FieldDef] = []
|
||
for sid in LIVE_STRATEGY_IDS:
|
||
lbl = LIVE_STRATEGY_LABELS.get(sid, sid)
|
||
univ_fields.append(
|
||
_f(
|
||
f"{sid}_UNIVERSE_SOURCE",
|
||
f"{lbl} 유니버스",
|
||
"select",
|
||
default="kiwoom_condition",
|
||
choices=_univ_choices,
|
||
hint=(
|
||
"ls_condition=LS 동명 AFR · 시세는 키움/한투 유지 · "
|
||
"매니저 최초 기동은 봇 재시작 필요할 수 있음 · "
|
||
"키움으로 자동 폴백 안 함"
|
||
),
|
||
),
|
||
)
|
||
groups.append({
|
||
"id": "universe_source",
|
||
"title": "유니버스 소스",
|
||
"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] = []
|
||
for sid in LIVE_STRATEGY_IDS:
|
||
lbl = LIVE_STRATEGY_LABELS.get(sid, sid)
|
||
budget_fields.extend([
|
||
_f(_sid_key(sid, "TOTAL_BUDGET_KRW"), f"{lbl} 총 한도(원)", "int"),
|
||
_f(_sid_key(sid, "SLOT_MONEY"), f"{lbl} 슬롯(원)", "int"),
|
||
_f(_sid_key(sid, "MAX_STOCKS"), f"{lbl} 동시 종목", "int"),
|
||
])
|
||
groups.append({
|
||
"id": "strategy_budget",
|
||
"title": "전략별 운용한도",
|
||
"hint": "config_{strategy}",
|
||
"fields": budget_fields,
|
||
})
|
||
|
||
# 전략별 일일 익절 서브
|
||
dp_fields: List[FieldDef] = []
|
||
for sid in LIVE_STRATEGY_IDS:
|
||
lbl = LIVE_STRATEGY_LABELS.get(sid, sid)
|
||
dp_fields.extend([
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_TARGET_ENABLED"),
|
||
f"{lbl} 손익 감시 켜기",
|
||
"bool",
|
||
hint="OFF = 해당 전략 일일익절 비활성 (매수 차단 없음)",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_TARGET_KRW"),
|
||
f"{lbl} 목표(원)",
|
||
"int",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_TARGET_PCT"),
|
||
f"{lbl} 목표(%)",
|
||
"float",
|
||
hint="퍼센트 그대로 (예: 2.5 = 2.5%) · 0=미사용",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_MODE"),
|
||
f"{lbl} 익절방식",
|
||
"text",
|
||
hint="비우면 마스터 상속 · fixed/trailing/both",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_TRAIL_TIERS"),
|
||
f"{lbl} 다단 트레일",
|
||
"text",
|
||
hint="수익원:컷%,… · 비우면 마스터 상속 · 예: 30000:50,100000:35,300000:25",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_TRAIL_DROP_PCT"),
|
||
f"{lbl} 단일 트레일 되돌림(%)",
|
||
"float",
|
||
hint="다단 비었을 때만 · 고점 대비 % · 비우면 마스터 상속 · 0=미사용",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_TRAIL_ARM_KRW"),
|
||
f"{lbl} 트레일 발동수익(원)",
|
||
"int",
|
||
hint="이 이상 벌어야 트레일 감시 · 비우면 마스터 상속",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_HALT_NEW_BUYS"),
|
||
f"{lbl} 신규매수 수동 중단",
|
||
"bool",
|
||
default=False,
|
||
hint="목표 무관 수동 잠금 · 비우면 마스터 상속 · OFF=정상",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_RISK_BUDGET_ENABLED"),
|
||
f"{lbl} 리스크버짓(B)",
|
||
"bool",
|
||
default=False,
|
||
hint="ON=일일익절 hit 후 보유최악손절합>cushion 이면 전량정리 · 기본 OFF · 마스터 없음",
|
||
),
|
||
_f(
|
||
_sid_key(sid, "DAILY_PROFIT_RISK_BUDGET_COOLDOWN_SEC"),
|
||
f"{lbl} 리스크버짓 쿨다운(초)",
|
||
"int",
|
||
default=300,
|
||
hint="같은 날 재평가 최소 간격 · 0=당일 1회만",
|
||
),
|
||
])
|
||
groups.append({
|
||
"id": "daily_profit_strategy",
|
||
"title": "일일 익절 — 전략별 (서브)",
|
||
"hint": (
|
||
"전략별 손익만 따로 감시. 비어 있으면 마스터 값 상속. "
|
||
"손익 감시 ON이면 목표 달성 시 해당 전략 신규매수 자동 차단. "
|
||
"리스크버짓(B)은 전략별만(기본 OFF)."
|
||
),
|
||
"fields": dp_fields,
|
||
})
|
||
|
||
# 전략별 휩쏘 ON/OFF + 꼬리 초·% (Optuna TPE 축과 동일 키)
|
||
ws_fields: List[FieldDef] = []
|
||
for sid in LIVE_STRATEGY_IDS:
|
||
lbl = LIVE_STRATEGY_LABELS.get(sid, sid)
|
||
pfx = "TAIL" if sid == "SHORT" else sid
|
||
ws_fields.append(
|
||
_f(f"{pfx}_WHIPSAW_FILTER_ENABLED", f"{lbl} 휩쏘 필터", "bool"),
|
||
)
|
||
groups.append({
|
||
"id": "whipsaw_strategy",
|
||
"title": "휩쏘 — 전략별 ON/OFF (진입 차단)",
|
||
"hint": (
|
||
"전략별 휩쏘(틱 급락→급반등) 진입 차단 ON/OFF. "
|
||
"수치는 위 「휩쏘 필터」 글로벌. "
|
||
"진입 호가필터·프로그램 필터·EXIT_OB와 별개."
|
||
),
|
||
"fields": ws_fields,
|
||
})
|
||
|
||
groups.append({
|
||
"id": "execution_fill",
|
||
"title": "🏦 주문·체결 설정 (실전+모의 공통)",
|
||
"hint": (
|
||
"⚠️ 현재 모의(mock) 모드로 운영 중. "
|
||
"실전 전환 시 STRICT_FILL_VERIFY 는 무관 (실전은 항상 체결 확인). "
|
||
"나머지 설정은 모의·실전 모두 동일하게 적용됨."
|
||
),
|
||
"fields": [
|
||
_f(
|
||
"USE_MARKET_IOC",
|
||
"⚡ 매수 시장가 IOC (즉시체결 후 미체결 취소)",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"ON(권장) = 매수 주문을 시장가로 내고, 즉시 안 채워진 나머지는 자동 취소 (실전만 해당, 모의는 항상 지정가) | "
|
||
"OFF = 시장가 보통주문 (미체결이 그냥 남아있음)"
|
||
),
|
||
),
|
||
_f(
|
||
"USE_MARKET_IOC_SELL",
|
||
"⚡ 매도 시장가 IOC",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"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·매도가능=0·시장 미체결 매도=0 일 때만 지정가 체결로 확정 | "
|
||
"매도가능만 0인 경우는 아직 안 팔린 것 → 확정 안 함"
|
||
),
|
||
),
|
||
_f(
|
||
"GHOST_PURGE_BLOCK_WHILE_PENDING_SELL",
|
||
"👻 매도 주문 대기 중 유령 정리 금지",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"ON(권장) = 아직 체결 확인 안 된 매도 주문이 있으면 유령(DB만 있는 포지션) 정리 안 함 | "
|
||
"→ '팔리는 중인데 유령이라고 지워버리는' 사고 방지"
|
||
),
|
||
),
|
||
_f(
|
||
"INQUIRE_PSBL_RVSECNCL_BEFORE_GHOST",
|
||
"📋 유령정리 전 정정취소가능 조회",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"ON(권장) = 실전은 한투 정정취소가능주문조회, 모의는 당일체결 미체결로 "
|
||
"시장에 남은 매도를 본 뒤에만 유령 정리 | "
|
||
"매도주문 나가면 매도가능=0 이 정상. 그걸 0주=없다 로 지우지 않음 | "
|
||
"OFF = 예전처럼 잔고 맵만 봄 (비권장)"
|
||
),
|
||
),
|
||
_f(
|
||
"PSBL_RVSECNCL_CACHE_TTL_SEC",
|
||
"📋 정정취소가능 조회 캐시(초)",
|
||
"int",
|
||
default=30,
|
||
hint="같은 초 안에 종목마다 REST 연타 방지. sell_locked retry 시 캐시 hit.",
|
||
),
|
||
_f(
|
||
"CANCELABLE_CCLD_MAX_PAGES",
|
||
"📋 모의 cancelable daily-ccld 최대 페이지",
|
||
"int",
|
||
default=3,
|
||
hint="모의투자 cancelable 조회 시 daily-ccld 페이지 상한. 기본 3 (유량·지연 보호).",
|
||
),
|
||
_f(
|
||
"SELL_LOCKED_ENQUEUE_COOLDOWN_SEC",
|
||
"⏸ sell_locked enqueue 쿨다운(초)",
|
||
"int",
|
||
default=20,
|
||
hint=(
|
||
"sell_locked/sellable_zero/pending 시 tick·scan 재 enqueue 대기. "
|
||
"REST 폭주 방지 (SELL_FAILURE_BACKOFF 와 별개)."
|
||
),
|
||
),
|
||
_f(
|
||
"CANCELABLE_RECONCILE_ENABLED",
|
||
"🔄 cancelable_open DB↔브로커 reconcile",
|
||
"bool",
|
||
default=True,
|
||
hint="ON = DB CANCELLED vs 브로커 미체결 open 불일치 시 자동 조사·복구 시도.",
|
||
),
|
||
_f(
|
||
"CANCELABLE_RECONCILE_CANCEL_RETRY",
|
||
"🔄 reconcile 시 cancel_order 재시도",
|
||
"bool",
|
||
default=True,
|
||
hint="reconcile 중 브로커 미체결 ODNO 에 cancel_order 1회 재시도.",
|
||
),
|
||
_f(
|
||
"PSBL_RVSECNCL_MAX_PAGES",
|
||
"📋 정정취소가능 최대 페이지",
|
||
"int",
|
||
default=5,
|
||
hint="한 페이지 최대 50건. 5페이지면 250건. REST 유량 보호 상한.",
|
||
),
|
||
_f(
|
||
"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="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)",
|
||
"hint": (
|
||
"한투 유량(계좌·앱키 단위): 실전 18건/초 · 모의 1건/초 · "
|
||
"신규 앱키는 첫 3일 3건/초 후 자동 상향 · 토큰발급 1건/초 · "
|
||
"동시호출 100~150ms 텀 권장. "
|
||
"※ 모의: 거래는 모의앱키(1건/초), 시세·랭킹·조건검색은 실전앱키(18건/초)로 분리. "
|
||
"운영(실전) 전환 시 거래+시세가 한 앱키로 합쳐져 18건/초를 공유."
|
||
),
|
||
"fields": [
|
||
_f("KIS_MIN_INTERVAL_SEC_MOCK", "모의 REST 최소간격(초)", "float", default=0.22,
|
||
hint="모의 도메인(거래: 주문·체결·잔고) · 조회·랭킹·조건은 실키라 무관 · 429 로그 없으면 0.22 유지 · 한투 공지 1건/초 엄수 필요 시 1.0 · 재시작 없이 반영(1초 TTL)"),
|
||
_f("KIS_MIN_INTERVAL_SEC_REAL", "실전 REST 최소간격(초)", "float", default=0.12,
|
||
hint="실전 도메인(시세 · 운영 시 거래+조회+취소+재주문 통합 한 앱키) · 18건/초 한도 → 0.12=약8건/초 · 신규앱키 첫 3일은 0.34(3건/초) · 재시작 없이 반영"),
|
||
_f("KIS_MIN_INTERVAL_SEC", "공통 폴백 최소간격(초)", "float", default=0.22,
|
||
hint="위 도메인별 키 미설정 시 폴백"),
|
||
_f("BALANCE_MAX_PAGES", "잔고 연속조회 최대 페이지", "int", default=20,
|
||
hint="잔고조회는 1페이지에 실전 50종목/모의 20종목만 내려옴. 보유가 많으면 다음 페이지를 이어 조회(누적 병합). 20p=최대 약 1000종목. 보유 종목이 50개를 넘으면 필수 — 부족하면 51번째부터 매도 검증에서 누락됨"),
|
||
],
|
||
})
|
||
|
||
groups.append({
|
||
"id": "scan_loop_sleep",
|
||
"title": "스캔 루프 회전율 (WS 따라가기)",
|
||
"hint": (
|
||
"REST 유량과 별개 — 전략 매수체크 루프의 sleep. 짧을수록 WS 실시간 신호를 빨리 잡지만 CPU↑. "
|
||
"탈락 경로는 REST 0건(WS·DB·연산만)이라 줄여도 한투 유량 무관. 재시작 없이 반영. "
|
||
"비우면 전략별 기본값(loop 0.1초) 사용."
|
||
),
|
||
"fields": [
|
||
_f("SCAN_REJECT_SLEEP_MIN", "탈락 후 sleep 최소(초)", "float", default=0.2,
|
||
hint="check_buy 탈락 종목마다 · REST 0건 · 0.05~0.1 로 낮추면 한 바퀴 대폭 단축"),
|
||
_f("SCAN_REJECT_SLEEP_MAX", "탈락 후 sleep 최대(초)", "float", default=0.5),
|
||
_f("SCAN_BUY_OK_SLEEP_MIN", "매수성공 후 sleep 최소(초)", "float", default=1.0,
|
||
hint="1루프 1매수 후 대기 · 포지션 과집중 방지"),
|
||
_f("SCAN_BUY_OK_SLEEP_MAX", "매수성공 후 sleep 최대(초)", "float", default=2.0),
|
||
_f("SCAN_BUY_FAIL_SLEEP_MIN", "주문실패 후 sleep 최소(초)", "float", default=0.3),
|
||
_f("SCAN_BUY_FAIL_SLEEP_MAX", "주문실패 후 sleep 최대(초)", "float", default=0.8),
|
||
_f("STRATEGY_LOOP_SLEEP_MIN", "루프 끝 sleep 최소(초)", "float", default=0.1,
|
||
hint="한 바퀴 끝 대기 · WS 구독 캐시 폴링 · 0.1=실매 반응 빠르게"),
|
||
_f("STRATEGY_LOOP_SLEEP_MAX", "루프 끝 sleep 최대(초)", "float", default=0.1),
|
||
_f("LOOP_PROFILE_ENABLED", "루프 숙제 ms 계측", "bool", default=False,
|
||
hint="ON=한 바퀴 단계별·락 wait/hold·check_buy 합/max 로그 · 병목 확정용 · 기본 OFF"),
|
||
_f("LOOP_PROFILE_EVERY_N", "계측 샘플 주기(바퀴)", "int", default=1,
|
||
hint="N바퀴마다 1회 상세 로그 · 1=매바퀴 · 로그 폭주 시 5~10"),
|
||
_f("LOOP_PROFILE_LOG_PATH", "계측 로그 파일", "str", default="logs/loop_profile.log",
|
||
hint="상대경로=레포루트 기준 · 비우면 전략 logger INFO 만"),
|
||
_f("CHECK_BUY_PROF_MIN_MS", "check_buy 상세 로그 하한(ms)", "int", default=500,
|
||
hint="이 ms 이상인 check_buy 만 [CHECK_BUY_PROF] 단계별 기록 · LOOP_PROFILE_ENABLED 필요"),
|
||
_f("HOLDINGS_DB_SYNC_INTERVAL_SEC", "보유 DB sync 안전망(초)", "int", default=60,
|
||
hint="루프마다 get_active_trades 대신 N초마다만 · 매수/매도 체결은 RAM 즉시 · "
|
||
"0=매루프(레거시) · 재시작·지정가체결은 강제 sync"),
|
||
_f("TODAY_TRADES_CACHE_TTL_SEC", "당일거래 RAM TTL(초)", "float", default=1.0,
|
||
hint="check_buy·일일한도 가드 공유 · N초 내 DB 재조회 생략 · 체결 시 즉시 무효화 · 0=매번 DB"),
|
||
_f("STRATEGY_ENV_CACHE_TTL_SEC", "전략설정 RAM TTL(초)", "float", default=1.0,
|
||
hint="config_* reload 매루프 SELECT 완화 · 기본 1초 · 웹 저장 시 즉시 무효화 · "
|
||
"유니버스/봉 분슬롯과 무관(임계값만)"),
|
||
_f("TICK_SELL_ENABLED", "틱매도(공통)", "bool", default=False,
|
||
hint="ON=WS 현재가 갱신마다 매도검사(백테 틱청산에 근접) · OFF=루프(~0.1초)만 · "
|
||
"전략별 *_TICK_SELL_ENABLED 가 비어있지 않으면 그쪽 우선 · 기본 OFF. "
|
||
"주문은 TICK_SELL_ASYNC_PLACE(기본 ON) 이면 워커"),
|
||
_f("TICK_SELL_MIN_INTERVAL_MS", "틱매도 최소간격(ms)", "int", default=50,
|
||
hint="같은 종목 연속 틱 폭주 시 매도검사 하한 · 백테 poll≈50~100ms 정합"),
|
||
_f("TICK_SELL_ASYNC_PLACE", "틱매도 주문 비동기", "bool", default=True,
|
||
hint="ON(기본)=틱 수신 스레드에서는 신호만 큐. 잔고·주문 REST는 워커. "
|
||
"OFF=예전처럼 틱 콜백에서 place()까지 동기(시세 일시정지)"),
|
||
],
|
||
})
|
||
|
||
# 전략별 틱매도 스위치 (비어 있으면 공통 TICK_SELL_ENABLED)
|
||
tick_sell_fields: List[FieldDef] = []
|
||
for sid in LIVE_STRATEGY_IDS:
|
||
lbl = LIVE_STRATEGY_LABELS.get(sid, sid)
|
||
tick_sell_fields.append(
|
||
_f(f"{sid}_TICK_SELL_ENABLED", f"{lbl} 틱매도", "bool", default=False,
|
||
hint="비우면 공통 TICK_SELL_ENABLED · true/false 명시 시 전략만 덮어씀 · 기본 false"),
|
||
)
|
||
groups.append({
|
||
"id": "tick_sell_by_strategy",
|
||
"title": "전략별 틱매도 (선택)",
|
||
"hint": (
|
||
"매도 규칙은 기존 check_sell_signals 그대로 · 호출 시점만 WS 틱. "
|
||
"실매↔백테 청산 타이밍 정합용 · 기본 OFF 권장(검증 후 ON)."
|
||
),
|
||
"fields": tick_sell_fields,
|
||
})
|
||
|
||
# 전략별 후보 하드캡 (WS 100 구독 한도 안전 + 매수체크 회전율)
|
||
cand_fields: List[FieldDef] = []
|
||
for sid in LIVE_STRATEGY_IDS:
|
||
lbl = LIVE_STRATEGY_LABELS.get(sid, sid)
|
||
cand_fields.append(
|
||
_f(f"{sid}_CAND_LIMIT", f"{lbl} 후보 상한", "int", default=0,
|
||
hint="0=무제한 · 조건검색/랭킹이 N개 줘도 상위 N개만 매수체크·WS구독 · "
|
||
"전 전략 합산이 키움 WS 100종 한도를 넘지 않게 조절"),
|
||
)
|
||
groups.append({
|
||
"id": "candidate_limit",
|
||
"title": "전략별 후보 상한 (WS 100 구독 분배)",
|
||
"hint": (
|
||
"env_config · 키움 WS 실시간 구독은 계좌당 100종목이 한도. "
|
||
"전략별 후보 상한 합 + 보유 + grace ≤ 100. "
|
||
"영구구독 KR은 LS(키움 100·한투 41에 안 넣음). "
|
||
"UPDOW 는 추가로 watchlist(UPDOWN_WATCH_MAX) 안에서만 유지됨."
|
||
),
|
||
"fields": cand_fields,
|
||
})
|
||
|
||
# UPDOWN 박스권 전략 — SCAN/판별/진입/청산 파라미터 (env_config UPDOWN_BOX_*)
|
||
groups.append({
|
||
"id": "updown_box",
|
||
"title": "UPDOWN 박스권 — 판별·진입·손절 스택",
|
||
"hint": (
|
||
"env_config · 횡보(박스권) 종목을 하단에서 줍고 상단/익절·박스이탈·패닉·래칫으로 청산. "
|
||
"파라서치(param_search_updown_box) 1위와 동일 키 · 재시작 없이 다음 루프 반영."
|
||
),
|
||
"fields": [
|
||
_f("UPDOWN_SCAN_INTERVAL_SEC", "SCAN 주기(초)", "int", default=300,
|
||
hint="조건검색→박스필터→watchlist 충전 주기 · 박스는 느리게 변하므로 300(5분) 권장"),
|
||
_f("UPDOWN_SCAN_TF_MIN", "분봉(분)", "int", default=15,
|
||
hint="박스 판별·진입에 쓰는 분봉 · 수집/백테/파라서치와 동일해야 함"),
|
||
_f("UPDOWN_WATCH_MAX", "관심종목 최대 수", "int", default=30,
|
||
hint="sticky watchlist 최대 보유 · REST 1콜 30종(intstock-multprice)과 정합"),
|
||
_f("UPDOWN_BOX_LOOKBACK_BARS", "박스 lookback(봉)", "int", default=20,
|
||
hint="박스 상·하단 산출에 쓸 최근 봉 수"),
|
||
_f("UPDOWN_BOX_RANGE_MAX_PCT", "박스 최대 높이(%)", "float", default=8.0,
|
||
hint="(상단-하단)/하단 · 이보다 넓으면 박스 아님(변동성 과대)"),
|
||
_f("UPDOWN_BOX_BB_BW_MAX", "밴드폭 상한(%)", "float", default=12.0,
|
||
hint="볼린저 밴드폭(스퀴즈) · 작을수록 횡보 · 초과 시 박스 아님"),
|
||
_f("UPDOWN_BOX_MA_SLOPE_TOL_PCT", "MA 기울기 허용(%)", "float", default=0.5,
|
||
hint="|MA 기울기| 이내여야 추세없음(횡보) 인정"),
|
||
_f("UPDOWN_BOX_NEAR_LOW_PCT", "하단 근접 진입(%)", "float", default=2.0,
|
||
hint="현재가가 박스 하단에서 이 % 이내로 내려오면 매수 후보(싸게 줍기)"),
|
||
_f("UPDOWN_BOX_BODY_DROP_MIN_PCT", "직전봉 음봉 최소(%)", "float", default=0.2,
|
||
hint="되돌림 확인 · 직전봉 음봉 몸통 하락률 하한 · 0=무시"),
|
||
_f("UPDOWN_BOX_TP_PCT", "익절(%)", "float", default=3.0,
|
||
hint="진입가 대비 · 박스 상단 도달 기대치 근방"),
|
||
_f("UPDOWN_BOX_STOP_LOSS_PCT", "하드 손절(%)", "float", default=2.5,
|
||
hint="진입가 대비 · 박스이탈·패닉이 안 걸릴 때 최종 방어"),
|
||
_f("UPDOWN_BOX_BREACH_BUFFER_PCT", "박스이탈 여유(%)", "float", default=0.3,
|
||
hint="현재가 < 하단×(1-여유%) 면 구조 붕괴 즉시 손절 · 노이즈 이탈 방지"),
|
||
_f("UPDOWN_PANIC_FROM_HIGH_PCT", "패닉 손절(고점대비 %)", "float", default=3.0,
|
||
hint="보유 중 고점 대비 이 % 급락 시 무조건 긴급컷(진입가 무관)"),
|
||
_f("UPDOWN_BOX_SHOULDER_MIN_HIGH_PCT", "래칫 무장(+%)", "float", default=1.0,
|
||
hint="고점이 진입가 대비 +이 % 도달해야 어깨컷(트레일) 무장"),
|
||
_f("UPDOWN_BOX_SHOULDER_CUT_PCT", "래칫 컷(고점대비 -%)", "float", default=1.0,
|
||
hint="무장 후 고점 대비 이 % 꺾이면 청산(어깨컷)"),
|
||
_f("UPDOWN_BOX_RATCHET_TIERS", "다단 래칫(수익%:컷%,…)", "text", default="",
|
||
hint="TAIL·BREAKOUT 동일 형식. 예 '2:1.5,5:1.0' = +2%부터 고점-1.5%컷, +5%부터 -1.0%컷. "
|
||
"비우면 OFF(위 단일 어깨컷 사용)"),
|
||
_f("UPDOWN_BOX_MAX_HOLD_BARS", "보유 한도(봉)", "int", default=64,
|
||
hint="이만큼 봉이 지나면 무조건 청산"),
|
||
_f("UPDOWN_FEED_MODE", "피드 모드", "text", default="rest",
|
||
hint="ws=키움 실시간(100한도 공유) / rest=한투 30배치 폴링 · watchlist 30종이면 rest 권장"),
|
||
_f("UPDOWN_REST_POLL_SEC", "REST 폴링 주기(초)", "int", default=5,
|
||
hint="rest 모드에서 현재가 스냅샷 갱신 주기"),
|
||
],
|
||
})
|
||
|
||
groups.append({
|
||
"id": "breakout_exit",
|
||
"title": "📉 매도 호가익절·손절 (EXIT_OB/STOP_OB) — 돌파",
|
||
"hint": (
|
||
"보유 중 청산만. 진입 호가필터(*_ORDERBOOK_FILTER_ENABLED)를 꺼도 "
|
||
"여기 ON이면 익절/손절 호가매도는 그대로 동작. "
|
||
"꼬리·스캘프는 스위치 없음."
|
||
),
|
||
"fields": [
|
||
_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": "📉 매도 호가익절·손절 (EXIT_OB/STOP_OB) — 모멘텀",
|
||
"hint": (
|
||
"보유 중 청산만. 진입 호가필터를 꺼도 여기 ON이면 그대로 동작. "
|
||
"돌파 카드와 동일 개념 · 꼬리·스캘프 없음."
|
||
),
|
||
"fields": [
|
||
_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": "eod_global",
|
||
"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 (빠르게 정리)"),
|
||
_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"),
|
||
],
|
||
})
|
||
|
||
groups.append({
|
||
"id": "error_watch_mm",
|
||
"title": "오류감시 → Mattermost",
|
||
"hint": (
|
||
"별도 프로세스 scripts/kis_error_watch_mm.py 가 journalctl -f 로 "
|
||
"kis_trader_main 을 감시 → Traceback/유닛다운 시 MM. 실매 봇과 분리."
|
||
),
|
||
"fields": [
|
||
_f("ERROR_WATCH_ENABLED", "오류감시 ON", "bool", default=True,
|
||
hint="감시 프로세스 시작 시 읽음 · OFF면 감시자 즉시 종료"),
|
||
_f("ERROR_WATCH_UNIT", "감시 systemd 유닛", "text",
|
||
default="kis_trader_main.service",
|
||
hint="journalctl -u 대상"),
|
||
_f("ERROR_WATCH_MM_CHANNEL", "알림 MM alias", "text", default="",
|
||
hint="비우면 KIS_SYSTEM_MM_CHANNEL(default=키스명령봇)"),
|
||
_f("ERROR_WATCH_COOLDOWN_SEC", "동일오류 쿨다운(초)", "int", default=180,
|
||
hint="같은 Traceback 지문 재알림 간격"),
|
||
_f("ERROR_WATCH_HEALTH_CHECK_SEC", "유닛생존 점검(초)", "int", default=60,
|
||
hint="systemctl is-active 주기"),
|
||
_f("ERROR_WATCH_TRACEBACK_EXTRA_LINES", "Traceback 추가줄", "int", default=12,
|
||
hint="Traceback 이후 스택 수집 줄 수"),
|
||
_f("ERROR_WATCH_STARTUP_NOTIFY", "감시자 기동 MM", "bool", default=True,
|
||
hint="감시 프로세스 시작 시 1회 알림"),
|
||
_f("ERROR_WATCH_MM_JITTER", "MM jitter", "bool", default=False,
|
||
hint="오류 알림은 즉시 권장(OFF)"),
|
||
],
|
||
})
|
||
|
||
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": "보유·매도 / 고아복구",
|
||
|
||
"hint": (
|
||
"보유·매도 탭 일괄매도·수동 보호·장마감 후 DB 동기화. "
|
||
"MANUAL_HOLD_CODES 에 넣은 종목은 일괄매도·고아복구 모두 제외."
|
||
),
|
||
"fields": [
|
||
_f("MANUAL_HOLD_CODES", "수동 보유 보호 종목", "text", default="",
|
||
hint="쉼표(,) 구분 종목코드 예: 005930,000660 · HTS 직접 매수·장기 보유분 보호 · 일괄매도·고아복구 제외"),
|
||
_f("BULK_SELL_INTERVAL_SEC", "일괄매도 종목 간격(초)", "float", default=0.3,
|
||
hint="미등록 봇고아 일괄 시장가매도 시 REST 429 방지 · 종목마다 sleep"),
|
||
_f("ORPHAN_RECONCILE_ENABLED", "장마감 고아복구", "bool", default=True,
|
||
hint="매일 15:36~16:00 1회 · 실계좌 잔고↔active_trades 대조 · orders BUY 있는 미기록분만 복구 · 수동·MANUAL_HOLD 제외"),
|
||
_f("ORPHAN_RECONCILE_ON_STARTUP", "기동 시 잔고=DB 맞춤", "bool", default=True,
|
||
hint="봇 시작 직후 1회 · 매도 없음 · 고아복구+유령삭제+수량/평단 sync · 전략 쓰레드보다 먼저 · OFF 시 웹 「잔고=DB 맞추기」만"),
|
||
_f("ORPHAN_RECONCILE_PRE_EOD_ENABLED", "Pre-EOD 고아복구", "bool", default=True,
|
||
hint="활성 전략 중 가장 이른 EOD − N분 1회 · EOD 청산 전 active_trades 반영"),
|
||
_f("ORPHAN_RECONCILE_PRE_EOD_LEAD_MIN", "Pre-EOD 선행(분)", "int", default=7,
|
||
hint="예: BREAKOUT EOD 15:15 → 15:08 실행 · REST 잔고 1회/일"),
|
||
_f("ORPHAN_SKIP_AFTER_SELL_HISTORY", "청산히스토리 있으면 재등록금지", "bool", default=True,
|
||
hint="ON=매수 이후 장마감청산 등 히스토리 있으면 고아복구로 active 다시 안 넣음"),
|
||
_f("ORPHAN_SKIP_SELL_REASONS", "재등록금지 청산사유", "str", default="장마감청산,eod",
|
||
hint="쉼표 구분 키워드. 비우면 매수 이후 모든 청산 히스토리 금지"),
|
||
_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 삭제 · **현재 KIS_MOCK=is_mock 행만** (모의↔실전 교차삭제 금지) · Pre/Post/기동"),
|
||
_f(
|
||
"GHOST_PURGE_RECORD_HISTORY",
|
||
"유령정리 시 거래내역 기록",
|
||
"bool",
|
||
default=True,
|
||
hint=(
|
||
"ON=유령 삭제 시 trade_history 에 sell_reason=ghost_purge "
|
||
"(realized_pnl=0) · OFF=삭제만 · 전전략 공통"
|
||
),
|
||
),
|
||
_f("INTRADAY_HOLDINGS_DRIFT_ENABLED", "장중 qty 드리프트 감시", "bool", default=False,
|
||
hint="기본 OFF · ON=N분마다 잔고 REST · 알림/자동복구는 별도 키"),
|
||
_f("INTRADAY_HOLDINGS_DRIFT_INTERVAL_SEC", "드리프트 조회주기(초)", "int", default=300,
|
||
hint="INTRADAY_HOLDINGS_DRIFT_ENABLED=ON 일 때만"),
|
||
_f("INTRADAY_HOLDINGS_DRIFT_AUTO_RECOVER", "드리프트 자동복구", "bool", default=False,
|
||
hint="OFF=알림만 · ON=active_trades qty 보정(기본 OFF)"),
|
||
],
|
||
})
|
||
|
||
# 전략 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
|
||
|
||
|
||
def all_live_config_keys() -> List[str]:
|
||
keys: List[str] = []
|
||
for g in build_live_config_groups():
|
||
for f in g.get("fields") or []:
|
||
k = f.get("key")
|
||
if k:
|
||
keys.append(str(k))
|
||
return keys
|
||
|
||
|
||
def snap_value_to_ui(raw: Any, ftype: str) -> Any:
|
||
"""DB 문자열 → 웹 입력값."""
|
||
if raw is None or str(raw).strip() in ("", "None"):
|
||
return None
|
||
s = str(raw).strip().split("#", 1)[0].strip()
|
||
if ftype == "bool":
|
||
return s.lower() in ("1", "true", "yes", "on")
|
||
try:
|
||
if ftype == "int":
|
||
return int(float(s))
|
||
if ftype in ("float", "pct"):
|
||
v = float(s)
|
||
if ftype == "pct" and 0 < abs(v) < 0.5:
|
||
return round(abs(v) * 100, 4)
|
||
return round(abs(v), 6) if ftype == "pct" else v
|
||
except (ValueError, TypeError):
|
||
return None
|
||
return s
|
||
|
||
|
||
def ui_value_to_db(val: Any, ftype: str) -> str:
|
||
"""웹 입력 → DB 저장 문자열."""
|
||
if ftype == "bool":
|
||
if val in (True, "true", "1", 1, "on", "yes"):
|
||
return "true"
|
||
return "false"
|
||
if val is None or (isinstance(val, str) and not val.strip()):
|
||
return ""
|
||
if ftype == "int":
|
||
return str(int(float(val)))
|
||
if ftype == "pct":
|
||
return str(float(val))
|
||
if ftype == "float":
|
||
return str(float(val))
|
||
return str(val).strip()
|