Code: Select all
import pyodbc
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from fastapi import FastAPI, Response
from fastapi.responses import HTMLResponse
import uvicorn
app = FastAPI(title="Microstructure Telemetry")
def get_figure_json():
# 1. Fetch live data
conn_str = (
"DRIVER={ODBC Driver 17 for SQL Server};"
"SERVER=localhost;"
"DATABASE=AlgoTelemetry;"
"Trusted_Connection=yes;"
)
try:
conn = pyodbc.connect(conn_str)
query = """
SELECT Timestamp, Symbol, Direction, Regime, Reason,
SpreadInPips, TopAskVolume, TopBidVolume
FROM TradeRejections
WHERE CAST(Timestamp AS DATE) = CAST(GETUTCDATE() AS DATE)
ORDER BY Timestamp ASC
"""
df = pd.read_sql(query, conn)
conn.close()
except Exception as e:
df = pd.DataFrame() # Handle connection failure gracefully
# 2. Handle Empty Data State
if df.empty:
empty_fig = go.Figure()
empty_fig.update_layout(
template="plotly_dark",
paper_bgcolor="#111111", plot_bgcolor="#111111",
annotations=[dict(
text="No rejected trades logged for today. Excellent discipline.",
showarrow=False, font=dict(size=20, color="white"),
xref="paper", yref="paper", x=0.5, y=0.5
)]
)
return empty_fig.to_json()
# 3. Process Data
df['TotalVolume'] = df['TopBidVolume'] + df['TopAskVolume']
df['BookImbalance'] = (df['TopBidVolume'] - df['TopAskVolume']) / df['TotalVolume'].replace(0, 1)
regime_colors = {
'Bullish': '#008080', 'Bearish': '#800000',
'Chop': '#808080', 'Exhausted': '#DAA520'
}
df['Color'] = df['Regime'].map(regime_colors).fillna('#FFFFFF')
# 4. Build Plotly Figure
fig = make_subplots(
rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05,
row_heights=[0.5, 0.5],
subplot_titles=("Spread Spikes (Liquidity Sweeps)", "Order Book Imbalance (Bid vs Ask)")
)
# Top Pane: Spread
for regime in df['Regime'].unique():
df_sub = df[df['Regime'] == regime]
fig.add_trace(
go.Scatter(
x=df_sub['Timestamp'], y=df_sub['SpreadInPips'],
mode='markers+lines', name=f"Spread ({regime})",
marker=dict(size=8, color=df_sub['Color'].iloc[0], line=dict(width=1, color='white')),
line=dict(width=1, color='rgba(255,255,255,0.2)'),
customdata=df_sub[['Direction', 'Reason']],
hovertemplate="<b>Time:</b> %{x}<br><b>Spread:</b> %{y} pips<br><b>Attempted:</b> %{customdata[0]}<br><b>Reason:</b> %{customdata[1]}<extra></extra>"
), row=1, col=1
)
fig.add_hline(y=1.5, line_dash="dash", line_color="red", row=1, col=1)
# Bottom Pane: Imbalance
fig.add_trace(
go.Bar(
x=df['Timestamp'], y=df['BookImbalance'], name="Book Imbalance",
marker_color=['#008080' if val > 0 else '#800000' for val in df['BookImbalance']],
customdata=df[['TopBidVolume', 'TopAskVolume']],
hovertemplate="<b>Imbalance:</b> %{y:.2f}<br><b>Bid Vol:</b> %{customdata[0]}<br><b>Ask Vol:</b> %{customdata[1]}<extra></extra>"
), row=2, col=1
)
fig.update_layout(
title_text="Post-Session Microstructure Analysis", template="plotly_dark",
hovermode="x unified", height=800, showlegend=True,
paper_bgcolor="#111111", plot_bgcolor="#111111"
)
# Export directly to JSON string
return fig.to_json()
# --- FASTAPI ROUTES ---
@app.get("/api/figure")
async def get_figure():
"""Returns the raw Plotly figure JSON structure."""
fig_json = get_figure_json()
return Response(content=fig_json, media_type="application/json")
@app.get("/", response_class=HTMLResponse)
async def serve_dashboard():
"""Serves the static HTML shell that polls the API."""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>Microstructure Telemetry</title>
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<style>
body { background-color: #111111; margin: 0; padding: 0; color: white; font-family: sans-serif; }
#dashboard { width: 100vw; height: 100vh; }
</style>
</head>
<body>
<div id="dashboard"></div>
<script>
async function fetchAndUpdatePlot() {
try {
const response = await fetch('/api/figure');
if (!response.ok) throw new Error('Network response was not ok');
const fig = await response.json();
// Plotly.react diffs the DOM and updates only what changed (flicker-free)
Plotly.react('dashboard', fig.data, fig.layout);
} catch (error) {
console.error('Failed to fetch plot data:', error);
}
}
// Initial load immediately on page render
fetchAndUpdatePlot();
// Poll the FastAPI backend every 10 seconds asynchronously
setInterval(fetchAndUpdatePlot, 10000);
</script>
</body>
</html>
"""
return html_content
if __name__ == "__main__":
uvicorn.run("dashboard_app:app", host="127.0.0.1", port=8050, reload=True)