feat: 대시보드 Circuit Breaker 게이지 추가 (#196) #197
@@ -84,14 +84,11 @@ def create_dashboard_app(db_path: str) -> FastAPI:
|
||||
pnl_pct_rows = conn.execute(
|
||||
"""
|
||||
SELECT key, value
|
||||
FROM contexts
|
||||
WHERE layer = 'L6_DAILY'
|
||||
AND timeframe = ?
|
||||
AND key LIKE 'portfolio_pnl_pct_%'
|
||||
FROM system_metrics
|
||||
WHERE key LIKE 'portfolio_pnl_pct_%'
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 20
|
||||
""",
|
||||
(today,),
|
||||
"""
|
||||
).fetchall()
|
||||
current_pnl_pct: float | None = None
|
||||
if pnl_pct_rows:
|
||||
|
||||
12
src/db.py
12
src/db.py
@@ -138,6 +138,18 @@ def init_db(db_path: str) -> sqlite3.Connection:
|
||||
" ON trades (stock_code, market, timestamp DESC)"
|
||||
)
|
||||
|
||||
# Lightweight key-value store for trading system runtime metrics (dashboard use only)
|
||||
# Intentionally separate from the AI context tree to preserve separation of concerns.
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS system_metrics (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
|
||||
15
src/main.py
15
src/main.py
@@ -430,13 +430,16 @@ async def trading_cycle(
|
||||
{"volume_ratio": candidate.volume_ratio},
|
||||
)
|
||||
|
||||
# Store latest pnl_pct in L6 (daily P&L layer) so the dashboard can display the CB gauge
|
||||
context_store.set_context(
|
||||
ContextLayer.L6_DAILY,
|
||||
datetime.now(UTC).date().isoformat(),
|
||||
f"portfolio_pnl_pct_{market.code}",
|
||||
{"pnl_pct": round(pnl_pct, 4)},
|
||||
# Write pnl_pct to system_metrics (dashboard-only table, separate from AI context tree)
|
||||
db_conn.execute(
|
||||
"INSERT OR REPLACE INTO system_metrics (key, value, updated_at) VALUES (?, ?, ?)",
|
||||
(
|
||||
f"portfolio_pnl_pct_{market.code}",
|
||||
json.dumps({"pnl_pct": round(pnl_pct, 4)}),
|
||||
datetime.now(UTC).isoformat(),
|
||||
),
|
||||
)
|
||||
db_conn.commit()
|
||||
|
||||
# Build portfolio data for global rule evaluation
|
||||
portfolio_data = {
|
||||
|
||||
@@ -355,20 +355,12 @@ def test_positions_empty_when_no_trades(tmp_path: Path) -> None:
|
||||
|
||||
def _seed_cb_context(conn: sqlite3.Connection, pnl_pct: float, market: str = "KR") -> None:
|
||||
import json as _json
|
||||
from datetime import UTC, datetime
|
||||
today = datetime.now(UTC).date().isoformat()
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO contexts (layer, timeframe, key, value, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
"INSERT OR REPLACE INTO system_metrics (key, value, updated_at) VALUES (?, ?, ?)",
|
||||
(
|
||||
"L6_DAILY",
|
||||
today,
|
||||
f"portfolio_pnl_pct_{market}",
|
||||
_json.dumps({"pnl_pct": pnl_pct}),
|
||||
f"{today}T10:00:00+00:00",
|
||||
f"{today}T10:00:00+00:00",
|
||||
"2026-02-22T10:00:00+00:00",
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
Reference in New Issue
Block a user