Spread Filters for Scalping: When Not to Enter
Re: Spread Filters for Scalping: When Not to Enter
To draw an execution line and guarantee it appears in the screenshot, you must bridge the gap between cTrader’s execution engine and its visual rendering thread.
When a position closes, it instantly transfers from the Positions collection to the History collection. You use the History record to fetch the exact entry and exit coordinates, draw the line using Chart.DrawTrendLine, and momentarily pause the execution thread to allow the UI to paint the line before the Windows screen capture fires.
When a position closes, it instantly transfers from the Positions collection to the History collection. You use the History record to fetch the exact entry and exit coordinates, draw the line using Chart.DrawTrendLine, and momentarily pause the execution thread to allow the UI to paint the line before the Windows screen capture fires.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
Here is the isolated method to replace your previous TriggerStopLossScreenshot function:
Code: Select all
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using cAlgo.API;
namespace cAlgo.Robots
{
public partial class InteractiveFiatHUD : Robot
{
[Parameter("Keep Execution Lines on Chart", Group = "Visuals", DefaultValue = true)]
public bool KeepLinesOnChart { get; set; }
/// <summary>
/// Draws the trade path, captures the chart, and dispatches the Telegram payload.
/// </summary>
private void TriggerStopLossScreenshot(Position closedPosition)
{
// 1. Fetch exact execution data from the History collection
var historicalTrade = History.FirstOrDefault(t => t.PositionId == closedPosition.Id);
if (historicalTrade == null)
{
Print("[Screenshot Error] Could not locate historical trade data.");
return;
}
// 2. Generate unique object names based on the Position ID
string lineName = $"SL_Line_{historicalTrade.PositionId}";
string iconName = $"SL_Icon_{historicalTrade.PositionId}";
// 3. Draw the execution path
Chart.DrawTrendLine(lineName,
historicalTrade.EntryTime, historicalTrade.EntryPrice,
historicalTrade.ClosingTime, historicalTrade.ClosingPrice,
Color.Crimson, 3, LineStyle.Solid);
// 4. Draw a marker exactly where the Stop Loss was executed
ChartIconType exitMarker = historicalTrade.TradeType == TradeType.Buy
? ChartIconType.DownArrow
: ChartIconType.UpArrow;
Chart.DrawIcon(iconName, exitMarker, historicalTrade.ClosingTime, historicalTrade.ClosingPrice, Color.Yellow);
// 5. THE GRAPHICS BUFFER SAFEGUARD
// cBots execute on a separate thread from the UI. Drawing an object queues a render request.
// We must pause the bot's thread for exactly 50 milliseconds to allow the UI thread to physically
// paint the pixels on the screen before the camera snaps the picture.
Thread.Sleep(50);
// 6. SYNCHRONOUS CAPTURE
byte[] screenshotBytes = CaptureScreenToMemory();
// 7. Post-Capture Cleanup
if (!KeepLinesOnChart)
{
Chart.RemoveObject(lineName);
Chart.RemoveObject(iconName);
}
// 8. ASYNCHRONOUS UPLOAD
if (screenshotBytes != null && !string.IsNullOrWhiteSpace(TelegramToken))
{
string caption = $"🛑 <b>STOP LOSS HIT</b>\n" +
$"<b>Pair:</b> {historicalTrade.SymbolName}\n" +
$"<b>Type:</b> {historicalTrade.TradeType}\n" +
$"<b>Loss:</b> {historicalTrade.NetProfit:C2}\n" +
$"<b>Duration:</b> {historicalTrade.ClosingTime - historicalTrade.EntryTime:hh\\:mm\\:ss}";
Task.Run(() => SendTelegramScreenshotAsync(screenshotBytes, caption));
}
}
// ... (CaptureScreenToMemory and SendTelegramScreenshotAsync remain the same)
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
Architectural Mechanics
The History Lookup: You cannot rely on closedPosition.EntryTime and Server.Time to draw the line accurately. Slippage occurs during execution. By querying History.FirstOrDefault(t => t.PositionId == closedPosition.Id), you extract the absolute, broker-confirmed execution price and exact millisecond the trade was liquidated, ensuring your chart drawing connects the perfect mathematical coordinates.
The Thread Block (Thread.Sleep(50)): If you call CaptureScreenToMemory() immediately after Chart.DrawTrendLine(), the screenshot will almost always be missing the line. Thread.Sleep(50) is a deliberate, highly-controlled 50-millisecond thread block. Because this only fires after a position has safely closed, sacrificing 50ms of bot tick execution time carries absolutely zero financial risk, while guaranteeing a perfect snapshot.
Directional Execution Markers: The logic dynamically flips the ChartIconType based on trade direction. A failed Buy displays a Yellow Down-Arrow at the exit, while a failed Sell displays a Yellow Up-Arrow, making it instantly obvious on your phone screen whether you were squeezed on the bid or the ask.
Transient Rendering (KeepLinesOnChart = false): If you prefer a clean chart but still want the screenshots marked up, setting KeepLinesOnChart to false immediately deletes the drawn objects after CaptureScreenToMemory() runs. Because the image bytes are already locked in RAM, Telegram receives the marked-up image, but your live cTrader chart remains spotless.
The History Lookup: You cannot rely on closedPosition.EntryTime and Server.Time to draw the line accurately. Slippage occurs during execution. By querying History.FirstOrDefault(t => t.PositionId == closedPosition.Id), you extract the absolute, broker-confirmed execution price and exact millisecond the trade was liquidated, ensuring your chart drawing connects the perfect mathematical coordinates.
The Thread Block (Thread.Sleep(50)): If you call CaptureScreenToMemory() immediately after Chart.DrawTrendLine(), the screenshot will almost always be missing the line. Thread.Sleep(50) is a deliberate, highly-controlled 50-millisecond thread block. Because this only fires after a position has safely closed, sacrificing 50ms of bot tick execution time carries absolutely zero financial risk, while guaranteeing a perfect snapshot.
Directional Execution Markers: The logic dynamically flips the ChartIconType based on trade direction. A failed Buy displays a Yellow Down-Arrow at the exit, while a failed Sell displays a Yellow Up-Arrow, making it instantly obvious on your phone screen whether you were squeezed on the bid or the ask.
Transient Rendering (KeepLinesOnChart = false): If you prefer a clean chart but still want the screenshots marked up, setting KeepLinesOnChart to false immediately deletes the drawn objects after CaptureScreenToMemory() runs. Because the image bytes are already locked in RAM, Telegram receives the marked-up image, but your live cTrader chart remains spotless.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
To build a bulletproof rate-limiter for a basket liquidation, you must completely decouple the capture of the screenshot from the transmission of the screenshot.
When 5 trades close in the same millisecond, the bot must instantly snap 5 pictures and store them in RAM. It then places those image bytes into a thread-safe queue. A background worker wakes up, uploads the first image, goes to sleep for 2 seconds, wakes up to upload the second, and so on—all while your trading bot continues executing live market logic uninterrupted.
When 5 trades close in the same millisecond, the bot must instantly snap 5 pictures and store them in RAM. It then places those image bytes into a thread-safe queue. A background worker wakes up, uploads the first image, goes to sleep for 2 seconds, wakes up to upload the second, and so on—all while your trading bot continues executing live market logic uninterrupted.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
Here is the complete C# architecture utilizing a ConcurrentQueue and a SemaphoreSlim to strictly enforce the Telegram API rate limit:
Code: Select all
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using cAlgo.API;
namespace cAlgo.Robots
{
public partial class InteractiveFiatHUD : Robot
{
// 1. Thread-safe FIFO (First-In, First-Out) queue for cross-thread data transfer
private ConcurrentQueue<TelegramPayload> _uploadQueue = new ConcurrentQueue<TelegramPayload>();
// 2. Asynchronous lock to guarantee only one worker thread processes the queue at a time
private SemaphoreSlim _queueWorkerLock = new SemaphoreSlim(1, 1);
/// <summary>
/// Data structure to marry the binary image data to its specific text caption.
/// </summary>
private class TelegramPayload
{
public byte[] ImageBytes { get; set; }
public string Caption { get; set; }
}
private void TriggerStopLossScreenshot(Position closedPosition)
{
var historicalTrade = History.FirstOrDefault(t => t.PositionId == closedPosition.Id);
if (historicalTrade == null) return;
// ... (Your existing chart drawing logic and Thread.Sleep(50) go here)
// 3. SYNCHRONOUS CAPTURE
byte[] screenshotBytes = CaptureScreenToMemory();
if (screenshotBytes != null && !string.IsNullOrWhiteSpace(TelegramToken))
{
string caption = $"🛑 <b>STOP LOSS HIT</b>\n" +
$"<b>Pair:</b> {historicalTrade.SymbolName}\n" +
$"<b>Loss:</b> {historicalTrade.NetProfit:C2}";
// 4. Enqueue the data into RAM rather than calling the API directly
_uploadQueue.Enqueue(new TelegramPayload
{
ImageBytes = screenshotBytes,
Caption = caption
});
// 5. Nudge the background processor to start working
Task.Run(() => ProcessUploadQueueAsync());
}
}
/// <summary>
/// A background worker that safely drains the queue at a strictly controlled pace.
/// </summary>
private async Task ProcessUploadQueueAsync()
{
// The Gatekeeper: WaitAsync(0) checks if a worker is already running.
// If it is, this new task immediately aborts, letting the existing worker handle the queue.
if (!await _queueWorkerLock.WaitAsync(0))
return;
try
{
// Dequeue items one by one until the list is completely empty
while (_uploadQueue.TryDequeue(out TelegramPayload payload))
{
Print($"[Telegram Queue] Dispatching payload. ({_uploadQueue.Count} remaining in queue)");
// Await the actual network transmission
await SendTelegramScreenshotAsync(payload.ImageBytes, payload.Caption);
// THE THROTTLE: Force the background thread to sleep for exactly 2 seconds
// This satisfies the Telegram API rate limits without freezing cTrader.
if (!_uploadQueue.IsEmpty)
{
await Task.Delay(2000);
}
}
}
finally
{
// CRITICAL: Release the lock when the queue is empty so future trades can trigger a new worker.
_queueWorkerLock.Release();
}
}
// ... (SendTelegramScreenshotAsync remains exactly the same)
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
Architectural Mechanics
ConcurrentQueue<T>: Standard C# List or Queue objects are not thread-safe. If your main bot thread attempts to push a new image into a standard list at the exact millisecond the background thread is pulling an image out, the bot will suffer a fatal InvalidOperationException. ConcurrentQueue natively handles simultaneous read/write operations from multiple threads safely.
The Payload Wrapper: When queuing operations, you cannot pass raw variables separately. The TelegramPayload class acts as an envelope. It holds the heavy byte array (the picture) and the specific string (the caption) together, ensuring the correct caption is always sent with the correct image, even if 10 trades stack up in memory.
The Worker Gatekeeper (SemaphoreSlim): When 5 trades close instantly, Task.Run(() => ProcessUploadQueueAsync()) fires 5 times. Without SemaphoreSlim, you would spawn 5 parallel background workers that all instantly hit the Telegram API, completely defeating the purpose of the queue. _queueWorkerLock.WaitAsync(0) allows the first worker through and instantly rejects the other 4. The single active worker then loops through all 5 items sequentially.
Smart Delay Logic: Notice the if (!_uploadQueue.IsEmpty) wrapper around Task.Delay(2000). If the worker just dispatched the final item in the queue, there is no need to make the thread sleep for 2 seconds before tearing down. It finishes instantly, freeing system resources.
ConcurrentQueue<T>: Standard C# List or Queue objects are not thread-safe. If your main bot thread attempts to push a new image into a standard list at the exact millisecond the background thread is pulling an image out, the bot will suffer a fatal InvalidOperationException. ConcurrentQueue natively handles simultaneous read/write operations from multiple threads safely.
The Payload Wrapper: When queuing operations, you cannot pass raw variables separately. The TelegramPayload class acts as an envelope. It holds the heavy byte array (the picture) and the specific string (the caption) together, ensuring the correct caption is always sent with the correct image, even if 10 trades stack up in memory.
The Worker Gatekeeper (SemaphoreSlim): When 5 trades close instantly, Task.Run(() => ProcessUploadQueueAsync()) fires 5 times. Without SemaphoreSlim, you would spawn 5 parallel background workers that all instantly hit the Telegram API, completely defeating the purpose of the queue. _queueWorkerLock.WaitAsync(0) allows the first worker through and instantly rejects the other 4. The single active worker then loops through all 5 items sequentially.
Smart Delay Logic: Notice the if (!_uploadQueue.IsEmpty) wrapper around Task.Delay(2000). If the worker just dispatched the final item in the queue, there is no need to make the thread sleep for 2 seconds before tearing down. It finishes instantly, freeing system resources.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
To bulletproof your memory management against network outages, you must implement a Dual-Layer Shield: a Pre-Emptive Capacity Limit that blocks the generation of new images, and a Network Circuit Breaker that instantly purges existing data if the socket goes dead.
If you allow the ConcurrentQueue to grow infinitely while the internet is down, not only will you crash cTrader's RAM, but the continuous 50ms graphics rendering blocks (Thread.Sleep) will severely lag your local terminal right when you need it to be responsive.
If you allow the ConcurrentQueue to grow infinitely while the internet is down, not only will you crash cTrader's RAM, but the continuous 50ms graphics rendering blocks (Thread.Sleep) will severely lag your local terminal right when you need it to be responsive.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
Here is the complete C# architecture to protect your system's memory and CPU:
Code: Select all
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using cAlgo.API;
namespace cAlgo.Robots
{
public partial class InteractiveFiatHUD : Robot
{
[Parameter("Max Screenshots in Memory", Group = "Memory Protection", DefaultValue = 10)]
public int MaxQueueSize { get; set; }
[Parameter("Purge Queue On Disconnect", Group = "Memory Protection", DefaultValue = true)]
public bool FlushQueueOnDisconnect { get; set; }
// 1. STRICT TIMEOUT: Prevent the background thread from hanging infinitely on a dead socket
private static readonly HttpClient _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
private void TriggerStopLossScreenshot(Position closedPosition)
{
// 2. THE PRE-EMPTIVE BLOCK
// Check RAM limits before engaging the expensive Windows graphics API
if (_uploadQueue.Count >= MaxQueueSize)
{
Print($"[Memory Shield] Queue full ({MaxQueueSize}). Capture skipped for Position {closedPosition.Id}.");
return;
}
var historicalTrade = History.FirstOrDefault(t => t.PositionId == closedPosition.Id);
if (historicalTrade == null) return;
// ... (Your existing chart drawing logic and Thread.Sleep(50) go here)
byte[] screenshotBytes = CaptureScreenToMemory();
if (screenshotBytes != null)
{
_uploadQueue.Enqueue(new TelegramPayload { ImageBytes = screenshotBytes, Caption = "🛑 SL Hit" });
Task.Run(() => ProcessUploadQueueAsync());
}
}
private async Task ProcessUploadQueueAsync()
{
if (!await _queueWorkerLock.WaitAsync(0)) return;
try
{
int consecutiveFailures = 0;
while (_uploadQueue.TryDequeue(out TelegramPayload payload))
{
Print($"[Telegram Queue] Dispatching payload. ({_uploadQueue.Count} remaining)");
// 3. Await the transmission and capture its boolean success state
bool isSuccess = await SendTelegramScreenshotAsync(payload.ImageBytes, payload.Caption);
if (isSuccess)
{
consecutiveFailures = 0; // Reset the breaker on a successful ping
}
else
{
consecutiveFailures++;
// 4. THE CIRCUIT BREAKER
if (FlushQueueOnDisconnect && consecutiveFailures >= 2)
{
Print("[Network Disconnect] Multiple upload failures. Purging queue to free RAM.");
// Instantly drain the remaining byte arrays into the garbage collector
while (_uploadQueue.TryDequeue(out _)) { }
break; // Exit the loop entirely
}
}
if (!_uploadQueue.IsEmpty)
{
await Task.Delay(2000);
}
}
}
finally
{
_queueWorkerLock.Release();
}
}
/// <summary>
/// Updated to return a boolean indicating network success or failure.
/// </summary>
private async Task<bool> SendTelegramScreenshotAsync(byte[] imageBytes, string captionText)
{
try
{
// ... (Your existing multipart form setup goes here) ...
HttpResponseMessage response = await _httpClient.PostAsync(url, form);
if (!response.IsSuccessStatusCode)
{
Print($"[Telegram API Error] {response.StatusCode}");
return false;
}
return true;
}
catch (TaskCanceledException)
{
// Catches the custom 10-second HttpClient.Timeout threshold
Print("[Network Error] Telegram connection timed out. Internet down?");
return false;
}
catch (HttpRequestException ex)
{
// Catches immediate DNS resolution failures (e.g., router disconnected)
Print($"[Network Failure] {ex.Message}");
return false;
}
catch (Exception ex)
{
Print($"[System Exception] {ex.Message}");
return false;
}
}
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
Architectural Mechanics
The Pre-Emptive CPU Shield: The check if (_uploadQueue.Count >= MaxQueueSize) happens before the bot fetches history, draws lines, or captures the screen. If your internet is down and the queue is full, the bot skips the 50ms Thread.Sleep and the heavy Bitmap rendering entirely. This guarantees your execution engine maintains microsecond reaction times during a violent market crash, even if your local network fails.
The Socket Timeout (TimeSpan.FromSeconds(10)): By default, the .NET HttpClient will wait up to 100 seconds for a server to respond. If your internet drops mid-transmission, the background worker would remain locked for almost two minutes. Enforcing a strict 10-second timeout throws a controlled TaskCanceledException, forcing the worker to quickly acknowledge the failure and move on.
The Circuit Breaker Pattern: When the internet disconnects, attempting to upload the remaining queued images is a waste of CPU cycles. If consecutiveFailures hits 2, the while (_uploadQueue.TryDequeue(out _)) { } loop activates. The out _ syntax is a C# discard; it pulls the heavy byte arrays out of the queue and instantly drops them into the abyss, allowing the .NET Garbage Collector to sweep the RAM completely clean in milliseconds.
The Pre-Emptive CPU Shield: The check if (_uploadQueue.Count >= MaxQueueSize) happens before the bot fetches history, draws lines, or captures the screen. If your internet is down and the queue is full, the bot skips the 50ms Thread.Sleep and the heavy Bitmap rendering entirely. This guarantees your execution engine maintains microsecond reaction times during a violent market crash, even if your local network fails.
The Socket Timeout (TimeSpan.FromSeconds(10)): By default, the .NET HttpClient will wait up to 100 seconds for a server to respond. If your internet drops mid-transmission, the background worker would remain locked for almost two minutes. Enforcing a strict 10-second timeout throws a controlled TaskCanceledException, forcing the worker to quickly acknowledge the failure and move on.
The Circuit Breaker Pattern: When the internet disconnects, attempting to upload the remaining queued images is a waste of CPU cycles. If consecutiveFailures hits 2, the while (_uploadQueue.TryDequeue(out _)) { } loop activates. The out _ syntax is a C# discard; it pulls the heavy byte arrays out of the queue and instantly drops them into the abyss, allowing the .NET Garbage Collector to sweep the RAM completely clean in milliseconds.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Spread Filters for Scalping: When Not to Enter
To aggregate multiple rapid-fire closures into a single alert, you must implement a Debouncer Pattern.
Instead of firing the screenshot immediately when a trade closes, you place the trade data into a temporary holding list and start a countdown timer. Every time another trade closes, the timer resets. When the market finally pauses for a specific duration (e.g., 1 second), the bot processes the entire list, draws all the execution lines at once, takes a single screenshot of the aftermath, and transmits one summarized Telegram message.
Because cTrader strictly prohibits accessing UI methods (like Chart.DrawTrendLine or CaptureScreenToMemory) from background threads, we must use cTrader's native OnTimer event to safely execute the batch on the main bot thread.
Instead of firing the screenshot immediately when a trade closes, you place the trade data into a temporary holding list and start a countdown timer. Every time another trade closes, the timer resets. When the market finally pauses for a specific duration (e.g., 1 second), the bot processes the entire list, draws all the execution lines at once, takes a single screenshot of the aftermath, and transmits one summarized Telegram message.
Because cTrader strictly prohibits accessing UI methods (like Chart.DrawTrendLine or CaptureScreenToMemory) from background threads, we must use cTrader's native OnTimer event to safely execute the batch on the main bot thread.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.