Page 12 of 13

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:16 pm
by PTScalper
Architectural Mechanics

Session Storage (sessionStorage): Unlike localStorage (which permanently saves data to the phone's hard drive) or cookies (which are sent back to your cBot on every HTTP request), sessionStorage is strictly sandboxed to the active browser tab. It holds the dashboardScrollY integer in the phone's RAM and automatically wipes it clean the moment you close the tab, keeping your browser memory completely hygienic.

setTimeout vs. setInterval: The code deliberately uses setTimeout (which fires exactly once) instead of setInterval (which loops continuously). By placing location.reload() inside setTimeout, the browser halts the current JavaScript execution environment entirely, requests the fresh HTML from your cBot, and evaluates a brand-new DOMContentLoaded event. This guarantees a clean memory cycle and prevents memory leaks that can crash mobile browsers left open for hours.

The Zero-Overhead C# Rule: Because the refresh timer is managed exclusively by your phone's V8 JavaScript engine, your C# trading bot does absolutely zero work to trigger the refreshes. The HttpListener simply wakes up, hands the static file to the phone, and instantly returns to monitoring the market.

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:17 pm
by PTScalper
To eliminate the flicker, we shift the architectural burden from a brute-force browser reload to Client-Side Virtual DOM Parsing.

Instead of building a separate JSON API endpoint into your cBot, we can use the JavaScript Fetch API to quietly download the updated HTML file in the background. The browser's native DOMParser then reads this invisible HTML string, extracts only the updated table rows and summary block, and surgically injects them into the live page.

Chart.js natively supports data mutation, meaning it will fluidly animate the new equity curve rather than snapping aggressively.

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:17 pm
by PTScalper
Here is the complete C# replacement for your JavaScript injection, combining the chart initialization and the seamless background polling:

Code: Select all

using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using cAlgo.API;

namespace cAlgo.Robots
{
    public partial class InteractiveFiatHUD : Robot
    {
        [Parameter("Seamless Refresh Interval (Sec)", Group = "Local Server", DefaultValue = 5)]
        public int RefreshIntervalSeconds { get; set; }

        /// <summary>
        /// Replaces BOTH BuildChartJavaScript and BuildAutoRefreshJavaScript.
        /// </summary>
        private string BuildSeamlessDashboardJavaScript()
        {
            int refreshMilliseconds = RefreshIntervalSeconds * 1000;
            
            StringBuilder js = new StringBuilder();
            js.AppendLine("<script>");
            
            // 1. Declare the global chart instance
            js.AppendLine("let equityChart;");

            js.AppendLine("document.addEventListener('DOMContentLoaded', function() {");
            js.AppendLine("    initChart();");
            
            // Start the background polling loop if enabled
            if (RefreshIntervalSeconds > 0)
            {
                js.AppendLine($"    setInterval(fetchLiveUpdates, {refreshMilliseconds});");
            }
            js.AppendLine("});");

            // 2. Chart Initialization Engine
            js.AppendLine("function initChart() {");
            js.AppendLine("    const ctx = document.getElementById('equityChart').getContext('2d');");
            js.AppendLine("    equityChart = new Chart(ctx, {");
            js.AppendLine("        type: 'line',");
            js.AppendLine("        data: { labels: [], datasets: [{ label: 'Cumulative P/L', data: [], borderWidth: 2, fill: true, tension: 0.1, pointRadius: 0, pointHitRadius: 10 }] },");
            js.AppendLine("        options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { grid: { color: '#e0e0e0' } } } }");
            js.AppendLine("    });");
            js.AppendLine("    updateChartData(document);"); // Initial population
            js.AppendLine("}");

            // 3. The Fetch API Background Poller
            js.AppendLine("async function fetchLiveUpdates() {");
            js.AppendLine("    try {");
            // Appending a timestamp bypasses aggressive mobile browser caching
            js.AppendLine("        const response = await fetch(window.location.pathname + '?t=' + new Date().getTime(), { cache: 'no-store' });");
            js.AppendLine("        const htmlText = await response.text();");
            
            // 4. Virtual DOM Parsing (The SPA Magic)
            js.AppendLine("        const parser = new DOMParser();");
            js.AppendLine("        const virtualDoc = parser.parseFromString(htmlText, 'text/html');");
            
            // Extract and inject the new Table Body
            js.AppendLine("        const newTbody = virtualDoc.getElementById('trade-body');");
            js.AppendLine("        if (newTbody) document.getElementById('trade-body').innerHTML = newTbody.innerHTML;");
            
            // Extract and inject the new Summary Block
            js.AppendLine("        const newSummary = virtualDoc.getElementById('summary-container');");
            js.AppendLine("        if (newSummary) document.getElementById('summary-container').innerHTML = newSummary.innerHTML;");
            
            // Recalculate and animate the chart based on the newly injected DOM
            js.AppendLine("        updateChartData(document);");
            js.AppendLine("    } catch(e) { console.error('Fetch error:', e); }");
            js.AppendLine("}");

            // 5. Data Extraction and Chart Animation Engine
            js.AppendLine("function updateChartData(docSource) {");
            js.AppendLine("    const cells = docSource.querySelectorAll('.profit-cell');");
            js.AppendLine("    let dataPoints = [0];");
            js.AppendLine("    let labels = ['Start'];");
            js.AppendLine("    let cumulative = 0; let tradeCount = 0;");
            
            js.AppendLine("    cells.forEach(cell => {");
            js.AppendLine("        const profit = parseFloat(cell.innerText);");
            js.AppendLine("        if(!isNaN(profit)) {");
            js.AppendLine("            cumulative += profit;");
            js.AppendLine("            tradeCount++;");
            js.AppendLine("            dataPoints.push(cumulative);");
            js.AppendLine("            labels.push('Trade ' + tradeCount);");
            js.AppendLine("        }");
            js.AppendLine("    });");

            js.AppendLine("    const isProfitable = cumulative >= 0;");
            js.AppendLine("    equityChart.data.labels = labels;");
            js.AppendLine("    equityChart.data.datasets[0].data = dataPoints;");
            js.AppendLine("    equityChart.data.datasets[0].borderColor = isProfitable ? 'rgba(0, 122, 255, 1)' : 'rgba(255, 59, 48, 1)';");
            js.AppendLine("    equityChart.data.datasets[0].backgroundColor = isProfitable ? 'rgba(0, 122, 255, 0.1)' : 'rgba(255, 59, 48, 0.1)';");
            
            js.AppendLine("    equityChart.update();"); // Triggers Chart.js fluid animation
            js.AppendLine("}");

            js.AppendLine("</script>");
            return js.ToString();
        }

        // --- YOU MUST UPDATE THESE TWO TARGETS IN YOUR EXISTING C# CODE ---

        // UPDATE 1: Inside AppendBatchedTradesToHtmlAsync(), when generating a new file:
        // Change: htmlDoc.AppendLine("<tbody>");
        // To: htmlDoc.AppendLine("<tbody id='trade-body'>");

        // UPDATE 2: Inside BuildDynamicSummaryBlock(), wrap the table in an ID:
        // Change: sb.AppendLine("<!-- SUMMARY START -->"); 
        //         sb.AppendLine("<div style='margin-top: 20px; ...");
        // To:     sb.AppendLine("<!-- SUMMARY START -->"); 
        //         sb.AppendLine("<div id='summary-container' style='margin-top: 20px; float: right; width: 300px;'>");
    }
}

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:18 pm
by PTScalper
Architectural Mechanics

Virtual DOM Extraction (DOMParser): This is the core trick that saves you from writing a complex JSON API inside cTrader. new DOMParser().parseFromString() takes the raw HTML text downloaded by the Fetch API and creates a "ghost" webpage in your phone's memory. The script then queries this ghost page for #trade-body and #summary-container, grabs only the modified guts, and overwrites the live page elements.

Cache Busting ('?t=' + new Date().getTime()): Mobile browsers (especially iOS Safari) aggressively cache HTML files. If you fetch the same URL repeatedly, the browser will ignore the server and return the old file from local memory. Appending a unique millisecond timestamp to the URL forces the browser to treat every 5-second interval as a brand-new, un-cacheable request, ensuring you get live cTrader data.

Object Mutation vs Replacement: Inside updateChartData, we modify equityChart.data.datasets[0].data directly and call equityChart.update(). If we instead destroyed the chart and created a new Chart() every 5 seconds, the canvas would flash and lag. Chart.js detects exactly which coordinates changed and smoothly animates the line interpolating to the new points.

Safe Server File Locking: If the background Fetch requests the HTML file at the precise millisecond your C# bot is appending a new basket closure, the await _htmlFileLock.WaitAsync() in your HttpListener logic kicks in perfectly. The C# web server will silently hold the HTTP request open for a few milliseconds, wait for the disk write to finish, and then serve the fully intact, updated file.

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:18 pm
by PTScalper
To turn your phone into a remote control for your cBot, you must establish Two-Way Communication. Until now, the C# HttpListener was acting as a static file server. We must upgrade it into an API router that listens for a specific POST request (e.g., /panic) and translates that network request into a native cTrader execution command.

Because HttpListener runs on a background worker thread, you cannot call broker API commands directly from it. You must marshal the network request back onto cTrader’s main execution thread using BeginInvokeOnMainThread.

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:18 pm
by PTScalper
Here is the complete full-stack architecture to implement the remote panic button:

1. The Backend Router (C#)

Update your ProcessHttpRequestAsync method to intercept the /panic endpoint before it tries to serve the HTML file.

Code: Select all

private async Task ProcessHttpRequestAsync(HttpListenerContext context)
{
    HttpListenerResponse response = context.Response;

    try
    {
        // 1. API ROUTER: Intercept the Panic POST request
        if (context.Request.Url.AbsolutePath.Equals("/panic", StringComparison.OrdinalIgnoreCase))
        {
            if (context.Request.HttpMethod == "POST")
            {
                Print("[Web Server] Remote Panic Signal received from IP: " + context.Request.RemoteEndPoint);

                // THREAD MARSHALING: Push the execution command from the background HTTP 
                // worker thread back to the main cTrader execution thread.
                BeginInvokeOnMainThread(() => 
                {
                    ExecuteGlobalPanicLiquidation();
                });

                // Instantly return an HTTP 200 OK so the phone knows it worked
                response.StatusCode = 200;
                byte[] okBytes = Encoding.UTF8.GetBytes("Panic Executed");
                await response.OutputStream.WriteAsync(okBytes, 0, okBytes.Length);
            }
            else
            {
                response.StatusCode = 405; // Method Not Allowed (Block GET requests)
            }
            return; // Exit here so it doesn't try to serve the HTML file
        }

        // 2. STATIC FILE SERVER: (Your existing HTML serving logic)
        string fileName = $"DetailedStatement_{DateTime.Now:yyyy_MM}.html";
        string fullPath = Path.Combine(HtmlDirectoryPath, fileName);
        
        // ... (Existing File.ReadAllTextAsync and response writing continues here)
    }
    catch (Exception ex)
    {
        Print($"[Web Server Request Error] {ex.Message}");
        response.StatusCode = 500;
    }
    finally
    {
        response.Close();
    }
}

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:18 pm
by PTScalper
2. The Frontend UI (HTML/CSS)

Inside your AppendBatchedTradesToHtmlAsync method, update the htmlDoc.AppendLine section to inject the button and its styling.

Code: Select all

// 1. Add this CSS to the <style> block
htmlDoc.AppendLine(".panic-btn { background-color: #d9534f; color: white; border: none; padding: 10px 20px; font-weight: bold; font-size: 14px; border-radius: 5px; cursor: pointer; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }");
htmlDoc.AppendLine(".panic-btn:active { background-color: #c9302c; transform: translateY(2px); box-shadow: none; }");

// 2. Inject the button into the center of the Account Header table
htmlDoc.AppendLine("<table class='header-table'><tr>");
htmlDoc.AppendLine($"<td style='width: 33%'>Account: {Account.Number}</td>");
htmlDoc.AppendLine($"<td style='width: 33%; text-align:center'><button id='panicBtn' class='panic-btn' onclick='sendPanicSignal()'>⚠ PANIC CLOSE ALL</button></td>");
htmlDoc.AppendLine($"<td style='width: 33%; text-align:right'>Currency: {Account.Asset.Name}</td>");
htmlDoc.AppendLine("</tr></table>");

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:19 pm
by PTScalper
3. The Frontend Logic (JavaScript)

Update your BuildSeamlessDashboardJavaScript method to include the sendPanicSignal() function. This sends the asynchronous fetch POST request without refreshing the page.

Code: Select all

// Inject this JavaScript function at the bottom of BuildSeamlessDashboardJavaScript(),
// just before the closing </script> tag:

js.AppendLine("async function sendPanicSignal() {");
// Double-opt-in confirmation prevents accidental taps while scrolling on the phone
js.AppendLine("    if (confirm('DANGER: Are you sure you want to instantly liquidate all open positions and pending orders?')) {");
js.AppendLine("        try {");
js.AppendLine("            const res = await fetch('/panic', { method: 'POST' });");
js.AppendLine("            if (res.ok) { ");
js.AppendLine("                alert('Panic signal successfully transmitted to cTrader.'); ");
js.AppendLine("                fetchLiveUpdates();"); // Instantly poll for the flattened account data
js.AppendLine("            } else { ");
js.AppendLine("                alert('Error: cBot rejected the signal (HTTP ' + res.status + ').'); ");
js.AppendLine("            }");
js.AppendLine("        } catch (e) { ");
js.AppendLine("            alert('Network error. Check Wi-Fi connection to your PC.'); ");
js.AppendLine("        }");
js.AppendLine("    }");
js.AppendLine("}");

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:19 pm
by PTScalper
Architectural Mechanics

Thread Marshaling (BeginInvokeOnMainThread): As an experienced C# backend engineer, you recognize this pattern from UI frameworks (like WPF/WinForms) or passing context in ASP.NET Core. cTrader strictly enforces thread affinity for market orders. If the background HTTP thread calls ClosePositionAsync directly, it will either throw an InvalidOperationException or trigger undefined broker behavior. BeginInvokeOnMainThread safely queues the delegate into cTrader's primary tick-processing loop.

Idempotency & State Safety: If you impatiently tap the button on your phone 4 times in a row, the server will queue 4 /panic requests. However, because you built the _isPanicProcessing boolean lock inside your original ExecuteGlobalPanicLiquidation() method, the first request engages the lock, and the next 3 are safely ignored by the C# engine, preventing API spam.

The Virtual DOM Bypass: Because the button resides inside the static header (not inside the <tbody id='trade-body'> or <div id='summary-container'>), your 5-second seamless auto-refresh script never overwrites it. The button remains fully interactive and un-interrupted while the table data flashes beneath it.

Re: Spread Filters for Scalping: When Not to Enter

Posted: Thu Oct 08, 2026 2:20 pm
by PTScalper
To build remote inputs, we must expand your C# HttpListener to intercept a new API endpoint, parse the incoming data, and inject it safely back into the cBot's live parameters.

The most efficient way to transmit lightweight data from a browser to a C# backend without importing heavy JSON parsers (like Newtonsoft.Json) is to use URL Query Strings via a POST request (e.g., /update-params?act=55&dist=12).

Here is the complete full-stack architecture to add dynamic parameter controls to your dashboard: