Key Architectural Decisions
Thread Safety (ContinueWith + BeginInvokeOnMainThread): The Task.Delay(delayMs).ContinueWith(...) chain operates in the background, keeping the cBot fully responsive to incoming ticks. The BeginInvokeOnMainThread boundary ensures the subsequent ClosePositionAsync call is legally executed within cAlgo's required thread context.
Exponential Backoff (delayMs * 2): Network congestion or liquidity vacuums that cause order rejection often resolve within seconds. Scaling the delay (e.g., 500ms, 1000ms, 2000ms, 4000ms) prevents you from spamming the broker's trade server, which can trigger rate-limiting or auto-blocking of your IC Markets account.
Orphan Mitigation: The Interlocked.Decrement command is explicitly executed in the else block of the final retry. If a position acts as a "poison pill" (e.g., the symbol was suddenly halted by the broker), the bot acknowledges the failure, drops it from the queue, and proceeds to terminate. Failing to decrement on a terminal error would trap the bot in a zombie state, preventing Stop() from ever executing.
Hitting most of the profit target in the first few days: what now?
Re: Hitting most of the profit target in the first few days: what now?
To integrate external HTTP calls in cTrader, you must first modify the bot's security permissions. Change the robot attribute at the top of your class to [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]. Without FullAccess, cAlgo's sandboxing will instantly block the HttpClient request.
Re: Hitting most of the profit target in the first few days: what now?
Here is the implementation using a single static HttpClient to prevent socket exhaustion, paired with a fire-and-forget task execution to ensure the HTTP latency does not block the bot's execution thread.
Code: Select all
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
public class SeriesGuardian : Robot
{
[Parameter("Telegram Bot Token", DefaultValue = "", Group = "Telegram")]
public string TelegramToken { get; set; }
[Parameter("Telegram Chat ID", DefaultValue = "", Group = "Telegram")]
public string TelegramChatId { get; set; }
// Reuse a single instance across the bot's lifecycle to prevent socket exhaustion
private static readonly HttpClient _httpClient = new HttpClient();
private int _pendingClosures;
private bool _isShuttingDown = false;
// ... [OnInit and OnTick logic remains the same] ...
private void ClosePositionWithRetry(Position position, int attempt, int maxRetries, int delayMs)
{
ClosePositionAsync(position, result =>
{
if (result.IsSuccessful)
{
Print($"Successfully closed position {position.Id} on attempt {attempt}.");
if (System.Threading.Interlocked.Decrement(ref _pendingClosures) == 0)
ExecuteShutdown();
}
else
{
Print($"Closure failed for {position.Id} (Attempt {attempt}/{maxRetries}). Error: {result.Error}");
if (attempt < maxRetries)
{
Task.Delay(delayMs).ContinueWith(_ =>
{
BeginInvokeOnMainThread(() =>
{
ClosePositionWithRetry(position, attempt + 1, maxRetries, delayMs * 2);
});
});
}
else
{
string fatalMsg = $"FATAL: Max retries exhausted for position {position.Id}. Error: {result.Error}. Manual intervention required.";
Print(fatalMsg);
// Dispatch the Telegram alert asynchronously
SendTelegramAlert(fatalMsg);
if (System.Threading.Interlocked.Decrement(ref _pendingClosures) == 0)
ExecuteShutdown();
}
}
});
}
private void SendTelegramAlert(string message)
{
if (string.IsNullOrWhiteSpace(TelegramToken) || string.IsNullOrWhiteSpace(TelegramChatId))
return;
string url = $"https://api.telegram.org/bot{TelegramToken}/sendMessage";
string payload = $"{{\"chat_id\": \"{TelegramChatId}\", \"text\": \"{message}\"}}";
var content = new StringContent(payload, Encoding.UTF8, "application/json");
// Fire-and-forget: Offloads the HTTP I/O to the ThreadPool so cTrader remains responsive
Task.Run(async () =>
{
try
{
var response = await _httpClient.PostAsync(url, content);
if (!response.IsSuccessStatusCode)
{
// Fallback logging if the Telegram API rejects the request
string errorResponse = await response.Content.ReadAsStringAsync();
Print($"Telegram Alert Failed: {response.StatusCode} - {errorResponse}");
}
}
catch (Exception ex)
{
Print($"Telegram HTTP Exception: {ex.Message}");
}
});
}
private void ExecuteShutdown()
{
BeginInvokeOnMainThread(() =>
{
// ClearLocalStorage();
Stop();
});
}
}
}Re: Hitting most of the profit target in the first few days: what now?
Key Architectural Details
AccessRights.FullAccess: cTrader blocks network I/O by default. When you compile and attach this bot, a security prompt will warn you that the bot requests full system access. You must accept this for the HttpClient to function.
Static HttpClient: Instantiating a new HttpClient inside the SendTelegramAlert method creates a new underlying socket every time it is called. If a flash crash causes 10 concurrent trades to exhaust their retries simultaneously, you risk socket exhaustion. A static readonly instance perfectly handles concurrent requests.
Fire-and-Forget Threading (Task.Run): Network requests introduce unpredictable latency (50ms to 2000ms+). By wrapping the HTTP call in Task.Run(), you push the I/O wait onto a background ThreadPool thread, ensuring the cAlgo main thread is instantly freed up to process tick data or finish the ExecuteShutdown sequence.
AccessRights.FullAccess: cTrader blocks network I/O by default. When you compile and attach this bot, a security prompt will warn you that the bot requests full system access. You must accept this for the HttpClient to function.
Static HttpClient: Instantiating a new HttpClient inside the SendTelegramAlert method creates a new underlying socket every time it is called. If a flash crash causes 10 concurrent trades to exhaust their retries simultaneously, you risk socket exhaustion. A static readonly instance perfectly handles concurrent requests.
Fire-and-Forget Threading (Task.Run): Network requests introduce unpredictable latency (50ms to 2000ms+). By wrapping the HTTP call in Task.Run(), you push the I/O wait onto a background ThreadPool thread, ensuring the cAlgo main thread is instantly freed up to process tick data or finish the ExecuteShutdown sequence.
Re: Hitting most of the profit target in the first few days: what now?
Packaging and deploying a FullAccess cBot requires managing both the .NET CLR security boundary and the physical uptime of the host environment. Because FullAccess bypasses cAlgo's sandbox to allow HTTP/network I/O, the deployment must be hardened against both external intrusion and terminal instability.
1. Packaging the .algo Payload
cTrader .algo files are essentially renamed ZIP archives containing your compiled C# .dll and source metadata.
Compile the solution in Visual Studio or cTrader Automate ensuring the Release configuration is selected.
In cTrader Automate, right-click the cBot and select Package.
Security Note: Never transfer the .algo file over unencrypted channels (like standard FTP or email) to the VPS. Use SFTP or a secure cloud storage endpoint to prevent man-in-the-middle injection into the payload.
1. Packaging the .algo Payload
cTrader .algo files are essentially renamed ZIP archives containing your compiled C# .dll and source metadata.
Compile the solution in Visual Studio or cTrader Automate ensuring the Release configuration is selected.
In cTrader Automate, right-click the cBot and select Package.
Security Note: Never transfer the .algo file over unencrypted channels (like standard FTP or email) to the VPS. Use SFTP or a secure cloud storage endpoint to prevent man-in-the-middle injection into the payload.
Re: Hitting most of the profit target in the first few days: what now?
2. Hardening the Windows VPS
Exposing standard RDP (Port 3389) on a Windows Server is a massive attack vector.
Zero-Trust Access: Do not rely solely on Windows Firewall. Route your RDP connection through a lightweight mesh VPN (like Tailscale or ZeroTier) or an Azure Network Security Group (NSG) strictly whitelisted to your local HP Z2 workstation's public IP.
Outbound Firewalling: Since the cBot runs with FullAccess, a compromised dependency could theoretically exfiltrate data. Configure the Windows Firewall to explicitly block all outbound traffic from cTrader.exe except to your broker's FIX/API IP ranges (for IC Markets) and the Telegram API (api.telegram.org:443).
Exposing standard RDP (Port 3389) on a Windows Server is a massive attack vector.
Zero-Trust Access: Do not rely solely on Windows Firewall. Route your RDP connection through a lightweight mesh VPN (like Tailscale or ZeroTier) or an Azure Network Security Group (NSG) strictly whitelisted to your local HP Z2 workstation's public IP.
Outbound Firewalling: Since the cBot runs with FullAccess, a compromised dependency could theoretically exfiltrate data. Configure the Windows Firewall to explicitly block all outbound traffic from cTrader.exe except to your broker's FIX/API IP ranges (for IC Markets) and the Telegram API (api.telegram.org:443).
Re: Hitting most of the profit target in the first few days: what now?
3. Terminal Optimization for Long-Running Execution
cTrader is built on WPF and can heavily consume RAM and GPU cycles over a multi-week trading series.
Disable Visuals: Navigate to cTrader Settings > General and disable Hardware Video Acceleration.
Chart Stripping: Open a single chart window for the EA. Switch it to a line chart, turn off the grid, and remove all indicators. Close all other terminal tabs.
Memory Restarts: WPF applications inherently leak UI memory over long periods. Schedule a weekly VPS reboot during the weekend market closure (Saturday) to flush the RAM.
cTrader is built on WPF and can heavily consume RAM and GPU cycles over a multi-week trading series.
Disable Visuals: Navigate to cTrader Settings > General and disable Hardware Video Acceleration.
Chart Stripping: Open a single chart window for the EA. Switch it to a line chart, turn off the grid, and remove all indicators. Close all other terminal tabs.
Memory Restarts: WPF applications inherently leak UI memory over long periods. Schedule a weekly VPS reboot during the weekend market closure (Saturday) to flush the RAM.
Re: Hitting most of the profit target in the first few days: what now?
4. Automating Disaster Recovery
cTrader does not run as a native Windows Service. If the VPS reboots due to a Windows Update (Patch Tuesday), your guardian will be offline.
Sysinternals Autologon: Use Microsoft's Autologon tool to securely encrypt your Windows credentials in the registry, allowing the VPS to automatically bypass the lock screen on boot.
Startup Folder: Place a shortcut to cTrader.exe in the shell:startup folder.
The FullAccess Prompt: On the very first run of a FullAccess bot on the VPS, cTrader will throw a security warning requiring manual GUI approval. You must connect via RDP, manually drag the cBot onto the chart, accept the prompt, and save the cTrader workspace. Upon auto-reboot, cTrader will load the saved workspace and automatically bypass the prompt.
cTrader does not run as a native Windows Service. If the VPS reboots due to a Windows Update (Patch Tuesday), your guardian will be offline.
Sysinternals Autologon: Use Microsoft's Autologon tool to securely encrypt your Windows credentials in the registry, allowing the VPS to automatically bypass the lock screen on boot.
Startup Folder: Place a shortcut to cTrader.exe in the shell:startup folder.
The FullAccess Prompt: On the very first run of a FullAccess bot on the VPS, cTrader will throw a security warning requiring manual GUI approval. You must connect via RDP, manually drag the cBot onto the chart, accept the prompt, and save the cTrader workspace. Upon auto-reboot, cTrader will load the saved workspace and automatically bypass the prompt.
Re: Hitting most of the profit target in the first few days: what now?
To monitor VPS health without disrupting your trade execution thread, you must decouple the system diagnostics from the OnTick() loop. By utilizing cTrader's Timer event and the System.Diagnostics namespace, you can poll hardware metrics on a scheduled interval (e.g., every 60 seconds) and push the evaluation to the ThreadPool.
Because a WPF application like cTrader is prone to creeping memory leaks over a multi-week series, tracking the specific cTrader.exe process footprint is often more actionable than querying global memory.
Because a WPF application like cTrader is prone to creeping memory leaks over a multi-week series, tracking the specific cTrader.exe process footprint is often more actionable than querying global memory.
Re: Hitting most of the profit target in the first few days: what now?
Here is the implementation integrating Windows Performance Counters and process memory tracking, complete with an alert-throttling mechanism to prevent Telegram rate-limiting during sustained spikes.
Code: Select all
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
public class SeriesGuardian : Robot
{
[Parameter("CPU Alert Threshold (%)", DefaultValue = 90.0, Group = "VPS Health")]
public double CpuThreshold { get; set; }
[Parameter("cTrader RAM Alert (MB)", DefaultValue = 1500.0, Group = "VPS Health")]
public double RamThresholdMb { get; set; }
private PerformanceCounter _cpuCounter;
private Process _cTraderProcess;
private DateTime _lastHealthAlert = DateTime.MinValue;
private readonly TimeSpan _alertCooldown = TimeSpan.FromMinutes(15); // Throttle Telegram spam
protected override void OnStart()
{
try
{
// Initialize the global CPU counter
_cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
_cpuCounter.NextValue(); // The first call always returns 0, initialize it here
}
catch (Exception ex)
{
Print($"Warning: Performance counters not available on this VPS OS build. {ex.Message}");
}
_cTraderProcess = Process.GetCurrentProcess();
// Run the health check every 60 seconds independently of incoming ticks
Timer.Start(TimeSpan.FromSeconds(60));
Print("VPS Health Monitoring initialized.");
}
protected override void OnTimer()
{
// Offload the WMI/Diagnostics query to a background thread
Task.Run(() => EvaluateVpsHealth());
}
private void EvaluateVpsHealth()
{
try
{
float cpuUsage = 0;
if (_cpuCounter != null)
{
cpuUsage = _cpuCounter.NextValue();
}
_cTraderProcess.Refresh(); // Crucial: updates the property cache
// Convert WorkingSet64 (bytes) to Megabytes
double ramUsageMb = _cTraderProcess.WorkingSet64 / (1024.0 * 1024.0);
if (cpuUsage >= CpuThreshold || ramUsageMb >= RamThresholdMb)
{
if (DateTime.UtcNow - _lastHealthAlert >= _alertCooldown)
{
_lastHealthAlert = DateTime.UtcNow;
string alertMsg = $"⚠️ VPS RESOURCE WARNING\nCPU Usage: {cpuUsage:F1}%\ncTrader RAM: {ramUsageMb:F1} MB";
Print(alertMsg);
// Call your existing Telegram integration here
SendTelegramAlert(alertMsg);
}
}
}
catch (Exception ex)
{
Print($"Health Check Exception: {ex.Message}");
}
}
// ... [Include your existing OnTick, SendTelegramAlert, and flattening logic] ...
}
}