Merge pull request 'feat: 대시보드 헤더에 모의투자/실전투자 모드 배지 표시 (#237)' (#238) from feature/issue-237-dashboard-mode-badge into main
Some checks failed
CI / test (push) Has been cancelled

Reviewed-on: #238
This commit was merged in pull request #238.
This commit is contained in:
2026-02-24 06:49:21 +09:00
3 changed files with 55 additions and 0 deletions

View File

@@ -111,6 +111,7 @@ def create_dashboard_app(db_path: str) -> FastAPI:
return {
"date": today,
"mode": os.getenv("MODE", "paper"),
"markets": market_status,
"totals": {
"trade_count": total_trades,

View File

@@ -43,6 +43,19 @@
font-size: 12px; transition: border-color 0.2s;
}
.refresh-btn:hover { border-color: var(--accent); color: var(--accent); }
.mode-badge {
padding: 3px 10px; border-radius: 5px; font-size: 12px; font-weight: 700;
letter-spacing: 0.5px;
}
.mode-badge.live {
background: rgba(224, 85, 85, 0.15); color: var(--red);
border: 1px solid rgba(224, 85, 85, 0.4);
animation: pulse-warn 2s ease-in-out infinite;
}
.mode-badge.paper {
background: rgba(232, 160, 64, 0.15); color: var(--warn);
border: 1px solid rgba(232, 160, 64, 0.4);
}
/* CB Gauge */
.cb-gauge-wrap {
@@ -225,6 +238,7 @@
<header>
<h1>&#x1F40D; The Ouroboros</h1>
<div class="header-right">
<span class="mode-badge" id="mode-badge">--</span>
<div class="cb-gauge-wrap" id="cb-gauge" title="Circuit Breaker">
<span class="cb-dot unknown" id="cb-dot"></span>
<span id="cb-label">CB --</span>
@@ -512,9 +526,22 @@
}
document.getElementById('card-pnl-sub').textContent = `결정 ${t.decision_count ?? 0}`;
renderCbGauge(d.circuit_breaker);
renderModeBadge(d.mode);
} catch {}
}
function renderModeBadge(mode) {
const el = document.getElementById('mode-badge');
if (!el) return;
if (mode === 'live') {
el.textContent = '🔴 실전투자';
el.className = 'mode-badge live';
} else {
el.textContent = '🟡 모의투자';
el.className = 'mode-badge paper';
}
}
async function fetchPerformance() {
try {
const r = await fetch('/api/performance?market=all');

View File

@@ -413,3 +413,30 @@ def test_status_circuit_breaker_unknown_when_no_data(tmp_path: Path) -> None:
cb = body["circuit_breaker"]
assert cb["status"] == "unknown"
assert cb["current_pnl_pct"] is None
def test_status_mode_paper(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""MODE=paper일 때 status 응답에 mode=paper가 포함돼야 한다."""
monkeypatch.setenv("MODE", "paper")
app = _app(tmp_path)
get_status = _endpoint(app, "/api/status")
body = get_status()
assert body["mode"] == "paper"
def test_status_mode_live(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""MODE=live일 때 status 응답에 mode=live가 포함돼야 한다."""
monkeypatch.setenv("MODE", "live")
app = _app(tmp_path)
get_status = _endpoint(app, "/api/status")
body = get_status()
assert body["mode"] == "live"
def test_status_mode_default_paper(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""MODE 환경변수가 없으면 mode 기본값은 paper여야 한다."""
monkeypatch.delenv("MODE", raising=False)
app = _app(tmp_path)
get_status = _endpoint(app, "/api/status")
body = get_status()
assert body["mode"] == "paper"