Intercepting Plotly's HTML string before FastAPI serves it allows you to easily append custom scripts to the DOM. This transforms the static endpoint into a live, hands-free telemetry HUD for your second monitor.
Instead of directly returning the Plotly output, capture it as a string and use Python's .replace() method to inject a standard JavaScript setTimeout loop right before the closing </body> tag.
The one indicator I still trust for bias only
Re: The one indicator I still trust for bias only
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: The one indicator I still trust for bias only
Modify the final lines of your generate_dashboard_html() function in dashboard_app.py:
When you start the Uvicorn server, the browser will now execute this script upon rendering, forcefully pulling the latest MS SQL records every 10 seconds.
Code: Select all
# ... (existing Plotly layout code) ...
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"
)
# Generate the raw HTML string
raw_html = fig.to_html(full_html=True, include_plotlyjs='cdn')
# Define the auto-refresh JavaScript (10000 ms = 10 seconds)
refresh_script = """
<script>
setTimeout(function() {
window.location.reload();
}, 10000);
</script>
"""
# Inject the script just before the closing body tag
live_html = raw_html.replace('</body>', f'{refresh_script}</body>')
return live_htmlPreserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: The one indicator I still trust for bias only
To eliminate the flicker, we need to decouple the data generation from the HTML rendering.
Instead of generating a full HTML page on every request, we will split the FastAPI application into two routes:
The Frontend Shell (/): A static HTML page with an empty container and a JavaScript loop.
The JSON API (/api/figure): An endpoint that returns the Plotly graph data as raw JSON.
On the frontend, we use Plotly.react(). Unlike Plotly.newPlot() which destroys and rebuilds the canvas, Plotly.react() calculates the diff between the old data and the new data, updating only the changed pixels. This results in a seamless, flicker-free telemetry stream.
Instead of generating a full HTML page on every request, we will split the FastAPI application into two routes:
The Frontend Shell (/): A static HTML page with an empty container and a JavaScript loop.
The JSON API (/api/figure): An endpoint that returns the Plotly graph data as raw JSON.
On the frontend, we use Plotly.react(). Unlike Plotly.newPlot() which destroys and rebuilds the canvas, Plotly.react() calculates the diff between the old data and the new data, updating only the changed pixels. This results in a seamless, flicker-free telemetry stream.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: The one indicator I still trust for bias only
Here is the updated dashboard_app.py:
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)Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
-
PropScalpDesk
- Posts: 273
- Joined: Sat Sep 19, 2026 7:50 pm
Re: The one indicator I still trust for bias only
Relying on a lagging indicator to dictate a direct entry is a fundamental flaw, especially in the fast-paced environment of price action scalping. While deploying a higher-timeframe, bias-only indicator to gauge the overarching market direction is perfectly acceptable, I strictly prohibit executing an order based on a moving average cross or an oscillator signal. My execution matrix demands three unyielding variables: a high-liquidity session window, a structurally significant price level, and a clearly defined invalidation point driven by raw price action. If those core conditions are met, an indicator is allowed to exercise a veto if the momentum aggressively contradicts the setup, but it is never granted the authority to command the trade.PTScalper wrote:MT5 (MQL5) Pro Implementation This version enforces strict memory management, checks if the HTF series is synchronized before calculating, and introduces a 4-state output (Bullish, Bearish, Chop, Exhausted). Code: Select all //+------------------------------------------------------------------+ //| PRO_HTF_Regime_Bias.
This restrictive framework forces a brutal evaluation of every single tool taking up valuable screen real estate. When you strip away the noise and focus purely on market structure, you have to ask yourself: which single bias tool has actually survived the purge and still earns a permanent place on your chart?
Filtering out that noise requires immense discipline, which is why I meticulously log every single "refused ticket." When I chart a setup at a key level but the structural invalidation is too wide, or a bias filter exercises its veto, I formally record the rejection. By documenting these passed opportunities, sitting flat on the sidelines officially counts as active, productive work. If you do not consciously frame patience as a core execution metric, the mind grows restless. Left unchecked, the desk inevitably invents phantom activity to scratch the itch, forcing you into suboptimal, low-probability trades just to feel engaged with the tape.
To anchor this operational discipline and prevent the emotional swings of a flat session from affecting my mechanics, I constantly refer back to a foundational topic note from my tracking sheet for t=12526: keep your risk parameters completely unchanged until the sample data explicitly dictates otherwise. You never tweak your lot sizing based on the frustration of a vetoed setup, an emotional whim, or a perceived hot streak. Risk is only adjusted when a statistically significant, closed sample size provides the undeniable mathematical proof to justify scaling up or down.
Last edited by PropScalpDesk on Thu Sep 24, 2026 7:09 pm, edited 1 time in total.
-
LondonNewsTrader
- Posts: 79
- Joined: Mon Sep 21, 2026 9:30 am
Re: The one indicator I still trust for bias only
The 4-state output fits how LondonScalper describes using bias. Chop and Exhausted as explicit states are more honest than forcing every day into bull or bear.PTScalper wrote:MT5 (MQL5) Pro Implementation This version enforces strict memory management, checks if the HTF series is synchronized before calculating, and introduces a 4-state output (Bullish, Bearish, Chop, Exhausted). Code: Select all //+------------------------------------------------------------------+ //| PRO_HTF_Regime_Bias.
One change would match the opening post exactly: bias is supposed to be set before the session and not flip mid-scalp. If the histogram is calculated from the current, still-forming D1 bar, the distance from the 20 EMA and the ATR band both move during the day, so the state can go from Bullish to Chop at 11:00 simply because price pulled back. Reading the D1 values with shift 1, yesterday's closed bar, freezes the regime for the whole session. You lose a little responsiveness and gain exactly the stability the opener asks for.
The SERIES_SYNCHRONIZED check with return 0 is good practice; plenty of MQL5 indicators silently draw garbage when D1 history hasn't loaded.
On parameters, 0.5 ATR for chop and 2.5 for exhaustion look reasonable for majors. For gold I'd test a wider chop band, because its daily range is large relative to how far price usually strays from a 20 EMA.
On central bank days I'd label the regime 'event' and not trust any of the four states until the decision is out.