이번에 들어간 내용

한투 호가 = 2번째 앱키 전용
키 없거나 start 실패 시 메인에 H0STASP0 안 붙임. 운영설정 WS_ORDERBOOK_SAVE_KIS 빨간 danger.

LS RAM 합집합
후보∪보유∪영구∪grace. sync_targets와 split reconcile 둘 다. 틱 DB 영구 게이트는 그대로.

분봉 쓰레기 → 다음 소스 봉 통째
그 분 틱 0건이거나 전부 봉끝 대비 LIVE_FEED_FALLBACK_MAX_AGE_SEC 초과면 구멍. 메인 WS → 2차 → LS → REST → rollup. CANDLE_GARBAGE_FALLBACK 기본 true.

파일: feed_fallback.py(신규), ws_manager.py, kis_ws.py, candle_series.py, bt_candle_source.py, live_config_schema.py, database.py, 스모크, MD 2개.

같은 ws_manager/database/kis_ws/live_config에는 직전 커밋 이후 쌓여 있던 시세 폴백·ENV 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
This commit is contained in:
Your Name
2026-08-19 22:11:31 +09:00
parent d1dc274f0e
commit 0ecac7cb95
229 changed files with 8487 additions and 1647 deletions

View File

@@ -371,6 +371,112 @@ def build_optuna_result_tiers(
}
def _optuna_overfit_sample_days(data: Dict[str, Any]) -> int:
try:
days = int(data.get("backtest_days") or 0)
except (TypeError, ValueError):
days = 0
if days > 0:
return days
start = str(data.get("start") or "")
end = str(data.get("end") or "")
try:
from datetime import datetime as _dt
return max(
1,
(_dt.strptime(end, "%Y-%m-%d") - _dt.strptime(start, "%Y-%m-%d")).days + 1,
)
except Exception:
return 1
def overfit_risk_pct_for_row(
data: Dict[str, Any],
row: Optional[Dict[str, Any]],
) -> Dict[str, Any]:
"""
후보 한 줄의 과적합 가능도% (0~100, 높을수록 위험 · 만점=100).
스터디 공통(표본 장일) + 이 trial의 거래수·승률·PF 이상치.
교차검증이 아님. DB apply 게이트와 별개.
"""
def _f(x: Any, default: float = 0.0) -> float:
try:
return float(x)
except (TypeError, ValueError):
return default
def _i(x: Any, default: int = 0) -> int:
try:
return int(x)
except (TypeError, ValueError):
return default
days = _optuna_overfit_sample_days(data or {})
risk = 0.0
if days <= 1:
risk += 40.0
elif days == 2:
risk += 28.0
elif days <= 4:
risk += 16.0
if not row:
risk = max(0.0, min(100.0, round(risk + 25.0, 1)))
return {
"overfit_risk_pct": risk,
"verdict": "비권장",
"verdict_ui": "위험 · 비권장",
}
nt = _i(row.get("total_trades"))
wr = _f(row.get("win_rate"))
pf = _f(row.get("pf"))
pnl = _f(row.get("total_pnl"))
if nt <= 1:
risk += 25.0
elif nt <= 3:
risk += 18.0
elif nt <= 9:
risk += 10.0
if wr >= 90.0 and nt < 10:
risk += 15.0
elif wr >= 80.0 and nt < 5:
risk += 10.0
if pf >= 50.0 and nt < 10:
risk += 10.0
elif pf >= 10.0 and nt < 5:
risk += 6.0
gated = list((data or {}).get("results_gated") or [])
learn = list((data or {}).get("results") or (data or {}).get("results_all") or [])
pool = gated if gated else learn
if pool and nt > 0:
best_pnl = round(pnl, 0)
same = [r for r in pool if abs(_f(r.get("total_pnl")) - best_pnl) < 1.0]
share = len(same) / max(1, len(pool))
if share >= 0.4 and len(same) >= 5:
risk += 12.0
elif share >= 0.25 and len(same) >= 3:
risk += 6.0
risk = max(0.0, min(100.0, round(risk, 1)))
if risk >= 70.0:
verdict, verd_ui = "비권장", "위험 · 비권장"
elif risk >= 40.0:
verdict, verd_ui = "주의", "주의"
else:
verdict, verd_ui = "상대적으로낮음", "상대적으로 낮음"
return {
"overfit_risk_pct": risk,
"verdict": verdict,
"verdict_ui": verd_ui,
}
def build_optuna_overfit_diagnostics(data: Dict[str, Any]) -> Dict[str, Any]:
"""
Optuna 결과 → 과적합 위험% · 적용 가능도% · 임계값(파라미터) 분포 표용 dict.
@@ -740,9 +846,13 @@ def ensure_optuna_gate_env_defaults(db: Any = None) -> None:
"OPTUNA_POST_TOP_N": "5",
"OPTUNA_POST_INCLUDE_MODE": "true",
"OPTUNA_POST_INCLUDE_LIVE": "true",
"OPTUNA_POST_INCLUDE_STABLE": "true",
"OPTUNA_POST_RUN_OB_WHIPSAW": "true",
"OPTUNA_OB_RECOMMEND_TRIALS": "1000",
"OPTUNA_OB_AXIS_TRIALS": "0",
"OPTUNA_OB_RECOMMEND_TRIALS": "500",
"OPTUNA_OB_AXIS_TRIALS": "500",
"OPTUNA_OB_COMBO_TRIALS_SINGLE": "150",
"OPTUNA_OB_COMBO_TRIALS_DOUBLE": "200",
"OPTUNA_OB_COMBO_TRIALS_TRIPLE": "250",
"OPTUNA_OB_ENTRY_SPREAD_MIN": "0.1",
"OPTUNA_OB_ENTRY_SPREAD_MAX": "8.0",
"OPTUNA_OB_ENTRY_RATIO_MIN": "0.05",