Advertisement IC Markets

Spread Filters for Scalping: When Not to Enter

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
Post Reply
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

Here is how to integrate the HUD engine into the trailing manager:

Code: Select all

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.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

Architectural Mechanics

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.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

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.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

Here is the complete interactive UI architecture:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class InteractiveFiatHUD : Robot
    {
        [Parameter("Activation Profit", DefaultValue = 50.0)]
        public double ActivationProfit { get; set; }

        [Parameter("Trailing Distance", DefaultValue = 10.0)]
        public double TrailingDistance { get; set; }

        private Dictionary<string, double> _highWaterMarks = new Dictionary<string, double>();
        private HashSet<string> _closingLtids = new HashSet<string>();

        // UI State Management
        private StackPanel _mainHudContainer;
        private Dictionary<string, Grid> _hudRows = new Dictionary<string, Grid>();

        protected override void OnStart()
        {
            Positions.Closed += OnPositionClosed;
            InitializeHUD();
        }

        /// <summary>
        /// Builds the master container for the interactive UI and attaches it to the chart.
        /// </summary>
        private void InitializeHUD()
        {
            _mainHudContainer = new StackPanel
            {
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
                BackgroundColor = Color.FromArgb(200, 20, 20, 20), // Semi-transparent dark background
                Margin = new Thickness(10)
            };

            // Add a static header
            _mainHudContainer.AddChild(new TextBlock 
            { 
                Text = "ACTIVE FIAT TRAILS", 
                Margin = new Thickness(5), 
                ForegroundColor = Color.White,
                FontWeight = FontWeight.Bold 
            });

            Chart.AddControl(_mainHudContainer);
        }

        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 UI safely
            UpdateInteractiveHUD();
        }

        private void UpdateInteractiveHUD()
        {
            // 1. Remove UI rows for trades that have been fully closed
            var closedLtids = _hudRows.Keys.Where(k => !_highWaterMarks.ContainsKey(k)).ToList();
            foreach (var ltid in closedLtids)
            {
                _mainHudContainer.RemoveChild(_hudRows[ltid]);
                _hudRows.Remove(ltid);
            }

            // 2. Add or Update rows for currently active trades
            foreach (var kvp in _highWaterMarks)
            {
                string ltid = kvp.Key;
                double peak = kvp.Value;
                double tripwire = peak - TrailingDistance;
                double livePnL = CalculateLogicalTradeNetPnL(ltid);

                string displayText = $" {ltid}  |  Peak: {peak:C2}  |  Trip: {tripwire:C2}  |  Live: {livePnL:C2} ";

                if (!_hudRows.ContainsKey(ltid))
                {
                    // Create a new grid row for a newly activated trade
                    Grid row = new Grid(1, 2) { Margin = new Thickness(5) };
                    
                    TextBlock textBlock = new TextBlock 
                    { 
                        Text = displayText, 
                        ForegroundColor = Color.Cyan,
                        VerticalAlignment = VerticalAlignment.Center 
                    };

                    Button closeBtn = new Button 
                    { 
                        Text = " X ", 
                        BackgroundColor = Color.DarkRed, 
                        ForegroundColor = Color.White,
                        FontWeight = FontWeight.Bold,
                        Margin = new Thickness(10, 0, 0, 0)
                    };

                    // CRITICAL: Capture the specific LTID variable for the click event
                    string capturedLtid = ltid;
                    closeBtn.Click += args => OnManualForceClose(capturedLtid);

                    row.AddChild(textBlock, 0, 0); // Text in column 0
                    row.AddChild(closeBtn, 0, 1);  // Button in column 1

                    _mainHudContainer.AddChild(row);
                    _hudRows[ltid] = row;
                }
                else
                {
                    // Update only the text of the existing row to save CPU
                    Grid existingRow = _hudRows[ltid];
                    TextBlock textBlock = (TextBlock)existingRow.ChildControls[0];
                    textBlock.Text = displayText;
                }
            }
        }

        /// <summary>
        /// Triggered when the user clicks the red "X" button on the chart.
        /// </summary>
        private void OnManualForceClose(string ltid)
        {
            if (_closingLtids.Contains(ltid)) return; // Prevent double-clicking
            
            Print($"[Manual Intervention] User forced closed LTID: {ltid} via Chart HUD.");
            
            // Pass to your existing mass closure logic
            ExecuteMassClosure(ltid, CalculateLogicalTradeNetPnL(ltid));
        }

        private void ExecuteMassClosure(string ltid, double executedPnL)
        {
            _closingLtids.Add(ltid);
            var fragmentsToClose = Positions.FindAll(ltid);
            foreach (var position in fragmentsToClose)
            {
                ClosePositionAsync(position, result => 
                {
                    if (!result.IsSuccessful && result.Position != null)
                        _closingLtids.Remove(result.Position.Label);
                });
            }
        }

        private void ManageFiatTrail(string ltid, double currentNetPnL)
        {
            if (!_highWaterMarks.ContainsKey(ltid))
                _highWaterMarks[ltid] = currentNetPnL;
            else
                _highWaterMarks[ltid] = Math.Max(_highWaterMarks[ltid], currentNetPnL);

            double dynamicTripwire = _highWaterMarks[ltid] - TrailingDistance;

            if (currentNetPnL <= dynamicTripwire)
            {
                ExecuteMassClosure(ltid, currentNetPnL);
            }
        }

        private double CalculateLogicalTradeNetPnL(string ltid)
        {
            return History.FindAll(ltid).Sum(t => t.NetProfit) + Positions.FindAll(ltid).Sum(p => p.NetProfit);
        }

        private void OnPositionClosed(PositionClosedEventArgs args)
        {
            string closedLabel = args.Position.Label;
            if (Positions.FindAll(closedLabel).Length == 0)
            {
                _closingLtids.Remove(closedLabel);
                _highWaterMarks.Remove(closedLabel);
                UpdateInteractiveHUD(); // Force instant UI cleanup
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

Architectural Mechanics

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.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

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.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

Here is the isolated UI architecture to add to your existing class:

Code: Select all

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.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

Architectural Mechanics

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.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

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.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Spread Filters for Scalping: When Not to Enter

Post by PTScalper »

Here is the code to wire the 'Escape' key into your existing dialog architecture:

Code: Select all

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.
Post Reply