feat(backtest): 대대적인 Optuna 백테스트 웹 UI 및 백엔드 파이프라인 개편
- Web UI: - Optuna 탭 추가 및 mode_combo (최빈값 조합), 사후합격 Top 10 시각화 기능 - 파라미터 분포(p25~p75, median, mode) 히스토그램 및 과적합(Overfit) 위험도 진단 UI 신설 - 체크박스 렌더링 깨짐 현상을 네이티브(appearance: auto)로 강제 복구 (CSS) - 다단 트레일링 스탑, 꼬리 진입/돌파 손절 등 고급 조건 설정 폼 UI 고도화 - Backend (Optuna Jobs): - CLI 환경에서 구동된 Optuna json 결과물을 웹 대시보드로 읽어오는 import 기능 강화 - JSON 메타데이터에 sort_by, mode, 호가 적용 여부 등 핵심 파라미터 파싱 누락 수정 - optuna_mode_combo.py 등 최빈값 조합 및 후보군 2차 검증을 위한 신규 모듈 추가 - DB & Execution: - WebSocket 호가/틱 피드 수집 통계(api_feed_collect_stats) 메모리 캐시 최적화 - KIS client 접속 키(approval_key) 등 인프라스트럭처 안정성 및 공유 관리 구조 개선 - 테스트 및 디버깅용 briefing 마크다운 자동 생성 기능 추가
This commit is contained in:
130
scripts/adhoc_cancelable_reconcile_smoke.py
Normal file
130
scripts/adhoc_cancelable_reconcile_smoke.py
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
scripts/adhoc_cancelable_reconcile_smoke.py
|
||||
===========================================
|
||||
Worker SELL C+D — 주말 구조 스모크 (REST 주문·cancel 호출 없음).
|
||||
|
||||
검증:
|
||||
· orders broker debug 컬럼 migrate
|
||||
· update_order_broker_snapshot / get_latest_sell_order_for_code 헬퍼
|
||||
· OrderManager._reconcile_cancelable_open disabled 분기 (noop)
|
||||
· poll 만료 cancel 실패 시 CANCELLED 금지 소스 앵커
|
||||
|
||||
실행:
|
||||
python3 -u scripts/adhoc_cancelable_reconcile_smoke.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
REQUIRED_ORDER_COLS = (
|
||||
"broker_open_qty",
|
||||
"broker_open_odno",
|
||||
"broker_reconcile_at",
|
||||
"broker_reconcile_note",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ok = True
|
||||
print("=== adhoc_cancelable_reconcile_smoke ===")
|
||||
|
||||
from kis_trader.database.db_manager import get_db
|
||||
from kis_trader.execution.order_manager import OrderManager, OrderRequest
|
||||
|
||||
db = get_db()
|
||||
db._ensure_orders_table()
|
||||
cols = [r["Field"] for r in db.conn.execute(
|
||||
"SHOW COLUMNS FROM orders",
|
||||
).fetchall()]
|
||||
missing = [c for c in REQUIRED_ORDER_COLS if c not in cols]
|
||||
if missing:
|
||||
print(f"FAIL orders 컬럼 누락: {missing}")
|
||||
ok = False
|
||||
else:
|
||||
print(f"OK orders broker debug 컬럼 {len(REQUIRED_ORDER_COLS)}개 확인")
|
||||
|
||||
snap_ok = db.update_order_broker_snapshot(
|
||||
ord_no="__smoke__",
|
||||
strategy_id="SMOKE",
|
||||
code="000000",
|
||||
open_qty=0,
|
||||
open_odno=None,
|
||||
note="adhoc_smoke",
|
||||
)
|
||||
if snap_ok:
|
||||
print("OK update_order_broker_snapshot (no-row UPDATE 허용)")
|
||||
else:
|
||||
print("WARN update_order_broker_snapshot False (no matching row — migrate OK)")
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_db = MagicMock()
|
||||
mock_db.get_latest_sell_order_for_code.return_value = None
|
||||
om = OrderManager(client=mock_client, db=mock_db)
|
||||
|
||||
req = OrderRequest(
|
||||
strategy_id="SMOKE",
|
||||
code="232680",
|
||||
name="232680",
|
||||
side="SELL",
|
||||
qty=1,
|
||||
price_ref=0.0,
|
||||
reason="smoke",
|
||||
)
|
||||
|
||||
def _env_bool(key, default=True):
|
||||
if key == "CANCELABLE_RECONCILE_ENABLED":
|
||||
return False
|
||||
if key == "CANCELABLE_RECONCILE_CANCEL_RETRY":
|
||||
return False
|
||||
return default
|
||||
|
||||
with patch(
|
||||
"kis_trader.execution.order_manager.get_env_bool",
|
||||
side_effect=_env_bool,
|
||||
):
|
||||
action = om._reconcile_cancelable_open(
|
||||
req, {"232680": {"psbl_qty": 10, "odno": "X"}},
|
||||
)
|
||||
if action != "disabled":
|
||||
print(f"FAIL reconcile disabled 기대=disabled 실제={action}")
|
||||
ok = False
|
||||
else:
|
||||
print("OK _reconcile_cancelable_open disabled → noop 경로")
|
||||
|
||||
from kis_trader.execution import order_manager as om_mod
|
||||
src = inspect.getsource(om_mod.OrderManager.poll_pending_fills)
|
||||
if "cancel_failed_at_poll" in src and "CANCELLED 금지" in src:
|
||||
print("OK poll_pending_fills cancel 실패 → CANCELLED 금지 앵커")
|
||||
else:
|
||||
print("FAIL poll_pending_fills cancel-fail 가드 소스 누락")
|
||||
ok = False
|
||||
|
||||
from kis_trader.strategies import base as base_mod
|
||||
base_src = inspect.getsource(base_mod.BaseStrategy._enqueue_sell)
|
||||
if "SELL_LOCKED_ENQUEUE_COOLDOWN_SEC" in base_src:
|
||||
print("OK base._enqueue_sell sell_locked cooldown 앵커")
|
||||
else:
|
||||
print("FAIL base enqueue cooldown 소스 누락")
|
||||
ok = False
|
||||
|
||||
ow_src = Path(ROOT / "kis_trader/execution/order_worker.py").read_text(encoding="utf-8")
|
||||
if "prefetch_broker_holdings(force=False)" in ow_src:
|
||||
print("OK order_worker prefetch force=False")
|
||||
else:
|
||||
print("FAIL order_worker prefetch force=False 누락")
|
||||
ok = False
|
||||
|
||||
print("=== 최종:", "통과" if ok else "실패", "===")
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -38,6 +38,9 @@ BREAKOUT_OPTUNA_SL_MODES="${BREAKOUT_OPTUNA_SL_MODES:-fixed}"
|
||||
BREAKOUT_OPTUNA_OB_MODES="${BREAKOUT_OPTUNA_OB_MODES:-off}"
|
||||
# kiwoom|ls — 웹 Optuna 이력소스 / CLI UNIVERSE_HISTORY_SOURCE
|
||||
UNIVERSE_HISTORY_SOURCE="${UNIVERSE_HISTORY_SOURCE:-${BACKTEST_UNIVERSE_HISTORY_SOURCE:-kiwoom}}"
|
||||
# 웹 순차 잡 ID (전략/조합별 1·2차 state 파일 prefix)
|
||||
OPTUNA_SEQ_JOB_ID="${OPTUNA_SEQ_JOB_ID:-seq}"
|
||||
SORT_BY="${SORT_BY:-score}"
|
||||
PY="${PY:-.venv/bin/python}"
|
||||
TS0="$(date +%Y%m%d_%H%M%S)"
|
||||
MASTER="logs/optuna_4strat_tpe_${START}_${END}_${TS0}_master.log"
|
||||
@@ -51,7 +54,7 @@ MASTER="logs/optuna_4strat_tpe_${START}_${END}_${TS0}_master.log"
|
||||
echo "BREAKOUT_OPTUNA_OB_MODES=$BREAKOUT_OPTUNA_OB_MODES"
|
||||
echo "UNIVERSE_HISTORY_SOURCE=$UNIVERSE_HISTORY_SOURCE"
|
||||
echo "min_wr=$MIN_WIN_RATE min_pf=$MIN_PF min_trades=$MIN_TRADES"
|
||||
echo "apply-best=OFF breakout-orderbook=스위치(스터디별) n_jobs=1 (사후 results_gated + briefing.md)"
|
||||
echo "apply-best=OFF · 전략/조합마다 1·2차 TPE 연쇄 · n_jobs=1 (사후 results_gated + briefing.md)"
|
||||
echo "master_log=$MASTER"
|
||||
free -h | sed -n '1,2p'
|
||||
df -h / | tail -1
|
||||
@@ -63,97 +66,106 @@ run_one() {
|
||||
local entry_mode="${2:-}"
|
||||
local sl_mode="${3:-}"
|
||||
local ob_mode="${4:-off}"
|
||||
local ts study log sort_by bo_extra
|
||||
local ts step_job_id refine_log state_file sort_by bo_extra extra min_trades_arg
|
||||
ts="$(date +%Y%m%d_%H%M%S)"
|
||||
study="${strat}_tpe_${START//-/}_${END//-/}_${ts}"
|
||||
log="logs/optuna_${strat}_tpe_${ts}.log"
|
||||
if [[ "$strat" == "tail" && -n "$entry_mode" ]]; then
|
||||
study="${strat}_${entry_mode}_tpe_${START//-/}_${END//-/}_${ts}"
|
||||
log="logs/optuna_${strat}_${entry_mode}_tpe_${ts}.log"
|
||||
fi
|
||||
if [[ "$strat" == "breakout" && -n "$sl_mode" ]]; then
|
||||
bo_extra="${sl_mode}_ob_${ob_mode}"
|
||||
study="${strat}_${bo_extra}_tpe_${START//-/}_${END//-/}_${ts}"
|
||||
log="logs/optuna_${strat}_${bo_extra}_tpe_${ts}.log"
|
||||
fi
|
||||
sort_by="${SORT_BY:-score}"
|
||||
case "$sort_by" in
|
||||
score|score_legacy|pnl|daily_avg|win_rate) ;;
|
||||
*) sort_by="score" ;;
|
||||
esac
|
||||
|
||||
local min_trades_arg
|
||||
if [[ "$strat" == "breakout" && -n "$sl_mode" ]]; then
|
||||
bo_extra="${sl_mode}_ob_${ob_mode}"
|
||||
extra="$bo_extra"
|
||||
elif [[ "$strat" == "tail" && -n "$entry_mode" ]]; then
|
||||
extra="$entry_mode"
|
||||
else
|
||||
extra=""
|
||||
fi
|
||||
|
||||
step_job_id="${OPTUNA_SEQ_JOB_ID}_${strat}${extra:+_${extra}}_${ts}"
|
||||
refine_log="logs/optuna_seq_refine_${step_job_id}.log"
|
||||
state_file="logs/${step_job_id}_refine_state.json"
|
||||
|
||||
min_trades_arg=$("$PY" -c "from kis_trader.backtest.optuna_common import resolve_optuna_min_trades; print(resolve_optuna_min_trades('${START}', '${END}', '${strat}')['min_trades'])")
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "-------- [$strat${entry_mode:+/$entry_mode}${sl_mode:+/$sl_mode}${bo_extra:+/$bo_extra}] START $(date -Is) study=$study univ=$UNIVERSE_HISTORY_SOURCE min_trades=$min_trades_arg --------"
|
||||
echo "-------- [$strat${entry_mode:+/$entry_mode}${sl_mode:+/$sl_mode}${bo_extra:+/$bo_extra}] START $(date -Is) 1·2차 step=$step_job_id --------"
|
||||
} | tee -a "$MASTER"
|
||||
echo "$log" > "logs/optuna_${strat}_tpe_latest.logpath"
|
||||
echo "$study" > "logs/optuna_${strat}_tpe_latest.study"
|
||||
# 웹 진행률: 전역 latest.study 가 이전 전략에 남으면 바가 1번에서 멈춤 → 잡별 파일
|
||||
|
||||
if [[ -n "${OPTUNA_SEQ_ACTIVE_FILE:-}" ]]; then
|
||||
if [[ "$strat" == "breakout" && -n "$bo_extra" ]]; then
|
||||
extra="$bo_extra"
|
||||
else
|
||||
extra="${entry_mode:-${sl_mode:-}}"
|
||||
fi
|
||||
{
|
||||
echo "strategy=$strat"
|
||||
echo "study=$study"
|
||||
echo "entry_mode=${entry_mode:-}"
|
||||
echo "sl_mode=${sl_mode:-}"
|
||||
echo "ob_mode=${ob_mode:-}"
|
||||
echo "extra=${extra}"
|
||||
echo "refine_state_path=$state_file"
|
||||
} > "$OPTUNA_SEQ_ACTIVE_FILE"
|
||||
fi
|
||||
|
||||
set +e
|
||||
set +e
|
||||
local cmd_args=(
|
||||
local refine_args=(
|
||||
--job-id "$step_job_id"
|
||||
--strategy "$strat"
|
||||
--mode "$MODE"
|
||||
--start "$START"
|
||||
--end "$END"
|
||||
--trials "$TRIALS"
|
||||
--min_trades "$min_trades_arg"
|
||||
--min_win_rate "$MIN_WIN_RATE"
|
||||
--min_pf "$MIN_PF"
|
||||
--orderbook-filter "$([[ "$strat" == "breakout" ]] && echo "$ob_mode" || echo off)"
|
||||
--no-progress
|
||||
--study-name "$study"
|
||||
--sort-by "$sort_by"
|
||||
--min-trades "$min_trades_arg"
|
||||
--universe-history-source "$UNIVERSE_HISTORY_SOURCE"
|
||||
)
|
||||
if [[ -n "${STUDY_TRIALS:-}" && "${STUDY_TRIALS}" != "0" ]]; then
|
||||
cmd_args+=(--study-trials "$STUDY_TRIALS")
|
||||
refine_args+=(--study-trials "$STUDY_TRIALS")
|
||||
fi
|
||||
if [[ "$strat" == "tail" && -n "$entry_mode" ]]; then
|
||||
cmd_args+=(--entry-mode "$entry_mode")
|
||||
refine_args+=(--entry-mode "$entry_mode")
|
||||
fi
|
||||
if [[ "$strat" == "breakout" && -n "$sl_mode" ]]; then
|
||||
cmd_args+=(--sl-mode "$sl_mode")
|
||||
refine_args+=(--sl-mode "$sl_mode" --ob-mode "$ob_mode")
|
||||
fi
|
||||
if [[ -n "${CANDLE_SOURCE:-}" ]]; then
|
||||
refine_args+=(--candle-source "$CANDLE_SOURCE")
|
||||
fi
|
||||
|
||||
if [[ -n "${TICK_SOURCE:-}" ]]; then
|
||||
cmd_args+=("--tick-source" "$TICK_SOURCE")
|
||||
refine_args+=(--tick-source "$TICK_SOURCE")
|
||||
fi
|
||||
if [[ -n "${OB_SOURCE:-}" ]]; then
|
||||
cmd_args+=("--ob-source" "$OB_SOURCE")
|
||||
refine_args+=(--ob-source "$OB_SOURCE")
|
||||
fi
|
||||
|
||||
"$PY" -u kis_trader/backtest/param_search_optuna.py "${cmd_args[@]}" >"$log" 2>&1
|
||||
"$PY" -u kis_trader/backtest/optuna_mode_refine_runner.py "${refine_args[@]}" >"$refine_log" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
|
||||
local result_json briefing_md
|
||||
result_json=""
|
||||
briefing_md=""
|
||||
if [[ -f "$state_file" ]]; then
|
||||
result_json=$("$PY" -c "import json; print(json.load(open('${state_file}')).get('result_json') or '')" 2>/dev/null || true)
|
||||
if [[ -n "$result_json" && -f "$result_json" ]]; then
|
||||
echo "OPTUNA_RESULT_JSON=$result_json" | tee -a "$MASTER"
|
||||
briefing_md="${result_json%.json}.briefing.md"
|
||||
if [[ -f "$briefing_md" ]]; then
|
||||
echo "OPTUNA_BRIEFING_MD=$briefing_md" | tee -a "$MASTER"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ -z "$result_json" ]]; then
|
||||
grep -E 'OPTUNA_RESULT_JSON=' "$refine_log" | tail -n 1 | tee -a "$MASTER" || true
|
||||
fi
|
||||
|
||||
{
|
||||
echo "-------- [$strat${entry_mode:+/$entry_mode}${sl_mode:+/$sl_mode}${bo_extra:+/$bo_extra}] END rc=$rc $(date -Is) --------"
|
||||
echo "LOG=$log"
|
||||
grep -E 'OPTUNA_RESULT_JSON=|OPTUNA_BRIEFING_MD=|Best trial|optuna_best|❌|KeyError|Traceback' "$log" | tail -n 24 || true
|
||||
echo "REFINE_LOG=$refine_log"
|
||||
echo "STATE=$state_file"
|
||||
grep -E 'OPTUNA_RESULT_JSON=|OPTUNA_BRIEFING_MD=|✅ 1·2차|❌ 1·2차|phase1|phase2|Traceback' "$refine_log" | tail -n 24 || true
|
||||
} | tee -a "$MASTER"
|
||||
|
||||
if [[ "$rc" -ne 0 ]]; then
|
||||
echo "⚠️ [$strat] 실패 rc=$rc — 다음 전략 계속" | tee -a "$MASTER"
|
||||
echo "⚠️ [$strat] 1·2차 실패 rc=$rc — 다음 전략 계속" | tee -a "$MASTER"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user