Operational Deployment
Instance: Attach this cBot to a blank, dedicated chart (e.g., a daily chart) so it stays out of the way of your active 1m/5m execution charts.
Asynchronous Execution: The flattener uses ClosePositionAsync and CancelPendingOrderAsync. In a fast-moving scalping environment, you want the cBot firing off the close commands instantly without waiting for the server to confirm each one sequentially.
The Intercept: Because the OnTimer loop runs every 1 second, if you attempt to override the limit by manually hitting "Buy" on the platform after you've hit max loss, the cBot will instantly detect the negative daily balance and kill the new trade in under a second.
Sticky-note daily loss limit that faces the screen
Re: Sticky-note daily loss limit that faces the screen
We can also leverage cTrader's WPF-style UI framework to build a proper dark-mode, institutional Grid layout rather than placing objects by pixel coordinates.
Here is the Pro Risk Desk cBot for cTrader, engineered with asynchronous flattener methods, strict state management, and real-time environment overrides.
cTrader: Pro Risk Desk (cBot)
Here is the Pro Risk Desk cBot for cTrader, engineered with asynchronous flattener methods, strict state management, and real-time environment overrides.
cTrader: Pro Risk Desk (cBot)
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Models;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ProRiskDesk : Robot
{
// =========================================================================
// 1. RISK PARAMETERS (Sunday Prep)
// =========================================================================
[Parameter("Daily Loss Limit ($)", Group = "Capital Risk", DefaultValue = 500.0, MinValue = 0, Step = 50)]
public double MaxDailyLoss { get; set; }
[Parameter("Value of 1R ($)", Group = "Capital Risk", DefaultValue = 100.0, MinValue = 1, Step = 10)]
public double R_Value { get; set; }
[Parameter("Max Trades Per Day", Group = "Behavioral Circuit Breakers", DefaultValue = 10, MinValue = 1)]
public int MaxTrades { get; set; }
[Parameter("Max Consecutive Losses", Group = "Behavioral Circuit Breakers", DefaultValue = 3, MinValue = 1)]
public int MaxStreak { get; set; }
[Parameter("Flash Chart Override", Group = "Environment", DefaultValue = true)]
public bool FlashChart { get; set; }
// =========================================================================
// 2. STATE TRACKING
// =========================================================================
private double _peakDailyEquity;
private DateTime _currentDay;
private bool _isHalted;
private string _haltReason;
private Color _originalBgColor;
private bool _alertFired;
// UI Components
private Border _mainBorder;
private TextBlock _uiTitle;
private Border _titleBorder;
private TextBlock _uiPnL;
private TextBlock _uiTargetR;
private TextBlock _uiDrawdown;
private TextBlock _uiTrades;
private TextBlock _uiStreak;
protected override void OnStart()
{
_originalBgColor = Chart.ColorSettings.BackgroundColor;
ResetDailyState();
InitializeHUD();
// Sub-second enforcement independent of tick volume
Timer.Start(TimeSpan.FromSeconds(1));
}
protected override void OnStop()
{
// Clean up environment on removal
Chart.ColorSettings.BackgroundColor = _originalBgColor;
Chart.RemoveControl(_mainBorder);
}
protected override void OnTick() { EnforceRiskLimits(); }
protected override void OnTimer() { EnforceRiskLimits(); }
// =========================================================================
// 3. CORE LOGIC & LINQ PARSING
// =========================================================================
private void EnforceRiskLimits()
{
// Session Rollover
if (Server.Time.Date != _currentDay)
{
ResetDailyState();
}
// High-Water Mark Tracking
_peakDailyEquity = Math.Max(_peakDailyEquity, Account.Equity);
double peakDrawdown = _peakDailyEquity - Account.Equity;
// Isolate today's executions
var tradesToday = History.Where(t => t.ClosingTime >= _currentDay).ToList();
int totalTrades = tradesToday.Count;
// PnL Math
double realizedPnL = tradesToday.Sum(t => t.NetProfit); // NetProfit natively includes swap/commission
double floatingPnL = Positions.Sum(p => p.NetProfit);
double totalPnL = realizedPnL + floatingPnL;
double pnlInR = totalPnL / Math.Max(R_Value, 1);
// Behavioral Metric: Consecutive Losses using LINQ
int consecLosses = 0;
foreach (var trade in tradesToday.OrderByDescending(t => t.ClosingTime))
{
if (trade.NetProfit < 0) consecLosses++;
else break; // Streak broken
}
// Halt Triggers
bool limitHit = totalPnL <= -MaxDailyLoss;
bool tiltHit = consecLosses >= MaxStreak;
bool overtradeHit = totalTrades >= MaxTrades;
if (limitHit || tiltHit || overtradeHit)
{
_isHalted = true;
_haltReason = limitHit ? "HARD LIMIT MET" : (tiltHit ? "TILT: MAX LOSS STREAK" : "OVERTRADING CAP MET");
FlattenAccountAsync();
if (!_alertFired)
{
Print($"RISK DESK OVERRIDE: {_haltReason}");
_alertFired = true;
// Physical environment override
if (FlashChart)
{
Chart.ColorSettings.BackgroundColor = limitHit ? Color.FromHex("#3A0000") : Color.FromHex("#2A0033");
}
}
}
UpdateHUD(totalPnL, pnlInR, peakDrawdown, totalTrades, consecLosses);
}
private void ResetDailyState()
{
_currentDay = Server.Time.Date;
_peakDailyEquity = Account.Equity;
_isHalted = false;
_alertFired = false;
_haltReason = string.Empty;
Chart.ColorSettings.BackgroundColor = _originalBgColor;
}
// =========================================================================
// 4. ASYNC EXECUTION INTERCEPT
// =========================================================================
private void FlattenAccountAsync()
{
// Non-blocking async closure is critical for high-frequency scalping safety
foreach (var pos in Positions)
{
ClosePositionAsync(pos);
}
foreach (var order in PendingOrders)
{
CancelPendingOrderAsync(order);
}
}
// =========================================================================
// 5. WPF-STYLE UI RENDERING
// =========================================================================
private void InitializeHUD()
{
_uiTitle = CreateText("RISK DESK: ACTIVE", Color.White, 11, FontWeight.ExtraBold);
_uiTitle.HorizontalAlignment = HorizontalAlignment.Center;
_uiTitle.Margin = new Thickness(0, 5, 0, 5);
_titleBorder = new Border { BackgroundColor = Color.FromHex("#00ACC1"), Child = _uiTitle };
_uiPnL = CreateValueText("$0.00");
_uiTargetR = CreateValueText("0.00 R");
_uiDrawdown = CreateValueText("$0.00");
_uiTrades = CreateValueText("0 / 0");
_uiStreak = CreateValueText("0 / 0");
var layoutPanel = new StackPanel { Orientation = Orientation.Vertical };
layoutPanel.AddChild(_titleBorder);
layoutPanel.AddChild(CreateDataRow("Daily PnL", _uiPnL));
layoutPanel.AddChild(CreateDataRow("Target (R)", _uiTargetR));
layoutPanel.AddChild(CreateDataRow("Peak Drawdown", _uiDrawdown));
layoutPanel.AddChild(CreateDataRow("Trades Today", _uiTrades));
layoutPanel.AddChild(CreateDataRow("Loss Streak", _uiStreak));
_mainBorder = new Border
{
BackgroundColor = Color.FromHex("#131722"),
BorderColor = Color.FromHex("#2A2E39"),
BorderThickness = new Thickness(1),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
Margin = new Thickness(20),
Width = 240,
Child = layoutPanel
};
Chart.AddControl(_mainBorder);
}
private Grid CreateDataRow(string label, TextBlock valueBlock)
{
var lbl = CreateText(label, Color.DarkGray, 11, FontWeight.Normal);
lbl.HorizontalAlignment = HorizontalAlignment.Left;
valueBlock.HorizontalAlignment = HorizontalAlignment.Right;
var rowGrid = new Grid(1, 2) { Margin = new Thickness(10, 5, 10, 5) };
rowGrid.AddChild(lbl, 0, 0);
rowGrid.AddChild(valueBlock, 0, 1);
return rowGrid;
}
private TextBlock CreateText(string text, Color color, double size, FontWeight weight)
{
return new TextBlock
{
Text = text,
ForegroundColor = color,
FontSize = size,
FontWeight = weight,
FontFamily = "Consolas" // Enforces tabular monospacing
};
}
private TextBlock CreateValueText(string text)
{
return CreateText(text, Color.White, 11, FontWeight.Bold);
}
private void UpdateHUD(double pnl, double pnlInR, double peakDrawdown, int tradesToday, int consecLosses)
{
if (_isHalted)
{
_uiTitle.Text = _haltReason;
_titleBorder.BackgroundColor = Color.FromHex("#8B0000"); // Dark Red
_mainBorder.BackgroundColor = Color.Black;
}
else
{
_uiTitle.Text = "RISK DESK: ACTIVE";
_titleBorder.BackgroundColor = Color.FromHex("#00ACC1"); // Teal
}
_uiPnL.Text = $"{(pnl >= 0 ? "+" : "-")}${Math.Abs(pnl):F2}";
_uiPnL.ForegroundColor = pnl >= 0 ? Color.LimeGreen : Color.OrangeRed;
_uiTargetR.Text = $"{(pnlInR >= 0 ? "+" : "")}{pnlInR:F2} R";
_uiTargetR.ForegroundColor = pnlInR >= 0 ? Color.LimeGreen : Color.OrangeRed;
_uiDrawdown.Text = $"${peakDrawdown:F2}";
_uiDrawdown.ForegroundColor = Color.DarkGray;
_uiTrades.Text = $"{tradesToday} / {MaxTrades}";
_uiTrades.ForegroundColor = tradesToday >= MaxTrades ? Color.Red : Color.White;
_uiStreak.Text = $"{consecLosses} / {MaxStreak}";
_uiStreak.ForegroundColor = consecLosses >= MaxStreak ? Color.Red : Color.White;
}
}
}Re: Sticky-note daily loss limit that faces the screen
Why this is a superior architecture for C#:
True Async Execution: Notice the use of ClosePositionAsync and CancelPendingOrderAsync. In MQL, closing trades is a synchronous, blocking action that queues up. If you are scalping raw PA and need the circuit breaker to trigger instantly while 4 orders are open, .Async drops them all simultaneously without hanging the thread.
LINQ over Loops: Pulling your daily trade count and consecutive losses takes exactly 2 lines of clean LINQ (Where, OrderByDescending, Sum) rather than tracking reverse index positions through an arbitrary historical array pool.
WPF UI Engine: Leveraging cTrader’s internal Automate UI wrapper (Border, StackPanel, Grid, TextBlock), we can build a dynamic UI that scales cleanly with the platform's DPI settings, rather than manually painting pixels onto a chart axis like in MetaTrader.
Run this on a clean cTrader chart (e.g., Daily timeframe). If you hit a behavioral tilt limit (e.g., 3 consecutive losses), the background goes dark purple and the engine locks out. If you hit the hard cash limit, the chart flashes maroon, terminating the session.
True Async Execution: Notice the use of ClosePositionAsync and CancelPendingOrderAsync. In MQL, closing trades is a synchronous, blocking action that queues up. If you are scalping raw PA and need the circuit breaker to trigger instantly while 4 orders are open, .Async drops them all simultaneously without hanging the thread.
LINQ over Loops: Pulling your daily trade count and consecutive losses takes exactly 2 lines of clean LINQ (Where, OrderByDescending, Sum) rather than tracking reverse index positions through an arbitrary historical array pool.
WPF UI Engine: Leveraging cTrader’s internal Automate UI wrapper (Border, StackPanel, Grid, TextBlock), we can build a dynamic UI that scales cleanly with the platform's DPI settings, rather than manually painting pixels onto a chart axis like in MetaTrader.
Run this on a clean cTrader chart (e.g., Daily timeframe). If you hit a behavioral tilt limit (e.g., 3 consecutive losses), the background goes dark purple and the engine locks out. If you hit the hard cash limit, the chart flashes maroon, terminating the session.