Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>HFT Environment Dashboard</title> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| :root { | |
| --bg-color: #121212; | |
| --panel-bg: #1e1e1e; | |
| --text-color: #e0e0e0; | |
| --muted-text: #a0a0a0; | |
| --border-color: #2d2d2d; | |
| --btn-bg: #333333; | |
| --btn-hover: #444444; | |
| --success-color: #81c784; | |
| --error-color: #e57373; | |
| --chart-color-1: #81c784; | |
| --font-family: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| } | |
| *, | |
| *::before, | |
| *::after { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: var(--font-family); | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| margin: 0; | |
| padding: 32px; | |
| display: flex; | |
| flex-direction: column; | |
| min-height: 100vh; | |
| font-weight: 300; | |
| } | |
| header { | |
| margin-bottom: 32px; | |
| padding-bottom: 16px; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| h1 { | |
| margin: 0; | |
| font-size: 1.25rem; | |
| font-weight: 400; | |
| letter-spacing: 0.5px; | |
| } | |
| .container { | |
| display: grid; | |
| grid-template-columns: 320px 1fr; | |
| gap: 32px; | |
| flex: 1; | |
| } | |
| .panel { | |
| background-color: var(--panel-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: 4px; | |
| padding: 24px; | |
| margin-bottom: 24px; | |
| width: 100%; | |
| } | |
| .panel h2 { | |
| margin-top: 0; | |
| margin-bottom: 20px; | |
| font-size: 1rem; | |
| font-weight: 500; | |
| color: var(--text-color); | |
| } | |
| .form-group { | |
| margin-bottom: 16px; | |
| display: flex; | |
| flex-direction: column; | |
| width: 100%; | |
| } | |
| label { | |
| font-size: 0.75rem; | |
| margin-bottom: 6px; | |
| color: var(--muted-text); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| input, | |
| select { | |
| background-color: var(--bg-color); | |
| border: 1px solid var(--border-color); | |
| color: var(--text-color); | |
| padding: 10px; | |
| border-radius: 2px; | |
| font-family: inherit; | |
| font-size: 0.875rem; | |
| width: 100%; | |
| } | |
| input:focus, | |
| select:focus { | |
| outline: none; | |
| border-color: #555555; | |
| } | |
| button { | |
| background-color: var(--btn-bg); | |
| border: 1px solid var(--border-color); | |
| padding: 10px 16px; | |
| border-radius: 2px; | |
| color: var(--text-color); | |
| font-weight: 400; | |
| cursor: pointer; | |
| width: 100%; | |
| transition: background-color 0.15s ease; | |
| font-family: inherit; | |
| margin-top: 8px; | |
| } | |
| button:hover { | |
| background-color: var(--btn-hover); | |
| } | |
| .output-section { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 24px; | |
| min-width: 0; | |
| } | |
| .chart-container { | |
| position: relative; | |
| height: 300px; | |
| width: 100%; | |
| } | |
| .output-box { | |
| background-color: var(--panel-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: 4px; | |
| display: flex; | |
| flex-direction: column; | |
| width: 100%; | |
| overflow: hidden; | |
| } | |
| .output-header { | |
| padding: 16px 20px; | |
| border-bottom: 1px solid var(--border-color); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| pre { | |
| margin: 0; | |
| padding: 20px; | |
| overflow-x: auto; | |
| color: var(--muted-text); | |
| font-size: 0.85rem; | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| font-family: "Courier New", Courier, monospace; | |
| } | |
| .log-pre { | |
| max-height: 150px; | |
| overflow-y: auto; | |
| color: #b0bec5; | |
| } | |
| .status { | |
| font-size: 0.75rem; | |
| padding: 4px 8px; | |
| border-radius: 2px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .grid-2-col { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 12px; | |
| width: 100%; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>HFT Environment Dashboard</h1> | |
| </header> | |
| <div class="container"> | |
| <div class="controls-section"> | |
| <div class="panel"> | |
| <h2>Reset Environment</h2> | |
| <div class="form-group"> | |
| <label for="task">Task</label> | |
| <select id="task"> | |
| <option value="basic_execution">Basic Execution</option> | |
| <option value="false_signal">False Signal</option> | |
| <option value="conflicting_signal">Conflicting Signal</option> | |
| <option value="flash_crash">Flash Crash</option> | |
| </select> | |
| </div> | |
| <div class="grid-2-col"> | |
| <div class="form-group"> | |
| <label for="max_steps">Max Steps</label> | |
| <input type="number" id="max_steps" placeholder="Optional" /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="tick_size">Tick Size</label> | |
| <input | |
| type="number" | |
| step="0.01" | |
| id="tick_size" | |
| placeholder="Optional" | |
| /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="inventory">Inventory</label> | |
| <input type="number" id="inventory" placeholder="Optional" /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="cash">Cash</label> | |
| <input | |
| type="number" | |
| step="0.01" | |
| id="cash" | |
| placeholder="Optional" | |
| /> | |
| </div> | |
| </div> | |
| <button onclick="resetEnvironment()">Reset Environment</button> | |
| </div> | |
| <div class="panel"> | |
| <h2>Take Action (Step)</h2> | |
| <div class="grid-2-col"> | |
| <div class="form-group"> | |
| <label for="limit_buy_price">Limit Buy Price</label> | |
| <input type="number" step="0.01" id="limit_buy_price" /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="limit_buy_size">Limit Buy Size</label> | |
| <input type="number" id="limit_buy_size" /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="limit_ask_price">Limit Ask Price</label> | |
| <input type="number" step="0.01" id="limit_ask_price" /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="limit_ask_size">Limit Ask Size</label> | |
| <input type="number" id="limit_ask_size" /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="market_buy_size">Market Buy Size</label> | |
| <input type="number" id="market_buy_size" /> | |
| </div> | |
| <div class="form-group"> | |
| <label for="market_ask_size">Market Ask Size</label> | |
| <input type="number" id="market_ask_size" /> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="cancel_order_id">Cancel Order ID</label> | |
| <input type="text" id="cancel_order_id" placeholder="Optional" /> | |
| </div> | |
| <button onclick="stepEnvironment()">Execute Step</button> | |
| </div> | |
| <div class="panel"> | |
| <h2>Environment State</h2> | |
| <button onclick="fetchState()">Refresh State</button> | |
| </div> | |
| </div> | |
| <div class="output-section"> | |
| <div class="panel"> | |
| <h2>Reward per Time Step</h2> | |
| <div class="chart-container"> | |
| <canvas id="rewardChart"></canvas> | |
| </div> | |
| </div> | |
| <div class="grid-2-col"> | |
| <div class="output-box"> | |
| <div class="output-header"> | |
| <span>Observation</span> | |
| <span id="obs_status" class="status">Waiting</span> | |
| </div> | |
| <pre id="observation_output">No data yet.</pre> | |
| </div> | |
| <div class="output-box"> | |
| <div class="output-header"> | |
| <span>State</span> | |
| <span id="state_status" class="status">Waiting</span> | |
| </div> | |
| <pre id="state_output">No data yet.</pre> | |
| </div> | |
| </div> | |
| <div class="output-box"> | |
| <div class="output-header"> | |
| <span>Action Log</span> | |
| </div> | |
| <pre id="action_log" class="log-pre">System Initialized.</pre> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const baseUrl = "{{ api_url }}"; | |
| let chartInstance = null; | |
| function initChart() { | |
| const ctx = document.getElementById("rewardChart").getContext("2d"); | |
| Chart.defaults.color = "#a0a0a0"; | |
| Chart.defaults.font.family = "'Inter', sans-serif"; | |
| chartInstance = new Chart(ctx, { | |
| type: "line", | |
| data: { | |
| labels: [], | |
| datasets: [ | |
| { | |
| label: "Reward", | |
| data: [], | |
| borderColor: "#81c784", | |
| backgroundColor: "rgba(129, 199, 132, 0.1)", | |
| borderWidth: 2, | |
| tension: 0.1, | |
| fill: true, | |
| pointRadius: 3, | |
| }, | |
| ], | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| x: { | |
| title: { display: true, text: "Time" }, | |
| grid: { color: "#2d2d2d" }, | |
| }, | |
| y: { | |
| title: { display: true, text: "Reward" }, | |
| grid: { color: "#2d2d2d" }, | |
| }, | |
| }, | |
| plugins: { legend: { display: false } }, | |
| }, | |
| }); | |
| } | |
| function updateOutput(elementId, statusId, data, isError = false) { | |
| const el = document.getElementById(elementId); | |
| const statusEl = document.getElementById(statusId); | |
| el.textContent = JSON.stringify(data, null, 2); | |
| if (isError) { | |
| el.style.color = "var(--error-color)"; | |
| statusEl.textContent = "Error"; | |
| } else { | |
| el.style.color = "var(--text-color)"; | |
| statusEl.textContent = "Success"; | |
| } | |
| } | |
| function appendLog(message) { | |
| const logBox = document.getElementById("action_log"); | |
| const timestamp = new Date().toLocaleTimeString("en-US", { | |
| hour12: false, | |
| }); | |
| logBox.textContent += `\n[${timestamp}] ${message}`; | |
| logBox.scrollTop = logBox.scrollHeight; | |
| } | |
| function getTask() { | |
| return document.getElementById("task").value; | |
| } | |
| async function resetEnvironment() { | |
| const task = getTask(); | |
| let url = `${baseUrl}/reset?task=${task}`; | |
| const params = ["max_steps", "tick_size", "inventory", "cash"]; | |
| params.forEach((param) => { | |
| const val = document.getElementById(param).value; | |
| if (val) { | |
| url += `&${param}=${val}`; | |
| } | |
| }); | |
| try { | |
| const response = await fetch(url, { | |
| method: "POST", | |
| credentials: "include", | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) throw data; | |
| chartInstance.data.labels = []; | |
| chartInstance.data.datasets[0].data = []; | |
| chartInstance.update(); | |
| appendLog(`RESET - Task: ${task}`); | |
| updateOutput("observation_output", "obs_status", data); | |
| await fetchState(); | |
| } catch (error) { | |
| appendLog(`ERROR - Failed to reset environment`); | |
| updateOutput("observation_output", "obs_status", error, true); | |
| } | |
| } | |
| async function stepEnvironment() { | |
| const task = getTask(); | |
| const actionData = {}; | |
| let logActionStr = ""; | |
| const numFields = [ | |
| "limit_buy_price", | |
| "limit_buy_size", | |
| "limit_ask_price", | |
| "limit_ask_size", | |
| "market_buy_size", | |
| "market_ask_size", | |
| ]; | |
| numFields.forEach((field) => { | |
| const val = document.getElementById(field).value; | |
| if (val !== "") actionData[field] = Number(val); | |
| }); | |
| const cancelId = document.getElementById("cancel_order_id").value; | |
| if (cancelId !== "") actionData["cancel_order_id"] = cancelId; | |
| if (actionData.limit_buy_size && actionData.limit_buy_price) { | |
| logActionStr = `LIMIT BUY ${actionData.limit_buy_size} @ ${actionData.limit_buy_price}`; | |
| } else if (actionData.limit_ask_size && actionData.limit_ask_price) { | |
| logActionStr = `LIMIT ASK ${actionData.limit_ask_size} @ ${actionData.limit_ask_price}`; | |
| } else if (actionData.market_buy_size) { | |
| logActionStr = `MARKET BUY ${actionData.market_buy_size}`; | |
| } else if (actionData.market_ask_size) { | |
| logActionStr = `MARKET ASK ${actionData.market_ask_size}`; | |
| } else if (actionData.cancel_order_id) { | |
| logActionStr = `CANCEL ${actionData.cancel_order_id}`; | |
| } else { | |
| logActionStr = `ACTION: ${JSON.stringify(actionData)}`; | |
| } | |
| try { | |
| const response = await fetch(`${baseUrl}/step?task=${task}`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| credentials: "include", | |
| body: JSON.stringify(actionData), | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) throw data; | |
| if (data.time !== undefined && data.reward !== undefined) { | |
| chartInstance.data.labels.push(data.time.toFixed(2)); | |
| chartInstance.data.datasets[0].data.push(data.reward); | |
| chartInstance.update(); | |
| } | |
| appendLog( | |
| `${logActionStr} -> Reward: ${data.reward !== undefined ? data.reward : "N/A"}`, | |
| ); | |
| updateOutput("observation_output", "obs_status", data); | |
| await fetchState(); | |
| } catch (error) { | |
| appendLog(`ERROR - Failed to execute: ${logActionStr}`); | |
| updateOutput("observation_output", "obs_status", error, true); | |
| } | |
| } | |
| async function fetchState() { | |
| const task = getTask(); | |
| try { | |
| const response = await fetch(`${baseUrl}/state?task=${task}`, { | |
| method: "GET", | |
| credentials: "include", | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) throw data; | |
| updateOutput("state_output", "state_status", data); | |
| } catch (error) { | |
| updateOutput("state_output", "state_status", error, true); | |
| } | |
| } | |
| window.onload = initChart; | |
| </script> | |
| </body> | |
| </html> | |