using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; // Required for high-performance string building
using cAlgo.API;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class FiatTrailingWithHUD : Robot
{
[Parameter("Activation Profit (Fiat)", Group = "Trailing Fiat", DefaultValue = 50.0)]
public double ActivationProfit { get; set; }
[Parameter("Trailing Distance (Fiat)", Group = "Trailing Fiat", DefaultValue = 10.0)]
public double TrailingDistance { get; set; }
[Parameter("Show HUD", Group = "UI", DefaultValue = true)]
public bool ShowHud { get; set; }
private Dictionary<string, double> _highWaterMarks = new Dictionary<string, double>();
private HashSet<string> _closingLtids = new HashSet<string>();
protected override void OnStart()
{
Positions.Closed += OnPositionClosed;
}
protected override void OnTick()
{
var activeLtids = Positions.Select(p => p.Label).Distinct();
foreach (var ltid in activeLtids)
{
if (_closingLtids.Contains(ltid)) continue;
double currentNetPnL = CalculateLogicalTradeNetPnL(ltid);
if (currentNetPnL >= ActivationProfit || _highWaterMarks.ContainsKey(ltid))
{
ManageFiatTrail(ltid, currentNetPnL);
}
}
// Refresh the on-screen display after all math is calculated
if (ShowHud)
{
UpdateChartHUD();
}
}
// ... (ManageFiatTrail, ExecuteMassClosure, and CalculateLogicalTradeNetPnL remain exactly the same as before)
/// <summary>
/// Generates and renders a live Heads-Up Display (HUD) showing all active trailed trades.
/// </summary>
private void UpdateChartHUD()
{
// 1. Clean up the screen if no trades are currently being trailed
if (_highWaterMarks.Count == 0)
{
Chart.RemoveObject("FiatTrailHUD");
return;
}
// 2. Use StringBuilder to prevent memory allocation spikes during high-frequency tick updates
StringBuilder hudBuilder = new StringBuilder();
hudBuilder.AppendLine("=== ACTIVE FIAT TRAILS ===");
foreach (var kvp in _highWaterMarks)
{
string ltid = kvp.Key;
double peak = kvp.Value;
double tripwire = peak - TrailingDistance;
// Fetch the live PnL again purely for UI display
double livePnL = CalculateLogicalTradeNetPnL(ltid);
// Build the data block for this specific trade
hudBuilder.AppendLine($"Trade: {ltid}");
hudBuilder.AppendLine($"Peak: {peak:C2} | Tripwire: {tripwire:C2}");
// Optional: Format the current PnL to visually indicate proximity to the tripwire
hudBuilder.AppendLine($"Live Net: {livePnL:C2}");
hudBuilder.AppendLine("--------------------------");
}
// 3. Render the text locked to the Top-Left of the chart window
Chart.DrawStaticText("FiatTrailHUD", hudBuilder.ToString(), VerticalAlignment.Top, HorizontalAlignment.Left, Color.Cyan);
}
private void OnPositionClosed(PositionClosedEventArgs args)
{
string closedLabel = args.Position.Label;
if (Positions.FindAll(closedLabel).Length == 0)
{
_closingLtids.Remove(closedLabel);
_highWaterMarks.Remove(closedLabel);
// Force an immediate HUD update to clear the closed trade off the screen instantly
if (ShowHud) UpdateChartHUD();
}
}
// Dummy implementations of previous methods to satisfy the compiler context
private double CalculateLogicalTradeNetPnL(string ltid) => 0;
private void ManageFiatTrail(string ltid, double currentNetPnL) { }
}
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
StringBuilder Efficiency: In OnTick, strings are regenerated hundreds of times per second. Using standard string concatenation (text += "new line") forces C# to allocate a new object in RAM on every tick, triggering heavy Garbage Collection pauses that can stutter your execution engine. StringBuilder modifies a single block of memory, keeping the footprint negligible.
Object Naming ("FiatTrailHUD"): The first parameter of Chart.DrawStaticText is the object name. Because we pass the exact same string name on every tick, cTrader overwrites the existing text object instead of creating millions of duplicated text layers on top of each other.
The Wipeout Protocol (Chart.RemoveObject): If the _highWaterMarks dictionary drops to zero (because all positions hit their tripwires and closed), the code explicitly calls Chart.RemoveObject("FiatTrailHUD"). Without this, the HUD would freeze on the screen showing the final state of the last closed trade indefinitely.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
To make the HUD interactive, you must abandon Chart.DrawStaticText. Static text is essentially "painted" onto the chart canvas and cannot intercept mouse clicks.
Instead, you must build a true UI using cTrader's native layout controls (StackPanel, Grid, and Button) and attach them to the chart via Chart.AddControl().
Because recreating a UI from scratch on every single micro-tick will freeze your terminal and destroy the button's "hover/click" state, the architecture must change. You create the button row once when the trade activates, and only update the text inside it on subsequent ticks.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Grid Layout Engine (Grid(1, 2)): The StackPanel stacks elements vertically. To put a text block and a button horizontally side-by-side on the same line, you nest a Grid with 1 row and 2 columns inside the StackPanel.
Variable Capture (string capturedLtid = ltid;): When assigning the button click event closeBtn.Click += args => OnManualForceClose(capturedLtid);, we create a local copy of the loop variable. If you pass the loop variable directly, the lambda expression may execute later using the final value of the loop, causing every button on the screen to mistakenly close the very last trade in the list. Capturing it locally guarantees the button is permanently hardwired to its specific trade.
Stateful UI Syncing: The logic if (!_hudRows.ContainsKey(ltid)) is the performance gatekeeper. The bot only generates heavy UI objects (Buttons, TextBlocks) at the exact millisecond the trailing stop activates. On the thousands of ticks that follow, it retrieves the existing TextBlock via (TextBlock)existingRow.ChildControls[0] and simply overrides the string, keeping execution times under 1 millisecond.
Double-Click Prevention: The user might panic-click the close button multiple times while the broker server is processing the first click. The if (_closingLtids.Contains(ltid)) return; safeguard at the top of OnManualForceClose instantly deadens the button once the first click is registered.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
To build a confirmation dialog, you must create a modal UI overlay that interrupts the execution flow. Instead of the red "X" button calling ExecuteMassClosure directly, it will call a new method that dynamically generates a centered pop-up box with "Confirm" and "Cancel" options.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
using System;
using cAlgo.API;
namespace cAlgo.Robots
{
// Note: Add this code inside your existing InteractiveFiatHUD class
public partial class InteractiveFiatHUD : Robot
{
// Tracks the active dialog so we don't spawn multiples
private Border _activeDialog;
/// <summary>
/// Replaces the direct execution call from the Red "X" button.
/// </summary>
private void OnManualForceClose(string ltid)
{
if (_closingLtids.Contains(ltid)) return;
// Spawn the confirmation UI instead of executing immediately
ShowConfirmationDialog(ltid);
}
/// <summary>
/// Dynamically builds a centered, modal dialog box on the chart.
/// </summary>
private void ShowConfirmationDialog(string ltid)
{
// 1. Prevent stacking: Remove any existing dialog if the user clicked multiple "X" buttons
CloseActiveDialog();
// 2. Build the main modal background container
_activeDialog = new Border
{
BackgroundColor = Color.FromArgb(240, 25, 25, 25), // Solid dark grey
BorderColor = Color.Red,
BorderThickness = new Thickness(2),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Padding = new Thickness(25),
CornerRadius = 5 // Rounded corners for a modern HUD look
};
// 3. Main vertical stack for text and buttons
StackPanel modalContent = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center };
// 4. Warning Text
TextBlock warningText = new TextBlock
{
Text = $"WARNING\nAre you sure you want to force-close trade:\n{ltid}?",
ForegroundColor = Color.White,
FontWeight = FontWeight.Bold,
Margin = new Thickness(0, 0, 0, 20),
TextAlignment = TextAlignment.Center
};
// 5. Horizontal stack for side-by-side buttons
StackPanel buttonRow = new StackPanel
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Center
};
Button confirmBtn = new Button
{
Text = "CONFIRM CLOSE",
BackgroundColor = Color.DarkRed,
ForegroundColor = Color.White,
Margin = new Thickness(0, 0, 10, 0),
Width = 140,
Height = 30
};
Button cancelBtn = new Button
{
Text = "CANCEL",
BackgroundColor = Color.DimGray,
ForegroundColor = Color.White,
Width = 100,
Height = 30
};
// 6. Wire up the event listeners
confirmBtn.Click += args => OnDialogConfirm(ltid);
cancelBtn.Click += args => CloseActiveDialog();
// 7. Assemble the UI tree
buttonRow.AddChild(confirmBtn);
buttonRow.AddChild(cancelBtn);
modalContent.AddChild(warningText);
modalContent.AddChild(buttonRow);
_activeDialog.Child = modalContent;
// 8. Render to the chart
Chart.AddControl(_activeDialog);
}
/// <summary>
/// Executes the closure and tears down the UI.
/// </summary>
private void OnDialogConfirm(string ltid)
{
CloseActiveDialog();
// CRITICAL CHECK: The market didn't pause while the dialog was open.
// We must verify the trade wasn't closed by a Stop Loss or Take Profit in the meantime.
if (Positions.FindAll(ltid).Length > 0)
{
ExecuteMassClosure(ltid, CalculateLogicalTradeNetPnL(ltid));
}
else
{
Print($"[Dialog Cancelled] Trade {ltid} no longer exists (likely hit TP/SL while dialog was open).");
}
}
/// <summary>
/// Safely removes the dialog from the chart memory.
/// </summary>
private void CloseActiveDialog()
{
if (_activeDialog != null)
{
Chart.RemoveControl(_activeDialog);
_activeDialog = null;
}
}
}
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
The Asynchronous Market Reality: The most important lines of code are inside OnDialogConfirm. A chart dialog does not pause the market. A user might open the pop-up, hesitate for 5 seconds, and in that window, a news spike hits their hard Stop Loss. If they click "Confirm" after the trade is already dead, the bot would crash trying to iterate over empty positions. The Positions.FindAll(ltid).Length > 0 check protects against this race condition.
Destructive UI Teardown: CloseActiveDialog() completely removes the Border from the chart's visual tree. This is more memory-efficient than toggling IsVisible = false, because it prevents dormant UI nodes from piling up in cTrader's memory over days of continuous operation.
Nesting Layouts (Border -> StackPanel -> StackPanel): cTrader UI controls require strict hierarchical nesting. The Border serves as the canvas, the first StackPanel forces elements to stack vertically (Text on top, Buttons on bottom), and the second nested StackPanel with Orientation.Horizontal forces the Confirm/Cancel buttons to sit side-by-side.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
To intercept keyboard inputs in cTrader, you must subscribe to the native Chart.KeyDown event. This event listens to all keystrokes made while the chart window is in focus, allowing you to route specific keys to your custom UI logic.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
using System;
using cAlgo.API;
namespace cAlgo.Robots
{
public partial class InteractiveFiatHUD : Robot
{
// Tracks the LTID currently pending confirmation, allowing advanced key mappings
private string _pendingConfirmationLtid;
protected override void OnStart()
{
// Existing subscriptions
Positions.Closed += OnPositionClosed;
// 1. Subscribe to the chart's keyboard event listener
Chart.KeyDown += OnChartKeyDown;
InitializeHUD();
}
/// <summary>
/// Captures all keystrokes when the chart is in focus.
/// </summary>
private void OnChartKeyDown(ChartKeyboardEventArgs args)
{
// Only process keystrokes if the dialog is currently visible
if (_activeDialog != null)
{
// Escape Key: Cancel and close the dialog
if (args.Key == Key.Escape)
{
Print("[Keyboard] Escape pressed. Canceling dialog.");
CloseActiveDialog();
}
// Optional Bonus: Enter Key to confirm the closure
else if (args.Key == Key.Enter && !string.IsNullOrEmpty(_pendingConfirmationLtid))
{
Print("[Keyboard] Enter pressed. Confirming closure.");
OnDialogConfirm(_pendingConfirmationLtid);
}
}
}
private void ShowConfirmationDialog(string ltid)
{
CloseActiveDialog();
// 2. Store the LTID in memory so the Enter key knows which trade to close
_pendingConfirmationLtid = ltid;
_activeDialog = new Border
{
// ... (Existing UI generation code remains exactly the same)
};
// ... (Existing UI generation code remains exactly the same)
Chart.AddControl(_activeDialog);
}
private void CloseActiveDialog()
{
if (_activeDialog != null)
{
Chart.RemoveControl(_activeDialog);
_activeDialog = null;
// 3. Clear the pending state to prevent accidental 'Enter' closures later
_pendingConfirmationLtid = null;
}
}
// ... (Remaining methods stay the same)
}
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.