Files
kis_bot/static/css/backtest.css
Your Name 9ba9ab73b6 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 마크다운 자동 생성 기능 추가
2026-09-01 02:47:51 +09:00

440 lines
16 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ── Bootstrap dark-theme 변수 오버라이드 ── */
[data-bs-theme="dark"] {
--bs-body-bg: #0d1117;
--bs-body-color: #e6edf3;
--bs-border-color: #21262d;
--bs-secondary-bg: #161b22;
--bs-tertiary-bg: #161b22;
--bs-table-color: #e6edf3;
--bs-table-bg: transparent;
--bs-table-border-color: #21262d;
--bs-table-hover-bg: rgba(88,166,255,.07);
--bs-table-hover-color: #e6edf3;
--bs-form-control-bg: #21262d;
--bs-form-control-color: #e6edf3;
--bs-input-bg: #21262d;
--bs-input-color: #e6edf3;
--bs-link-color: #58a6ff;
}
:root {
--bg: #0d1117; --surface: #161b22; --border: #21262d;
--text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
--green: #3fb950; --red: #f85149; --yellow: #d29922; --purple: #bc8cff;
}
* { box-sizing: border-box; }
body { background: var(--bg) !important; color: var(--text) !important;
font-family: 'Segoe UI', sans-serif; font-size: 14px; }
/* 네비 / 탭 */
.navbar-brand { font-weight: 700; letter-spacing: 1px; color: var(--accent) !important; }
.nav-tabs .nav-link { color: var(--muted); border: none; padding: 10px 20px; }
.nav-tabs .nav-link.active { color: var(--accent); border-bottom: 2px solid var(--accent); background: transparent; }
.nav-tabs { border-bottom: 1px solid var(--border); }
/* 카드 */
.card { background: var(--surface) !important; border: 1px solid var(--border) !important; border-radius: 10px; }
.card-title { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 4px; }
.stat-val { font-size: 26px; font-weight: 700; color: var(--text); }
.stat-val.green { color: var(--green) !important; }
.stat-val.red { color: var(--red) !important; }
/* 폼 컨트롤 */
.form-control, .form-select, input[type=number], input[type=date] {
background: #21262d !important; border: 1px solid #30363d !important;
color: var(--text) !important; border-radius: 6px;
}
.form-control:focus, .form-select:focus, input:focus {
background: #21262d !important; color: var(--text) !important;
border-color: var(--accent) !important; box-shadow: none !important;
}
.btn-primary { background: var(--accent) !important; border: none !important; font-weight: 600; color: #0d1117 !important; }
.btn-primary:hover { background: #79b8ff !important; }
label, .form-label { color: var(--muted) !important; font-size: 12px; }
/* 테이블 — Bootstrap dark theme가 대부분 처리, 남은 것만 보정 */
.table { font-size: 12px; }
.table td, .table th { color: var(--text) !important; border-color: var(--border) !important; padding: 6px 10px; }
.table thead th { color: var(--muted) !important; }
.table-responsive { max-height: 420px; overflow-y: auto; }
.table-responsive::-webkit-scrollbar { width: 5px; }
.table-responsive::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
/* 배지 */
.badge-win { background: rgba(63,185,80,.2); color: var(--green) !important; padding: 2px 8px; border-radius: 20px; }
.badge-loss { background: rgba(248,81,73,.2); color: var(--red) !important; padding: 2px 8px; border-radius: 20px; }
.badge-flat { background: rgba(210,153,34,.15); color: var(--yellow) !important; padding: 2px 8px; border-radius: 20px; }
.badge-reason { background: rgba(88,166,255,.12); color: #8b949e !important; padding: 2px 8px; border-radius: 20px; font-size: 11px; white-space: nowrap; }
.trade-sort-bar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: 12px; }
.trade-sort-bar .trade-sort-label { color: var(--muted); margin-right: 4px; font-weight: 600; }
.trade-sort-btn { font-size: 11px !important; padding: 2px 8px !important; }
.trade-sort-btn.active { background: rgba(88,166,255,.22) !important; border-color: var(--accent) !important; color: var(--accent) !important; }
/* 스피너 */
.spin-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.55);
z-index: 9999; align-items: center; justify-content: center; }
.spin-overlay.show { display: flex; }
.trade-db-chart-overlay {
position: fixed; inset: 0; z-index: 10050;
background: rgba(0,0,0,.65);
align-items: center; justify-content: center;
padding: 16px;
}
.trade-db-chart-panel {
width: min(960px, 96vw);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 14px 16px 12px;
box-shadow: 0 12px 40px rgba(0,0,0,.45);
}
.trade-ext-links a { color: var(--accent); text-decoration: none; }
.trade-ext-links a:hover { text-decoration: underline; }
.spinner { width: 48px; height: 48px; border: 4px solid var(--border);
border-top-color: var(--accent); border-radius: 50%; animation: spin .9s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* 기타 */
canvas { background: transparent !important; }
.section-title { font-size: 13px; font-weight: 600; color: var(--muted);
text-transform: uppercase; letter-spacing: 1px; margin: 24px 0 12px; }
input[type=radio] { accent-color: var(--accent); }
.ratio-bar { height: 8px; border-radius: 4px; background: var(--border); margin-top: 4px; overflow: hidden; }
.ratio-fill-g { height: 100%; border-radius: 4px 0 0 4px; background: var(--green); display: inline-block; }
.ratio-fill-r { height: 100%; border-radius: 0 4px 4px 0; background: var(--red); display: inline-block; float: right; }
.text-pnl-pos { color: var(--green) !important; }
.text-pnl-neg { color: var(--red) !important; }
/* 보유·실거래 보유중 — 업비트식 틱 플래시 (표시만) */
.live-quote-flash-up {
animation: liveQuoteFlashUp 0.45s ease;
}
.live-quote-flash-down {
animation: liveQuoteFlashDown 0.45s ease;
}
@keyframes liveQuoteFlashUp {
from { background-color: rgba(63, 185, 80, 0.35); }
to { background-color: transparent; }
}
@keyframes liveQuoteFlashDown {
from { background-color: rgba(248, 81, 73, 0.35); }
to { background-color: transparent; }
}
.live-quote-stale { opacity: 0.55; }
/* 파라미터 설명 툴팁 */
.param-help { font-size: 10px; color: var(--muted); margin-top: 2px; }
/* 운영 설정 — 당일 손익 칩 한 줄 */
.lc-status-row {
display: flex;
flex-wrap: nowrap;
align-items: stretch;
gap: 10px;
overflow-x: auto;
padding-bottom: 2px;
}
.lc-status-chip {
flex: 0 0 auto;
min-width: 148px;
max-width: 220px;
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
background: rgba(22, 27, 34, 0.55);
font-size: 13px;
}
.lc-status-chip .lc-status-label {
font-size: 11px;
color: var(--muted);
margin-bottom: 2px;
white-space: nowrap;
}
.lc-status-chip .lc-status-value {
font-size: 18px;
font-weight: 650;
line-height: 1.2;
}
.lc-status-chip .lc-status-meta {
font-size: 11px;
color: var(--muted);
margin-top: 2px;
white-space: nowrap;
}
/* 전략 ON/OFF — 토글 한 줄 */
.lc-switch-row {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 10px 14px;
overflow-x: auto;
padding: 4px 0 2px;
}
.lc-switch-chip {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
font-size: 13px;
margin: 0;
cursor: pointer;
user-select: none;
}
.lc-switch-chip input[type="checkbox"] {
margin: 0;
flex-shrink: 0;
}
.lc-switch-chip .lc-switch-sid {
font-size: 10px;
color: var(--muted);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.lc-switch-chip.lc-switch-warn {
color: #d29922;
}
/* 운영 설정 탭 — 최대 2열, 위에서 아래로 읽기 */
.lc-field-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 20px;
max-width: 920px;
}
@media (max-width: 700px) {
.lc-field-grid { grid-template-columns: 1fr; }
}
.lc-field {
min-width: 0;
}
.lc-field input.form-control {
max-width: 100%;
}
.lc-field-label { font-size: 12px; color: var(--muted); display: block; margin-bottom: 2px; line-height: 1.35; }
.lc-field-hint { font-size: 10px; color: var(--muted); margin-top: 2px; line-height: 1.35; }
.lc-field-tbl { font-size: 10px; color: #58a6ff; margin-left: 4px; }
.lc-field-key { font-size: 10px; color: #8b949e; margin-left: 4px; font-weight: normal; }
/* 키/테이블 태그: 평소엔 숨기고 호버 시만 — 라벨·입력에 시선 집중 */
.lc-field-key,
.lc-field-tbl {
opacity: 0.35;
transition: opacity 0.12s ease;
}
.lc-field:hover .lc-field-key,
.lc-field:hover .lc-field-tbl,
.lc-field:focus-within .lc-field-key,
.lc-field:focus-within .lc-field-tbl {
opacity: 1;
}
/* 카테고리 안 전략·주제 구역 */
.lc-strat-stack {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 960px;
}
.lc-strat-block {
border: 1px solid var(--border);
border-left-width: 4px;
border-radius: 8px;
padding: 10px 12px 12px;
background: rgba(22, 27, 34, 0.55);
}
.lc-strat-head {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 8px;
padding-bottom: 5px;
border-bottom: 1px solid var(--border);
font-size: 13px;
font-weight: 650;
letter-spacing: 0.02em;
color: var(--text);
}
.lc-strat-head .lc-strat-sid {
font-size: 11px;
font-weight: 500;
color: var(--muted);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.lc-topic-head {
font-size: 12px;
font-weight: 600;
color: var(--muted);
margin: 4px 0 8px;
padding-left: 2px;
}
.lc-strat-common { border-left-color: #6e7681; }
.lc-strat-short { border-left-color: #39d2c0; }
.lc-strat-momentum { border-left-color: #d29922; }
.lc-strat-breakout { border-left-color: #3fb950; }
.lc-strat-updow { border-left-color: #58a6ff; }
.lc-strat-scalp { border-left-color: #8b949e; }
.lc-strat-range_break { border-left-color: #79c0ff; }
.lc-strat-dbband { border-left-color: #f0883e; }
.lc-strat-other { border-left-color: #bc8cff; }
.lc-strat-topic { border-left-color: #6e7681; }
.lc-strat-short .lc-strat-head { color: #39d2c0; }
.lc-strat-momentum .lc-strat-head { color: #d29922; }
.lc-strat-breakout .lc-strat-head { color: #3fb950; }
.lc-strat-updow .lc-strat-head { color: #58a6ff; }
.lc-strat-scalp .lc-strat-head { color: #c9d1d9; }
.lc-strat-common .lc-strat-head { color: #8b949e; }
/* OHLC 폴백 — 정합 위험 (절대규칙: 틱 모드에서 숫자 변조 금지). UI만 경고 표시 */
.lc-field-danger,
.bt-ohlc-fallback-danger {
background: rgba(248, 81, 73, 0.18) !important;
border: 1px solid rgba(248, 81, 73, 0.65) !important;
border-radius: 6px;
padding: 6px 10px !important;
margin: 4px 0;
}
.lc-field-danger .lc-field-label,
.bt-ohlc-fallback-danger label,
.bt-ohlc-fallback-danger .form-check-label {
color: #ff7b72 !important;
font-weight: 600;
}
.lc-field-danger .lc-field-hint,
.bt-ohlc-fallback-danger .bt-ohlc-fallback-warn {
color: #ffa198 !important;
font-size: 11px;
}
.ob-stamp { font-size: 10px; line-height: 1.35; }
.ob-stamp-danger {
color: #f85149 !important;
font-weight: 700;
}
.ob-metric-ok { color: var(--green) !important; font-weight: 600; }
.ob-metric-bad { color: var(--red) !important; font-weight: 600; }
/* 거래표 — 정합 예외 경로(OHLC폴백·틱공백EOD 등) */
tr.trade-row-parity-ex {
background: rgba(248, 81, 73, 0.16) !important;
position: relative;
}
tr.trade-row-parity-ex td {
border-color: rgba(248, 81, 73, 0.35) !important;
}
td.trade-name-cell { position: relative; padding-top: 14px !important; }
.parity-cause-corner {
position: absolute;
top: 2px;
right: 4px;
font-size: 9px;
font-weight: 700;
color: #ff7b72;
background: rgba(248, 81, 73, 0.25);
border: 1px solid rgba(248, 81, 73, 0.55);
border-radius: 3px;
padding: 0 4px;
line-height: 1.4;
white-space: nowrap;
max-width: 110px;
overflow: hidden;
text-overflow: ellipsis;
}
/* Optuna 전역/탭 프로그레스 */
.optuna-nav-bar { min-width: 180px; }
.optuna-nav-bar.d-none { display: none !important; }
.optuna-prog-track {
height: 6px; border-radius: 4px; background: var(--border); overflow: hidden;
}
.optuna-prog-fill {
height: 100%; border-radius: 4px; background: var(--accent);
transition: width .35s ease;
}
.optuna-prog-fill.is-done { background: var(--green); }
.optuna-prog-fill.is-error { background: var(--red); }
.optuna-prog-fill.is-post { background: #d29922; }
.opt-join-overlay {
display: none;
position: fixed;
inset: 0;
z-index: 2200;
background: rgba(0, 0, 0, 0.62);
align-items: center;
justify-content: center;
padding: 16px;
}
.opt-join-overlay.is-open { display: flex; }
.opt-join-panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
max-width: 860px;
width: 100%;
max-height: 90vh;
overflow: auto;
padding: 16px;
}
.opt-join-pre {
font-size: 11px;
white-space: pre-wrap;
word-break: break-all;
background: #0d1117;
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px;
color: var(--text);
max-height: 220px;
overflow: auto;
}
.opt-period-banner {
border: 1px solid var(--border);
font-size: 12px;
}
.opt-period-banner.opt-period-ok {
background: rgba(63, 185, 80, 0.08);
border-color: rgba(63, 185, 80, 0.35);
}
.opt-period-banner.opt-period-warn {
background: rgba(210, 153, 34, 0.12);
border-color: rgba(210, 153, 34, 0.55);
}
.opt-period-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
border: 1px solid var(--border);
background: rgba(255, 255, 255, 0.04);
font-size: 11px;
white-space: nowrap;
}
.opt-period-badge b { font-weight: 600; color: var(--text); }
.opt-period-badge-form { border-color: #58a6ff; }
.opt-period-badge-master { border-color: #8b949e; }
.opt-period-badge-p1 { border-color: #3fb950; }
.opt-period-badge-p2 { border-color: #d29922; }
.opt-period-badge-active { border-color: #bc8cff; }
.opt-period-badge-warn {
background: rgba(248, 81, 73, 0.12);
border-color: rgba(248, 81, 73, 0.55);
}
.opt-period-cell-warn { color: #d29922; }
/* 오늘 운영 — 조건검색 이력(키움/LS) */
.dash-univ-table .dash-univ-src-kiwoom { color: #58a6ff; font-weight: 600; }
.dash-univ-table .dash-univ-src-kis { color: #3fb950; font-weight: 600; }
.dash-univ-table .dash-univ-src-ls { color: #bc8cff; font-weight: 600; }
.dash-univ-table tr.dash-univ-group-start td { border-top: 2px solid var(--border); }
.dash-univ-table tr.dash-univ-stale td { opacity: 0.85; }
/* 전략×2행(10행)이 잘리지 않도록 — 전역 .table-responsive 420px 오버라이드 */
.dash-univ-wrap.table-responsive { max-height: none; overflow-y: visible; }
/* 오늘 운영 — 전략별 키움/LS 매매 두 줄 */
.dash-ops-table .dash-univ-src-kiwoom { color: #58a6ff; font-weight: 600; }
.dash-ops-table .dash-univ-src-kis { color: #3fb950; font-weight: 600; }
.dash-ops-table .dash-univ-src-ls { color: #bc8cff; font-weight: 600; }
.dash-ops-table tr.dash-broker-head td {
background: rgba(88, 166, 255, 0.08);
font-weight: 700;
border-top: 2px solid var(--border);
color: var(--text);
}
.dash-ops-table tr.dash-broker-head.dash-broker-kis td { background: rgba(63, 185, 80, 0.08); }
.dash-ops-table tr.dash-ops-group-start td { border-top: 2px solid var(--border); }
.dash-ops-table tr.dash-ops-inactive td { opacity: 0.55; }