COB data and dash

This commit is contained in:
Dobromir Popov
2025-06-18 16:23:47 +03:00
parent e238ce374b
commit 3cadae60f7
16 changed files with 7539 additions and 19 deletions

401
simple_cob_dashboard.py Normal file
View File

@ -0,0 +1,401 @@
#!/usr/bin/env python3
"""
Simple Windows-compatible COB Dashboard
"""
import asyncio
import json
import logging
import time
from datetime import datetime
from http.server import HTTPServer, SimpleHTTPRequestHandler
from socketserver import ThreadingMixIn
import threading
import webbrowser
from urllib.parse import urlparse, parse_qs
from core.multi_exchange_cob_provider import MultiExchangeCOBProvider
logger = logging.getLogger(__name__)
class COBHandler(SimpleHTTPRequestHandler):
"""HTTP handler for COB dashboard"""
def __init__(self, *args, cob_provider=None, **kwargs):
self.cob_provider = cob_provider
super().__init__(*args, **kwargs)
def do_GET(self):
"""Handle GET requests"""
path = urlparse(self.path).path
if path == '/':
self.serve_dashboard()
elif path.startswith('/api/cob/'):
self.serve_cob_data()
elif path == '/api/status':
self.serve_status()
else:
super().do_GET()
def serve_dashboard(self):
"""Serve the dashboard HTML"""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>COB Dashboard</title>
<style>
body { font-family: Arial; background: #1a1a1a; color: white; margin: 20px; }
.header { text-align: center; margin-bottom: 20px; }
.header h1 { color: #00ff88; }
.container { display: grid; grid-template-columns: 1fr 400px; gap: 20px; }
.chart-section { background: #2a2a2a; padding: 15px; border-radius: 8px; }
.orderbook-section { background: #2a2a2a; padding: 15px; border-radius: 8px; }
.orderbook-header { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px;
padding: 10px 0; border-bottom: 1px solid #444; font-weight: bold; }
.orderbook-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px;
padding: 3px 0; font-size: 0.9rem; }
.ask-row { color: #ff6b6b; }
.bid-row { color: #4ecdc4; }
.mid-price { text-align: center; padding: 15px; border: 1px solid #444;
margin: 10px 0; font-size: 1.2rem; font-weight: bold; color: #00ff88; }
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 20px; }
.stat-card { background: #2a2a2a; padding: 15px; border-radius: 8px; text-align: center; }
.stat-label { color: #888; font-size: 0.9rem; }
.stat-value { color: #00ff88; font-size: 1.3rem; font-weight: bold; }
.controls { text-align: center; margin-bottom: 20px; }
button { background: #333; color: white; border: 1px solid #555; padding: 8px 15px;
border-radius: 4px; margin: 0 5px; cursor: pointer; }
button:hover { background: #444; }
.status { padding: 10px; text-align: center; border-radius: 4px; margin-bottom: 20px; }
.connected { background: #1a4a1a; color: #00ff88; border: 1px solid #00ff88; }
.disconnected { background: #4a1a1a; color: #ff4444; border: 1px solid #ff4444; }
</style>
</head>
<body>
<div class="header">
<h1>Consolidated Order Book Dashboard</h1>
<div>Hybrid WebSocket + REST API | Real-time + Deep Market Data</div>
</div>
<div class="controls">
<button onclick="refreshData()">Refresh Data</button>
<button onclick="toggleSymbol()">Switch Symbol</button>
</div>
<div id="status" class="status disconnected">Loading...</div>
<div class="container">
<div class="chart-section">
<h3>Market Analysis</h3>
<div id="chart-placeholder">
<p>Chart data will be displayed here</p>
<div>Current implementation shows:</div>
<ul>
<li>✓ Real-time order book data (WebSocket)</li>
<li>✓ Deep market data (REST API)</li>
<li>✓ Session Volume Profile</li>
<li>✓ Hybrid data merging</li>
</ul>
</div>
</div>
<div class="orderbook-section">
<h3>Order Book Ladder</h3>
<div class="orderbook-header">
<div>Price</div>
<div>Size</div>
<div>Total</div>
</div>
<div id="asks-section"></div>
<div class="mid-price" id="mid-price">$--</div>
<div id="bids-section"></div>
</div>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-label">Total Liquidity</div>
<div class="stat-value" id="total-liquidity">--</div>
</div>
<div class="stat-card">
<div class="stat-label">Book Depth</div>
<div class="stat-value" id="book-depth">--</div>
</div>
<div class="stat-card">
<div class="stat-label">Spread</div>
<div class="stat-value" id="spread">-- bps</div>
</div>
</div>
<script>
let currentSymbol = 'BTC/USDT';
function refreshData() {
document.getElementById('status').textContent = 'Refreshing...';
fetch(`/api/cob/${encodeURIComponent(currentSymbol)}`)
.then(response => response.json())
.then(data => {
updateOrderBook(data);
updateStatus('Connected - Data updated', true);
})
.catch(error => {
console.error('Error:', error);
updateStatus('Error loading data', false);
});
}
function updateOrderBook(data) {
const bids = data.bids || [];
const asks = data.asks || [];
const stats = data.stats || {};
// Update asks section
const asksSection = document.getElementById('asks-section');
asksSection.innerHTML = '';
asks.sort((a, b) => a.price - b.price).reverse().forEach(ask => {
const row = document.createElement('div');
row.className = 'orderbook-row ask-row';
row.innerHTML = `
<div>$${ask.price.toFixed(2)}</div>
<div>${ask.size.toFixed(4)}</div>
<div>$${(ask.volume/1000).toFixed(0)}K</div>
`;
asksSection.appendChild(row);
});
// Update bids section
const bidsSection = document.getElementById('bids-section');
bidsSection.innerHTML = '';
bids.sort((a, b) => b.price - a.price).forEach(bid => {
const row = document.createElement('div');
row.className = 'orderbook-row bid-row';
row.innerHTML = `
<div>$${bid.price.toFixed(2)}</div>
<div>${bid.size.toFixed(4)}</div>
<div>$${(bid.volume/1000).toFixed(0)}K</div>
`;
bidsSection.appendChild(row);
});
// Update mid price
document.getElementById('mid-price').textContent = `$${(stats.mid_price || 0).toFixed(2)}`;
// Update stats
const totalLiq = (stats.bid_liquidity + stats.ask_liquidity) || 0;
document.getElementById('total-liquidity').textContent = `$${(totalLiq/1000).toFixed(0)}K`;
document.getElementById('book-depth').textContent = `${(stats.bid_levels || 0) + (stats.ask_levels || 0)}`;
document.getElementById('spread').textContent = `${(stats.spread_bps || 0).toFixed(2)} bps`;
}
function updateStatus(message, connected) {
const statusEl = document.getElementById('status');
statusEl.textContent = message;
statusEl.className = `status ${connected ? 'connected' : 'disconnected'}`;
}
function toggleSymbol() {
currentSymbol = currentSymbol === 'BTC/USDT' ? 'ETH/USDT' : 'BTC/USDT';
refreshData();
}
// Auto-refresh every 2 seconds
setInterval(refreshData, 2000);
// Initial load
refreshData();
</script>
</body>
</html>
"""
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_content.encode())
def serve_cob_data(self):
"""Serve COB data"""
try:
# Extract symbol from path
symbol = self.path.split('/')[-1].replace('%2F', '/')
if not self.cob_provider:
data = self.get_mock_data(symbol)
else:
data = self.get_real_data(symbol)
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
except Exception as e:
logger.error(f"Error serving COB data: {e}")
self.send_error(500, str(e))
def serve_status(self):
"""Serve status"""
status = {
'server': 'running',
'timestamp': datetime.now().isoformat(),
'cob_provider': 'active' if self.cob_provider else 'mock'
}
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(json.dumps(status).encode())
def get_real_data(self, symbol):
"""Get real data from COB provider"""
try:
cob_snapshot = self.cob_provider.get_consolidated_orderbook(symbol)
if not cob_snapshot:
return self.get_mock_data(symbol)
# Convert to dashboard format
bids = []
asks = []
for level in cob_snapshot.consolidated_bids[:20]:
bids.append({
'price': level.price,
'size': level.total_size,
'volume': level.total_volume_usd
})
for level in cob_snapshot.consolidated_asks[:20]:
asks.append({
'price': level.price,
'size': level.total_size,
'volume': level.total_volume_usd
})
return {
'symbol': symbol,
'bids': bids,
'asks': asks,
'stats': {
'mid_price': cob_snapshot.volume_weighted_mid,
'spread_bps': cob_snapshot.spread_bps,
'bid_liquidity': cob_snapshot.total_bid_liquidity,
'ask_liquidity': cob_snapshot.total_ask_liquidity,
'bid_levels': len(cob_snapshot.consolidated_bids),
'ask_levels': len(cob_snapshot.consolidated_asks),
'imbalance': cob_snapshot.liquidity_imbalance
}
}
except Exception as e:
logger.error(f"Error getting real data: {e}")
return self.get_mock_data(symbol)
def get_mock_data(self, symbol):
"""Get mock data for testing"""
base_price = 50000 if 'BTC' in symbol else 3000
bids = []
asks = []
# Generate mock bids
for i in range(20):
price = base_price - (i * 10)
size = 1.0 + (i * 0.1)
bids.append({
'price': price,
'size': size,
'volume': price * size
})
# Generate mock asks
for i in range(20):
price = base_price + 10 + (i * 10)
size = 1.0 + (i * 0.1)
asks.append({
'price': price,
'size': size,
'volume': price * size
})
return {
'symbol': symbol,
'bids': bids,
'asks': asks,
'stats': {
'mid_price': base_price + 5,
'spread_bps': 2.5,
'bid_liquidity': sum(b['volume'] for b in bids),
'ask_liquidity': sum(a['volume'] for a in asks),
'bid_levels': len(bids),
'ask_levels': len(asks),
'imbalance': 0.1
}
}
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Thread pool server"""
allow_reuse_address = True
def start_cob_dashboard():
"""Start the COB dashboard"""
print("Starting Simple COB Dashboard...")
# Initialize COB provider
cob_provider = None
try:
print("Initializing COB provider...")
cob_provider = MultiExchangeCOBProvider(symbols=['BTC/USDT', 'ETH/USDT'])
# Start in background thread
def run_provider():
asyncio.run(cob_provider.start_streaming())
provider_thread = threading.Thread(target=run_provider, daemon=True)
provider_thread.start()
time.sleep(2) # Give it time to connect
print("COB provider started")
except Exception as e:
print(f"Warning: COB provider failed to start: {e}")
print("Running in mock mode...")
# Start HTTP server
def handler(*args, **kwargs):
COBHandler(*args, cob_provider=cob_provider, **kwargs)
port = 8053
server = ThreadedHTTPServer(('localhost', port), handler)
print(f"COB Dashboard running at http://localhost:{port}")
print("Press Ctrl+C to stop")
# Open browser
try:
webbrowser.open(f'http://localhost:{port}')
except:
pass
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nStopping dashboard...")
server.shutdown()
if cob_provider:
asyncio.run(cob_provider.stop_streaming())
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
start_cob_dashboard()