From 42cf02cf3a4f36a1568cb6f15defb98100640c99 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Mon, 4 Aug 2025 20:28:48 +0300 Subject: [PATCH] web dash --- .../multi-exchange-data-aggregation/tasks.md | 3 + .vscode/settings.json | 2 + COBY/web/__init__.py | 11 + COBY/web/static/css/dashboard.css | 596 ++++++++++++++++++ COBY/web/static/css/heatmap.css | 427 +++++++++++++ COBY/web/static/index.html | 187 ++++++ 6 files changed, 1226 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 COBY/web/__init__.py create mode 100644 COBY/web/static/css/dashboard.css create mode 100644 COBY/web/static/css/heatmap.css create mode 100644 COBY/web/static/index.html diff --git a/.kiro/specs/multi-exchange-data-aggregation/tasks.md b/.kiro/specs/multi-exchange-data-aggregation/tasks.md index fc9e5a7..4b7e6ff 100644 --- a/.kiro/specs/multi-exchange-data-aggregation/tasks.md +++ b/.kiro/specs/multi-exchange-data-aggregation/tasks.md @@ -10,6 +10,7 @@ + - _Requirements: 1.1, 6.1, 7.3_ - [ ] 2. Implement TimescaleDB integration and database schema @@ -61,6 +62,8 @@ - _Requirements: 1.4, 6.3, 8.1_ - [ ] 6. Implement price bucket aggregation system + + - Create aggregation engine for converting order book data to price buckets - Implement configurable bucket sizes ($10 for BTC, $1 for ETH) - Create heatmap data structure generation from price buckets diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/COBY/web/__init__.py b/COBY/web/__init__.py new file mode 100644 index 0000000..1001897 --- /dev/null +++ b/COBY/web/__init__.py @@ -0,0 +1,11 @@ +""" +Web dashboard for the COBY system. +""" + +from .dashboard_server import create_dashboard_app +from .static_handler import StaticHandler + +__all__ = [ + 'create_dashboard_app', + 'StaticHandler' +] \ No newline at end of file diff --git a/COBY/web/static/css/dashboard.css b/COBY/web/static/css/dashboard.css new file mode 100644 index 0000000..44b7c0a --- /dev/null +++ b/COBY/web/static/css/dashboard.css @@ -0,0 +1,596 @@ +/* COBY Dashboard Styles */ + +:root { + --primary-color: #2563eb; + --secondary-color: #1e40af; + --success-color: #10b981; + --warning-color: #f59e0b; + --error-color: #ef4444; + --background-color: #0f172a; + --surface-color: #1e293b; + --surface-light: #334155; + --text-primary: #f8fafc; + --text-secondary: #cbd5e1; + --text-muted: #64748b; + --border-color: #475569; + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background-color: var(--background-color); + color: var(--text-primary); + line-height: 1.6; + overflow-x: hidden; +} + +/* Layout */ +#app { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* Header */ +.dashboard-header { + background: var(--surface-color); + border-bottom: 1px solid var(--border-color); + padding: 1rem 2rem; + box-shadow: var(--shadow); +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1400px; + margin: 0 auto; +} + +.logo { + font-size: 1.5rem; + font-weight: 700; + color: var(--primary-color); + letter-spacing: -0.025em; +} + +.header-info { + display: flex; + gap: 2rem; + align-items: center; +} + +.connection-status { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + font-weight: 500; +} + +.connection-status::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--error-color); + animation: pulse 2s infinite; +} + +.connection-status.connected::before { + background: var(--success-color); +} + +.last-update { + font-size: 0.875rem; + color: var(--text-secondary); +} + +/* Main Content */ +.dashboard-main { + flex: 1; + padding: 2rem; + max-width: 1400px; + margin: 0 auto; + width: 100%; + display: grid; + grid-template-columns: 1fr 300px; + grid-template-rows: auto auto 1fr; + gap: 2rem; + grid-template-areas: + "selector selector" + "overview overview" + "heatmap sidebar"; +} + +/* Symbol Selector */ +.symbol-selector { + grid-area: selector; + display: flex; + gap: 2rem; + align-items: center; + background: var(--surface-color); + padding: 1rem 1.5rem; + border-radius: 0.5rem; + border: 1px solid var(--border-color); +} + +.symbol-dropdown { + background: var(--surface-light); + border: 1px solid var(--border-color); + border-radius: 0.375rem; + padding: 0.5rem 1rem; + color: var(--text-primary); + font-size: 1rem; + min-width: 150px; +} + +.exchange-toggles { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.exchange-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--surface-light); + border: 1px solid var(--border-color); + border-radius: 0.375rem; + cursor: pointer; + transition: all 0.2s; +} + +.exchange-toggle:hover { + background: var(--primary-color); +} + +.exchange-toggle.active { + background: var(--primary-color); + border-color: var(--primary-color); +} + +/* Market Overview */ +.market-overview { + grid-area: overview; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; +} + +.metric-card { + background: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 0.5rem; + padding: 1.5rem; + text-align: center; + transition: transform 0.2s; +} + +.metric-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.metric-card h3 { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.metric-value { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 0.25rem; +} + +.metric-change { + font-size: 0.875rem; + font-weight: 500; +} + +.metric-change.positive { + color: var(--success-color); +} + +.metric-change.negative { + color: var(--error-color); +} + +/* Imbalance Bar */ +.imbalance-bar { + height: 4px; + background: var(--surface-light); + border-radius: 2px; + margin-top: 0.5rem; + position: relative; + overflow: hidden; +} + +.imbalance-bar::after { + content: ''; + position: absolute; + top: 0; + left: 50%; + width: 0%; + height: 100%; + background: var(--primary-color); + transition: all 0.3s ease; + transform: translateX(-50%); +} + +/* Liquidity Indicator */ +.liquidity-indicator { + height: 4px; + background: var(--surface-light); + border-radius: 2px; + margin-top: 0.5rem; + position: relative; + overflow: hidden; +} + +.liquidity-indicator::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 0%; + height: 100%; + background: linear-gradient(90deg, var(--error-color), var(--warning-color), var(--success-color)); + transition: width 0.3s ease; +} + +/* Side Panels */ +.side-panels { + grid-area: sidebar; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.panel { + background: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 0.5rem; + padding: 1rem; +} + +.panel h3 { + font-size: 1rem; + font-weight: 600; + margin-bottom: 1rem; + color: var(--text-primary); + border-bottom: 1px solid var(--border-color); + padding-bottom: 0.5rem; +} + +/* Order Book Panel */ +.order-book-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.section-header { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.5rem; +} + +.order-levels { + display: flex; + flex-direction: column; + gap: 2px; + max-height: 150px; + overflow-y: auto; +} + +.order-level { + display: flex; + justify-content: space-between; + padding: 0.25rem 0.5rem; + background: var(--surface-light); + border-radius: 0.25rem; + font-size: 0.75rem; + position: relative; + overflow: hidden; +} + +.order-level::before { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + background: rgba(59, 130, 246, 0.1); + transition: width 0.3s ease; +} + +.order-level.bid::before { + background: rgba(34, 197, 94, 0.1); +} + +.order-level.ask::before { + background: rgba(239, 68, 68, 0.1); +} + +.spread-indicator { + text-align: center; + padding: 0.5rem; + background: var(--surface-light); + border-radius: 0.25rem; + margin: 0.5rem 0; +} + +.spread-value { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); +} + +/* Exchange Status Panel */ +.exchange-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.exchange-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem; + background: var(--surface-light); + border-radius: 0.25rem; +} + +.exchange-name { + font-weight: 500; + text-transform: capitalize; +} + +.exchange-status { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-weight: 500; +} + +.exchange-status.connected { + background: rgba(34, 197, 94, 0.2); + color: var(--success-color); +} + +.exchange-status.disconnected { + background: rgba(239, 68, 68, 0.2); + color: var(--error-color); +} + +/* Statistics Panel */ +.stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.5rem; +} + +.stat-item { + display: flex; + flex-direction: column; + padding: 0.5rem; + background: var(--surface-light); + border-radius: 0.25rem; + text-align: center; +} + +.stat-label { + font-size: 0.75rem; + color: var(--text-secondary); + margin-bottom: 0.25rem; +} + +.stat-value { + font-size: 1rem; + font-weight: 600; + color: var(--text-primary); +} + +/* Footer */ +.dashboard-footer { + background: var(--surface-color); + border-top: 1px solid var(--border-color); + padding: 1rem 2rem; + margin-top: auto; +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1400px; + margin: 0 auto; + font-size: 0.875rem; + color: var(--text-secondary); +} + +/* Loading Overlay */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(15, 23, 42, 0.9); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +.loading-spinner { + width: 40px; + height: 40px; + border: 4px solid var(--surface-light); + border-top: 4px solid var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +.loading-text { + font-size: 1rem; + color: var(--text-secondary); +} + +/* Modal */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 1001; + backdrop-filter: blur(4px); +} + +.modal.show { + display: flex; + justify-content: center; + align-items: center; +} + +.modal-content { + background: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 0.5rem; + max-width: 400px; + width: 90%; + max-height: 80vh; + overflow-y: auto; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + border-bottom: 1px solid var(--border-color); +} + +.modal-header h3 { + margin: 0; + font-size: 1.125rem; + font-weight: 600; +} + +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + color: var(--text-secondary); + cursor: pointer; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-close:hover { + color: var(--text-primary); +} + +.modal-body { + padding: 1.5rem; +} + +/* Animations */ +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .dashboard-main { + grid-template-columns: 1fr; + grid-template-areas: + "selector" + "overview" + "heatmap" + "sidebar"; + } + + .side-panels { + flex-direction: row; + overflow-x: auto; + } + + .panel { + min-width: 250px; + } +} + +@media (max-width: 768px) { + .dashboard-main { + padding: 1rem; + gap: 1rem; + } + + .header-content { + padding: 0 1rem; + } + + .market-overview { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } + + .symbol-selector { + flex-direction: column; + align-items: stretch; + gap: 1rem; + } + + .exchange-toggles { + justify-content: center; + } +} + +/* Utility Classes */ +.hidden { + display: none !important; +} + +.fade-in { + animation: fadeIn 0.3s ease-in; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} \ No newline at end of file diff --git a/COBY/web/static/css/heatmap.css b/COBY/web/static/css/heatmap.css new file mode 100644 index 0000000..2ad9c56 --- /dev/null +++ b/COBY/web/static/css/heatmap.css @@ -0,0 +1,427 @@ +/* Heatmap Specific Styles */ + +.heatmap-container { + grid-area: heatmap; + background: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 0.5rem; + padding: 1rem; + display: flex; + flex-direction: column; + min-height: 600px; +} + +.heatmap-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--border-color); +} + +.heatmap-header h2 { + font-size: 1.25rem; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +.heatmap-controls { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.control-btn { + background: var(--surface-light); + border: 1px solid var(--border-color); + border-radius: 0.375rem; + padding: 0.5rem 1rem; + color: var(--text-primary); + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s; +} + +.control-btn:hover { + background: var(--primary-color); + border-color: var(--primary-color); +} + +.control-btn:active { + transform: translateY(1px); +} + +.control-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.control-label { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); + cursor: pointer; + margin-left: 1rem; +} + +.control-label input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--primary-color); +} + +/* Heatmap Wrapper */ +.heatmap-wrapper { + flex: 1; + display: grid; + grid-template-columns: 60px 1fr 60px; + grid-template-rows: 1fr; + gap: 0.5rem; + min-height: 400px; + margin-bottom: 1rem; +} + +.price-axis { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-end; + padding: 0.5rem; + font-size: 0.75rem; + color: var(--text-secondary); + background: var(--surface-light); + border-radius: 0.25rem; +} + +.volume-axis { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + padding: 0.5rem; + font-size: 0.75rem; + color: var(--text-secondary); + background: var(--surface-light); + border-radius: 0.25rem; +} + +.heatmap-canvas-container { + position: relative; + background: var(--background-color); + border-radius: 0.25rem; + overflow: hidden; + border: 1px solid var(--border-color); +} + +#heatmapCanvas { + display: block; + width: 100%; + height: 100%; + cursor: crosshair; +} + +.heatmap-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10; +} + +/* Heatmap Tooltip */ +.heatmap-tooltip { + position: absolute; + background: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 0.375rem; + padding: 0.75rem; + font-size: 0.875rem; + color: var(--text-primary); + box-shadow: var(--shadow-lg); + z-index: 20; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s; + max-width: 200px; +} + +.heatmap-tooltip.show { + opacity: 1; +} + +.tooltip-price { + font-weight: 600; + color: var(--primary-color); + margin-bottom: 0.25rem; +} + +.tooltip-volume { + color: var(--text-secondary); + margin-bottom: 0.25rem; +} + +.tooltip-side { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.tooltip-side.bid { + color: var(--success-color); +} + +.tooltip-side.ask { + color: var(--error-color); +} + +/* Heatmap Legend */ +.heatmap-legend { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + background: var(--surface-light); + border-radius: 0.375rem; + margin-top: auto; +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); +} + +.legend-color { + width: 16px; + height: 16px; + border-radius: 0.25rem; +} + +.bid-color { + background: linear-gradient(135deg, + rgba(34, 197, 94, 0.3) 0%, + rgba(34, 197, 94, 0.8) 100%); +} + +.ask-color { + background: linear-gradient(135deg, + rgba(239, 68, 68, 0.3) 0%, + rgba(239, 68, 68, 0.8) 100%); +} + +.intensity-scale { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.75rem; + color: var(--text-muted); +} + +.intensity-gradient { + width: 100px; + height: 8px; + background: linear-gradient(90deg, + rgba(59, 130, 246, 0.2) 0%, + rgba(59, 130, 246, 0.5) 50%, + rgba(59, 130, 246, 1) 100%); + border-radius: 4px; +} + +/* Crosshair */ +.heatmap-crosshair { + position: absolute; + pointer-events: none; + z-index: 15; +} + +.crosshair-line { + position: absolute; + background: rgba(59, 130, 246, 0.6); +} + +.crosshair-horizontal { + width: 100%; + height: 1px; + left: 0; +} + +.crosshair-vertical { + width: 1px; + height: 100%; + top: 0; +} + +/* Price Level Indicators */ +.price-level-indicator { + position: absolute; + right: -30px; + background: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 0.25rem; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + color: var(--text-primary); + white-space: nowrap; + z-index: 20; + transform: translateY(-50%); +} + +.price-level-indicator.bid { + border-left-color: var(--success-color); + border-left-width: 3px; +} + +.price-level-indicator.ask { + border-left-color: var(--error-color); + border-left-width: 3px; +} + +/* Volume Bars */ +.volume-bar { + position: absolute; + background: rgba(59, 130, 246, 0.3); + border-radius: 2px; + transition: all 0.3s ease; +} + +.volume-bar.bid { + background: rgba(34, 197, 94, 0.3); +} + +.volume-bar.ask { + background: rgba(239, 68, 68, 0.3); +} + +.volume-bar:hover { + opacity: 0.8; + transform: scaleX(1.05); +} + +/* Zoom Controls */ +.zoom-indicator { + position: absolute; + top: 10px; + right: 10px; + background: rgba(15, 23, 42, 0.8); + border: 1px solid var(--border-color); + border-radius: 0.25rem; + padding: 0.5rem; + font-size: 0.75rem; + color: var(--text-secondary); + backdrop-filter: blur(4px); + z-index: 25; +} + +/* Loading State */ +.heatmap-loading { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 400px; + color: var(--text-secondary); +} + +.heatmap-loading .loading-spinner { + width: 32px; + height: 32px; + margin-bottom: 1rem; +} + +/* Error State */ +.heatmap-error { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 400px; + color: var(--error-color); + text-align: center; +} + +.heatmap-error-icon { + font-size: 2rem; + margin-bottom: 1rem; +} + +.heatmap-error-message { + font-size: 1rem; + margin-bottom: 0.5rem; +} + +.heatmap-error-details { + font-size: 0.875rem; + color: var(--text-muted); +} + +/* Animation Classes */ +.heatmap-fade-in { + animation: heatmapFadeIn 0.5s ease-in; +} + +@keyframes heatmapFadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.heatmap-pulse { + animation: heatmapPulse 2s infinite; +} + +@keyframes heatmapPulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.7; + } +} + +/* Responsive Heatmap */ +@media (max-width: 768px) { + .heatmap-wrapper { + grid-template-columns: 50px 1fr 50px; + min-height: 300px; + } + + .heatmap-header { + flex-direction: column; + gap: 1rem; + align-items: stretch; + } + + .heatmap-controls { + justify-content: center; + flex-wrap: wrap; + } + + .heatmap-legend { + flex-direction: column; + gap: 1rem; + align-items: stretch; + } + + .intensity-scale { + justify-content: center; + } + + .price-axis, + .volume-axis { + font-size: 0.625rem; + padding: 0.25rem; + } +} \ No newline at end of file diff --git a/COBY/web/static/index.html b/COBY/web/static/index.html new file mode 100644 index 0000000..1e6a95a --- /dev/null +++ b/COBY/web/static/index.html @@ -0,0 +1,187 @@ + + + + + + COBY - Market Data Dashboard + + + + + + +
+ +
+
+

COBY

+
+ Connecting... + Never +
+
+
+ + +
+ +
+ +
+ +
+
+ + +
+
+

Mid Price

+
--
+
--
+
+
+

Spread

+
--
+
--
+
+
+

Volume Imbalance

+
--
+
+
+
+

Liquidity Score

+
--
+
+
+
+ + +
+
+

Order Book Heatmap

+
+ + + + +
+
+
+
+
+ +
+
+
+
+
+
+
+ Bids +
+
+
+ Asks +
+
+ Low +
+ High +
+
+
+ + +
+ +
+

Order Book

+
+
+
Asks
+
+
+
+ Spread: -- +
+
+
Bids
+
+
+
+
+ + +
+

Exchange Status

+
+ +
+
+ + +
+

Statistics

+
+
+ Updates/sec + 0 +
+
+ Total Points + 0 +
+
+ Bid Points + 0 +
+
+ Ask Points + 0 +
+
+
+
+
+ + +
+ +
+
+ + +
+
+
Loading market data...
+
+ + + + + + + + + + + \ No newline at end of file