Page 2 of 2
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:49 am
by PTScalper
Because cTrader’s algorithmic environment (cAlgo) is built entirely on the .NET framework, we can bypass the clunky file-handling limitations of MQL and leverage native C# libraries. We can use System.Diagnostics.Stopwatch for highly precise microsecond network telemetry and System.IO.StreamWriter to write the Total Cost Analysis (TCA) data directly to your Windows file system.
To run this as a single-execution benchmarking script, it is structured as a cBot that executes the trade in OnStart() and then immediately terminates itself via Stop().
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:49 am
by PTScalper
cTrader: Pro Execution Telemetry cBot (C#)
Important Configuration: You must assign AccessRights.FileSystem in the Robot attribute; otherwise, cTrader will sandbox the cBot and block it from generating the CSV file.
Code: Select all
using System;
using System.IO;
using System.Diagnostics;
using cAlgo.API;
namespace cAlgo.Robots
{
// FileSystem access is required to bypass the sandbox and write the CSV telemetry log
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
public class ExecutionTelemetry_Pro : Robot
{
[Parameter("Execution Volume (Lots)", DefaultValue = 1.0)]
public double InpLotSize { get; set; }
[Parameter("Max Slippage (Pips, 0 = True Market)", DefaultValue = 0)]
public double InpMaxSlippage { get; set; }
[Parameter("Telemetry File Name", DefaultValue = "BrokerTelemetry_cTrader.csv")]
public string InpFileName { get; set; }
protected override void OnStart()
{
// Convert standard lots to broker-specific units (e.g., 100,000 for Forex)
double volumeInUnits = Symbol.QuantityToVolumeInUnits(InpLotSize);
// Pre-Execution Microstructure Snapshot
double askPrice = Symbol.Ask;
double bidPrice = Symbol.Bid;
double initialSpread = (askPrice - bidPrice) / Symbol.PipSize;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
TradeResult result;
// Dispatch order synchronously to measure complete RTT latency
// If slippage is capped, use the matching engine's native Range Order
if (InpMaxSlippage > 0)
{
result = ExecuteMarketRangeOrder(TradeType.Buy, SymbolName, volumeInUnits, InpMaxSlippage, askPrice, "ProBench");
}
else
{
result = ExecuteMarketOrder(TradeType.Buy, SymbolName, volumeInUnits, "ProBench");
}
stopwatch.Stop();
double latencyMs = stopwatch.Elapsed.TotalMilliseconds;
if (result.IsSuccessful)
{
Position pos = result.Position;
// cTrader natively aggregates partial fills into a unified EntryPrice (VWAP)
double execPrice = pos.EntryPrice;
double slippagePips = (execPrice - askPrice) / Symbol.PipSize;
double commission = pos.Commissions; // Captures real-time entry commissions
LogToCSV(SymbolName, InpLotSize, initialSpread, askPrice, execPrice, slippagePips, commission, latencyMs);
Print($"Success | Vol: {InpLotSize:F2} | Slip: {slippagePips:F1} pips | Latency: {latencyMs:F2} ms | CSV Saved");
}
else
{
Print($"Execution Failed: {result.Error} | Latency: {latencyMs:F2} ms");
}
// Terminate cBot immediately after the single benchmark execution
Stop();
}
private void LogToCSV(string sym, double vol, double spread, double reqPrice, double execPrice, double slip, double comm, double lat)
{
// Dynamically map to the Documents folder for easy database/Excel retrieval
string dirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "cAlgo", "Telemetry");
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
string filePath = Path.Combine(dirPath, InpFileName);
bool fileExists = File.Exists(filePath);
try
{
using (StreamWriter sw = new StreamWriter(filePath, true))
{
if (!fileExists)
{
sw.WriteLine("Timestamp,Symbol,Volume_Lots,Spread_Pips,Req_Price,Exec_Price,Slippage_Pips,Commission,Latency_ms");
}
// High-precision server timestamp for exact session mapping
string timestamp = Server.Time.ToString("yyyy-MM-dd HH:mm:ss.fff");
sw.WriteLine($"{timestamp},{sym},{vol:F2},{spread:F1},{reqPrice},{execPrice},{slip:F1},{comm:F2},{lat:F2}");
}
}
catch (Exception ex)
{
Print($"File Write Error: {ex.Message}");
}
}
}
}
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:50 am
by PTScalper
Architectural Differences for cTrader
Native VWAP Aggregation: Unlike MT5 where you have to parse historical deals to calculate the average fill price, cTrader's TradeResult.Position.EntryPrice automatically calculates the Volume-Weighted Average Price across all consumed liquidity tiers natively.
Matching Engine Range Orders: If you set InpMaxSlippage to greater than 0, the script utilizes ExecuteMarketRangeOrder. This delegates the slippage constraint directly to the broker's backend—if the broker's liquidity pool is too shallow to fill your volume within that limit, the matching engine rejects it instantly, which is an excellent stress test for firm LPs versus "last look" dealer plugins.
Data Storage: The telemetry file won't be hidden deep in an obscure AppData terminal folder. It automatically creates a new Telemetry directory directly in Documents\cAlgo\Telemetry\BrokerTelemetry_cTrader.csv.
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:51 am
by PTScalper
To elevate the cTrader telemetry to a true institutional standard, we must move beyond measuring slippage against Level 1 (Top-of-Book) prices. Since you are analyzing market microstructure and liquidity sweeps, a professional script must read the Level 2 Depth of Market (DoM) before execution.
By analyzing the visible liquidity tiers (as shown in your DoM screenshots), the script calculates the exact predicted Volume-Weighted Average Price (VWAP) for your intended lot size. When the order is executed, it compares the Expected L2 VWAP against the Actual Executed VWAP. Any deviation here isolates "invisible" slippage caused by broker latency, stale quotes, or "last look" rejections, separating it from natural market impact.
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:51 am
by PTScalper
cTrader: Level 2 Microstructure Telemetry cBot
This C# cBot queries the MarketDepth API to mathematically sweep the order book, calculates the anticipated cost, executes the benchmark, and logs the TCA (Total Cost Analysis) metrics to a CSV.
Code: Select all
using System;
using System.IO;
using System.Diagnostics;
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
public class L2_Microstructure_Benchmark : Robot
{
[Parameter("Execution Volume (Lots)", DefaultValue = 5.0)]
public double InpLotSize { get; set; }
[Parameter("Telemetry File Name", DefaultValue = "L2_BrokerTelemetry.csv")]
public string InpFileName { get; set; }
private MarketDepth _marketDepth;
protected override void OnStart()
{
// Subscribe to Level 2 Order Book
_marketDepth = MarketData.GetMarketDepth(SymbolName);
// Allow a brief moment for the DoM collection to sync with the server
if (_marketDepth.AskEntries.Count == 0)
{
Print("Level 2 DoM is empty. Ensure you have L2 feed active and try again.");
Stop();
return;
}
double volumeInUnits = Symbol.QuantityToVolumeInUnits(InpLotSize);
// 1. Calculate Predicted L2 VWAP based on visible liquidity tiers
double predictedVwap = CalculatePredictedVwap(volumeInUnits, true);
if (predictedVwap < 0)
{
Print("Insufficient liquidity in the visible order book for this volume.");
Stop();
return;
}
double topOfBookAsk = Symbol.Ask;
double topOfBookBid = Symbol.Bid;
double initialSpreadPips = (topOfBookAsk - topOfBookBid) / Symbol.PipSize;
// Calculate natural market impact (Expected Sweep Penalty)
double expectedMarketImpactPips = (predictedVwap - topOfBookAsk) / Symbol.PipSize;
// 2. Dispatch Order and measure RTT Latency
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
TradeResult result = ExecuteMarketOrder(TradeType.Buy, SymbolName, volumeInUnits, "L2_Bench");
stopwatch.Stop();
double latencyMs = stopwatch.Elapsed.TotalMilliseconds;
if (result.IsSuccessful)
{
Position pos = result.Position;
double actualVwap = pos.EntryPrice;
double totalSlippagePips = (actualVwap - topOfBookAsk) / Symbol.PipSize;
// Invisible Slippage = Total Slippage - Expected Sweep Penalty
double invisibleSlippagePips = totalSlippagePips - expectedMarketImpactPips;
double commission = pos.Commissions;
LogToCSV(SymbolName, InpLotSize, initialSpreadPips, topOfBookAsk, predictedVwap, actualVwap, expectedMarketImpactPips, invisibleSlippagePips, commission, latencyMs);
Print($"[L2 BENCHMARK] Vol: {InpLotSize:F2} | Latency: {latencyMs:F2} ms");
Print($"Top-of-Book: {topOfBookAsk} | Predicted VWAP: {predictedVwap:F5} | Actual VWAP: {actualVwap:F5}");
Print($"Market Impact: {expectedMarketImpactPips:F2} pips | Unexplained Slippage: {invisibleSlippagePips:F2} pips");
}
else
{
Print($"Execution Failed: {result.Error} | Latency: {latencyMs:F2} ms");
}
Stop();
}
// Simulates an order book sweep to calculate expected average fill price
private double CalculatePredictedVwap(double targetVolume, bool isBuy)
{
double accumulatedVolume = 0;
double totalCost = 0;
var entries = isBuy ? _marketDepth.AskEntries : _marketDepth.BidEntries;
foreach (var entry in entries)
{
double availableVolume = entry.VolumeInUnits;
double price = entry.Price;
if (accumulatedVolume + availableVolume >= targetVolume)
{
double remainingNeeded = targetVolume - accumulatedVolume;
totalCost += remainingNeeded * price;
accumulatedVolume += remainingNeeded;
break;
}
else
{
totalCost += availableVolume * price;
accumulatedVolume += availableVolume;
}
}
return accumulatedVolume >= targetVolume ? (totalCost / targetVolume) : -1;
}
private void LogToCSV(string sym, double vol, double spread, double topOfBook, double expectedVwap, double actualVwap, double expectedImpact, double invisibleSlip, double comm, double lat)
{
string dirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "cAlgo", "Telemetry");
if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
string filePath = Path.Combine(dirPath, InpFileName);
bool fileExists = File.Exists(filePath);
try
{
using (StreamWriter sw = new StreamWriter(filePath, true))
{
if (!fileExists)
{
sw.WriteLine("Timestamp,Symbol,Vol_Lots,Spread_Pips,Top_Book,Expected_VWAP,Actual_VWAP,Expected_Impact_Pips,Invisible_Slip_Pips,Commission,Latency_ms");
}
string timestamp = Server.Time.ToString("yyyy-MM-dd HH:mm:ss.fff");
sw.WriteLine($"{timestamp},{sym},{vol:F2},{spread:F1},{topOfBook},{expectedVwap},{actualVwap},{expectedImpact:F2},{invisibleSlip:F2},{comm:F2},{lat:F2}");
}
}
catch (Exception ex)
{
Print($"CSV Error: {ex.Message}");
}
}
}
}
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:51 am
by PTScalper
Analytical Breakdown of the L2 Telemetry
CalculatePredictedVwap(): This method reconstructs the math occurring on the matching engine. If you fire a 5-lot order, it iterates through MarketDepth.AskEntries, exhausting the 1-lot tier, then the 3-lot tier, and finally 1 lot of the third tier, outputting the exact blended price you should receive before latency is factored in.
expectedMarketImpactPips: This logs the penalty incurred strictly from the natural thinness of the broker's order book.
invisibleSlippagePips: This is the ultimate metric for broker quality. If this number is consistently above 0.0, the LPs are updating quotes faster than the broker can route your execution (latency arbitrage), or a virtual dealer plugin is intentionally holding the order to execute at a worse tier (asymmetric slippage). Institutional flows demand this number stay as close to 0.0 as possible regardless of lot size.
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:52 am
by PTScalper
Pine Script operates within a cloud-based sandbox, meaning it cannot natively measure network microsecond round-trip time (RTT), sweep Level 2 Depth of Market (DoM), or write directly to local CSV files.
To achieve an institutional-grade telemetry benchmark from TradingView, the architecture must be split. Pine Script acts as the Level 1 microstructure snapshot and execution trigger, packaging the exact market conditions into a JSON webhook. A custom webhook receiver—built in a backend environment like C# ASP.NET Core or PHP—then catches the payload, starts the latency stopwatch, dispatches the order via API (e.g., cTrader Open API or FIX API), and logs the resulting Volume-Weighted Average Price (VWAP) slip to a database.
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:53 am
by PTScalper
1. Pine Script (v5) Telemetry Trigger
This script captures the exact Level 1 pricing (Ask/Bid) and spread at the exact millisecond your logic triggers, formatting it into a JSON string for your webhook endpoint.
Code: Select all
//@version=5
strategy("Pro Execution Telemetry", overlay=true, calc_on_every_tick=true, margin_long=100, margin_short=100)
// Execution Inputs
var float lotSize = input.float(5.0, title="Execution Volume (Lots)")
var string webhookUrl = input.string("https://your-api-endpoint.com/webhook", title="Backend API URL")
// Variables to capture Level 1 Microstructure
var float currentAsk = na
var float currentBid = na
var float spreadPips = na
if barstate.isrealtime
currentAsk := syminfo.ask
currentBid := syminfo.bid
// Standardize spread to pips (handles Forex 5-digit vs JPY 3-digit)
spreadPips := (currentAsk - currentBid) / syminfo.mintick / 10
// Benchmark Trigger Condition (e.g., sweeping the high of the previous 15m candle)
triggerCondition = ta.crossover(close, high[1])
if triggerCondition and barstate.isrealtime
// Construct the JSON payload with TradingView's expected price
jsonPayload = '{"symbol": "' + syminfo.ticker + '", ' +
'"action": "BUY", ' +
'"volume": ' + str.tostring(lotSize) + ', ' +
'"tv_ask": ' + str.tostring(currentAsk) + ', ' +
'"tv_bid": ' + str.tostring(currentBid) + ', ' +
'"spread_pips": ' + str.tostring(spreadPips) + ', ' +
'"tv_timestamp": ' + str.tostring(timenow) + '}'
// Fire the webhook alert
alert(jsonPayload, alert.freq_once_per_bar_close)
// Visual marker on the chart for the requested Top-of-Book
label.new(bar_index, currentAsk, text="Req Ask: " + str.tostring(currentAsk), style=label.style_label_down, color=color.blue, textcolor=color.white)
Re: Tightest Spreads Forex: Low Latency Broker Test
Posted: Mon Sep 28, 2026 8:53 am
by PTScalper
2. The Total Cost Analysis (TCA) Pipeline
Because the execution does not happen locally on the TradingView server, the concept of "latency" is expanded. When your C# or PHP backend receives the JSON payload, it must calculate two distinct phases of slippage to isolate the broker's performance from network lag:
Phase 1: TradingView to Server Transit Lag: Compare the tv_timestamp from the JSON payload against the server's local UTC time upon receipt. If this delay is 200–500 ms (common with webhooks), the market may have already moved before the broker even saw the order.
Phase 2: Broker Execution RTT & L2 Sweeping: Just as in the cTrader/MQL scripts, your backend starts a Stopwatch the moment it fires the API command to the broker. When the API returns the executed EntryPrice (VWAP), you calculate the delta against the tv_ask.
If your backend logs a 300 ms webhook transit time but only a 5 ms broker RTT, any adverse slippage is likely due to the inherent delay of webhook-based infrastructure, not a shallow liquidity pool or a dealer plugin capping your fills. If you require zero-latency scalping, the webhook architecture must be abandoned entirely in favor of running the logic directly on a cross-connected VPS using local C# or MQL code.