diff --git a/api/static/dashboard.html b/api/static/dashboard.html
index 412bb90..a65b58d 100644
--- a/api/static/dashboard.html
+++ b/api/static/dashboard.html
@@ -471,7 +471,8 @@ function render(d) {
$("risk-day").textContent = rk.day_pnl_pct != null ? (rk.day_pnl_pct * 100).toFixed(1) + "% / -" + (rk.daily_loss_limit_pct * 100).toFixed(0) + "%" : "—";
const fr = $("risk-fresh");
fr.textContent = rk.data_age_s != null ? rk.data_age_s.toFixed(0) + "s" : "—";
- fr.style.color = (rk.data_age_s == null) ? "" : (rk.data_age_s > 90 ? "#ff4d4f" : rk.data_age_s > 30 ? "#f1c40f" : "");
+ // broker sync runs ~every 60s, so >90s = a missed sync (amber), >180s = stale (red)
+ fr.style.color = (rk.data_age_s == null) ? "" : (rk.data_age_s > 180 ? "#ff4d4f" : rk.data_age_s > 90 ? "#f1c40f" : "");
const accts = d.accounts || [];
$("acct-count").textContent = accts.length;
diff --git a/storage/projections.py b/storage/projections.py
index 9079314..941368a 100644
--- a/storage/projections.py
+++ b/storage/projections.py
@@ -625,8 +625,12 @@ def query_risk_state(store) -> dict:
t = (datetime.fromisoformat(ts_latest.replace("Z", "+00:00"))
if isinstance(ts_latest, str) else ts_latest)
if t.tzinfo is None:
- t = t.replace(tzinfo=timezone.utc)
- age = (datetime.now(timezone.utc) - t).total_seconds()
+ # event timestamps are stored NAIVE LOCAL (datetime.now()), so
+ # compare against naive-local now — comparing to UTC would be off
+ # by the whole timezone offset (the chip read ~4h on fresh data).
+ age = (datetime.now() - t).total_seconds()
+ else:
+ age = (datetime.now(timezone.utc) - t).total_seconds()
except Exception: # noqa: BLE001
pass