Compare commits
2 Commits
feature/is
...
feature/is
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a063bd9d10 | ||
| 847456e0af |
@@ -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,
|
||||
|
||||
@@ -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>🐍 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');
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user