Page 5 of 5

Re: The one indicator I still trust for bias only

Posted: Fri Sep 18, 2026 7:41 pm
by PTScalper
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.

Re: The one indicator I still trust for bias only

Posted: Fri Sep 18, 2026 7:41 pm
by PTScalper
Modify the final lines of your generate_dashboard_html() function in dashboard_app.py:

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_html
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.

Re: The one indicator I still trust for bias only

Posted: Fri Sep 18, 2026 7:42 pm
by PTScalper
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.

Re: The one indicator I still trust for bias only

Posted: Fri Sep 18, 2026 7:42 pm
by PTScalper
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)

Re: The one indicator I still trust for bias only

Posted: Thu Sep 24, 2026 1:57 am
by PropScalpDesk
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.
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.

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.

Re: The one indicator I still trust for bias only

Posted: Thu Sep 24, 2026 10:28 am
by LondonNewsTrader
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.
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.

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.