Price chart: narrower volume bars, right-side volume Y-axis; UI polish
Browse files- Volume bars width reduced to 35% of slot (was 65%) for cleaner chart
- Volume Y-axis labels added on right side (0, 50%, max) with k-suffix
- pad.right widened to 46px to accommodate volume labels
- Trading Statistics thead row: background #f0f0f0 (matches Orders/Trades)
- Auto mode: End of Day button stays enabled when session is active
- Default mode changed to Automatic (backend + frontend)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dashboard/dashboard.py +1 -1
- dashboard/templates/index.html +23 -13
dashboard/dashboard.py
CHANGED
|
@@ -20,7 +20,7 @@ sse_clients = []
|
|
| 20 |
sse_clients_lock = threading.Lock()
|
| 21 |
|
| 22 |
# Session state
|
| 23 |
-
session_state = {"active": False, "start_time": None, "suspended": False, "mode": "
|
| 24 |
|
| 25 |
SCHEDULE_FILE = os.getenv("SCHEDULE_FILE", "/app/shared_data/market_schedule.txt")
|
| 26 |
|
|
|
|
| 20 |
sse_clients_lock = threading.Lock()
|
| 21 |
|
| 22 |
# Session state
|
| 23 |
+
session_state = {"active": False, "start_time": None, "suspended": False, "mode": "automatic"}
|
| 24 |
|
| 25 |
SCHEDULE_FILE = os.getenv("SCHEDULE_FILE", "/app/shared_data/market_schedule.txt")
|
| 26 |
|
dashboard/templates/index.html
CHANGED
|
@@ -201,9 +201,9 @@
|
|
| 201 |
Trading Dashboard
|
| 202 |
<span id="status" class="status connecting"><span class="dot"></span><span id="status-text">Connecting...</span></span>
|
| 203 |
<span id="session-badge" class="status idle"><span class="dot"></span><span id="session-text">IDLE</span></span>
|
| 204 |
-
<button id="day-btn" onclick="toggleDay()" class="btn-day btn-start">Start of Day</button>
|
| 205 |
<button id="suspend-btn" onclick="toggleSuspend()" class="btn-day btn-suspend" disabled>Suspend</button>
|
| 206 |
-
<button id="mode-btn" onclick="toggleMode()" class="btn-day btn-
|
| 207 |
<a href="/fix/" style="margin-left:auto; padding:4px 14px; background:#6c757d; color:#fff; border-radius:20px; font-size:12px; font-weight:bold; text-decoration:none;">FIX UI</a>
|
| 208 |
</h1>
|
| 209 |
<div class="container">
|
|
@@ -310,7 +310,7 @@
|
|
| 310 |
<div id="stats-container" style="flex-grow:1; overflow-y:auto; padding: 10px;">
|
| 311 |
<table id="stats-table" style="width:100%; margin-bottom:10px; font-size:12px;">
|
| 312 |
<thead>
|
| 313 |
-
<tr>
|
| 314 |
<th>Symbol</th>
|
| 315 |
<th>Trades</th>
|
| 316 |
<th>Volume</th>
|
|
@@ -764,7 +764,7 @@
|
|
| 764 |
return;
|
| 765 |
}
|
| 766 |
|
| 767 |
-
const pad = { top: 18, right:
|
| 768 |
const W = canvas.width - pad.left - pad.right;
|
| 769 |
const H = canvas.height - pad.top - pad.bottom;
|
| 770 |
const priceH = H * 0.70;
|
|
@@ -778,9 +778,10 @@
|
|
| 778 |
const maxV = Math.max(...points.map(p => p.volume), 1);
|
| 779 |
const toY = p => priceY + priceH - ((p - minP) / (maxP - minP)) * priceH;
|
| 780 |
|
| 781 |
-
const n
|
| 782 |
-
const slotW
|
| 783 |
-
const bodyW
|
|
|
|
| 784 |
|
| 785 |
// Price grid
|
| 786 |
ctx.strokeStyle = "#eee"; ctx.lineWidth = 1;
|
|
@@ -797,10 +798,19 @@
|
|
| 797 |
|
| 798 |
// Volume bars
|
| 799 |
points.forEach((p, i) => {
|
| 800 |
-
const x = pad.left + i * slotW + (slotW -
|
| 801 |
const bh = (p.volume / maxV) * volH;
|
| 802 |
ctx.fillStyle = p.close >= p.open ? "rgba(38,166,154,0.4)" : "rgba(239,83,80,0.4)";
|
| 803 |
-
ctx.fillRect(x, volY + volH - bh,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
});
|
| 805 |
|
| 806 |
// Candlestick wicks + bodies
|
|
@@ -1052,7 +1062,7 @@
|
|
| 1052 |
|
| 1053 |
let _sessionActive = false;
|
| 1054 |
let _sessionSuspended = false;
|
| 1055 |
-
let _sessionMode = "
|
| 1056 |
|
| 1057 |
function updateSessionBadge(active, suspended) {
|
| 1058 |
const badge = document.getElementById("session-badge");
|
|
@@ -1070,7 +1080,7 @@
|
|
| 1070 |
btn.className = "btn-day btn-suspend";
|
| 1071 |
dayBtn.textContent = "Start of Day";
|
| 1072 |
dayBtn.className = "btn-day btn-start";
|
| 1073 |
-
dayBtn.disabled = isAuto;
|
| 1074 |
} else if (suspended) {
|
| 1075 |
badge.className = "status suspended";
|
| 1076 |
text.textContent = "PAUSED";
|
|
@@ -1079,7 +1089,7 @@
|
|
| 1079 |
btn.className = "btn-day btn-resume";
|
| 1080 |
dayBtn.textContent = "End of Day";
|
| 1081 |
dayBtn.className = "btn-day btn-end";
|
| 1082 |
-
dayBtn.disabled =
|
| 1083 |
} else {
|
| 1084 |
badge.className = "status active";
|
| 1085 |
text.textContent = "ACTIVE";
|
|
@@ -1088,7 +1098,7 @@
|
|
| 1088 |
btn.className = "btn-day btn-suspend";
|
| 1089 |
dayBtn.textContent = "End of Day";
|
| 1090 |
dayBtn.className = "btn-day btn-end";
|
| 1091 |
-
dayBtn.disabled =
|
| 1092 |
}
|
| 1093 |
}
|
| 1094 |
|
|
|
|
| 201 |
Trading Dashboard
|
| 202 |
<span id="status" class="status connecting"><span class="dot"></span><span id="status-text">Connecting...</span></span>
|
| 203 |
<span id="session-badge" class="status idle"><span class="dot"></span><span id="session-text">IDLE</span></span>
|
| 204 |
+
<button id="day-btn" onclick="toggleDay()" class="btn-day btn-start" disabled>Start of Day</button>
|
| 205 |
<button id="suspend-btn" onclick="toggleSuspend()" class="btn-day btn-suspend" disabled>Suspend</button>
|
| 206 |
+
<button id="mode-btn" onclick="toggleMode()" class="btn-day btn-automatic">Automatic</button>
|
| 207 |
<a href="/fix/" style="margin-left:auto; padding:4px 14px; background:#6c757d; color:#fff; border-radius:20px; font-size:12px; font-weight:bold; text-decoration:none;">FIX UI</a>
|
| 208 |
</h1>
|
| 209 |
<div class="container">
|
|
|
|
| 310 |
<div id="stats-container" style="flex-grow:1; overflow-y:auto; padding: 10px;">
|
| 311 |
<table id="stats-table" style="width:100%; margin-bottom:10px; font-size:12px;">
|
| 312 |
<thead>
|
| 313 |
+
<tr style="background:#f0f0f0;">
|
| 314 |
<th>Symbol</th>
|
| 315 |
<th>Trades</th>
|
| 316 |
<th>Volume</th>
|
|
|
|
| 764 |
return;
|
| 765 |
}
|
| 766 |
|
| 767 |
+
const pad = { top: 18, right: 46, bottom: 32, left: 55 };
|
| 768 |
const W = canvas.width - pad.left - pad.right;
|
| 769 |
const H = canvas.height - pad.top - pad.bottom;
|
| 770 |
const priceH = H * 0.70;
|
|
|
|
| 778 |
const maxV = Math.max(...points.map(p => p.volume), 1);
|
| 779 |
const toY = p => priceY + priceH - ((p - minP) / (maxP - minP)) * priceH;
|
| 780 |
|
| 781 |
+
const n = points.length;
|
| 782 |
+
const slotW = W / n;
|
| 783 |
+
const bodyW = Math.max(1, Math.floor(slotW * 0.65));
|
| 784 |
+
const volBarW = Math.max(1, Math.floor(slotW * 0.35));
|
| 785 |
|
| 786 |
// Price grid
|
| 787 |
ctx.strokeStyle = "#eee"; ctx.lineWidth = 1;
|
|
|
|
| 798 |
|
| 799 |
// Volume bars
|
| 800 |
points.forEach((p, i) => {
|
| 801 |
+
const x = pad.left + i * slotW + (slotW - volBarW) / 2;
|
| 802 |
const bh = (p.volume / maxV) * volH;
|
| 803 |
ctx.fillStyle = p.close >= p.open ? "rgba(38,166,154,0.4)" : "rgba(239,83,80,0.4)";
|
| 804 |
+
ctx.fillRect(x, volY + volH - bh, volBarW, bh);
|
| 805 |
+
});
|
| 806 |
+
|
| 807 |
+
// Volume Y-axis labels (right side)
|
| 808 |
+
ctx.fillStyle = "#888"; ctx.font = "9px Arial"; ctx.textAlign = "left";
|
| 809 |
+
[1.0, 0.5, 0.0].forEach(frac => {
|
| 810 |
+
const val = Math.round(maxV * frac);
|
| 811 |
+
const y = volY + volH * (1 - frac);
|
| 812 |
+
const lbl = val >= 1000 ? (val / 1000).toFixed(1) + "k" : val.toString();
|
| 813 |
+
ctx.fillText(lbl, canvas.width - pad.right + 3, y + 3);
|
| 814 |
});
|
| 815 |
|
| 816 |
// Candlestick wicks + bodies
|
|
|
|
| 1062 |
|
| 1063 |
let _sessionActive = false;
|
| 1064 |
let _sessionSuspended = false;
|
| 1065 |
+
let _sessionMode = "automatic";
|
| 1066 |
|
| 1067 |
function updateSessionBadge(active, suspended) {
|
| 1068 |
const badge = document.getElementById("session-badge");
|
|
|
|
| 1080 |
btn.className = "btn-day btn-suspend";
|
| 1081 |
dayBtn.textContent = "Start of Day";
|
| 1082 |
dayBtn.className = "btn-day btn-start";
|
| 1083 |
+
dayBtn.disabled = isAuto; // auto handles start; manual can't
|
| 1084 |
} else if (suspended) {
|
| 1085 |
badge.className = "status suspended";
|
| 1086 |
text.textContent = "PAUSED";
|
|
|
|
| 1089 |
btn.className = "btn-day btn-resume";
|
| 1090 |
dayBtn.textContent = "End of Day";
|
| 1091 |
dayBtn.className = "btn-day btn-end";
|
| 1092 |
+
dayBtn.disabled = false; // always allow manual end
|
| 1093 |
} else {
|
| 1094 |
badge.className = "status active";
|
| 1095 |
text.textContent = "ACTIVE";
|
|
|
|
| 1098 |
btn.className = "btn-day btn-suspend";
|
| 1099 |
dayBtn.textContent = "End of Day";
|
| 1100 |
dayBtn.className = "btn-day btn-end";
|
| 1101 |
+
dayBtn.disabled = false; // always allow manual end
|
| 1102 |
}
|
| 1103 |
}
|
| 1104 |
|