Advertisement IC Markets

Mistake: Ignoring Spreads When Choosing Setups

Compare ECN/Raw spread brokers, analyze execution speeds, report slippage, and evaluate commission structures for high-frequency traders.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Mistake: Ignoring Spreads When Choosing Setups

Post by PTScalper »

2. Update the OnTick Method

Replace your current OnTick() method with this. It loops through the active positions managed by this bot, dynamically calculates the 1:1 price based on the actual executed Stop Loss distance, and triggers the close.

Code: Select all

public override void OnTick()
{
    // Keeps the UI math updating
    UpdateDashboard();

    // Iterate only through positions opened by this specific cBot
    foreach (var position in Positions.FindAll("SpreadProtected", SymbolName))
    {
        // Skip if we already took a partial, or if there is no Stop Loss
        if (_partiallyClosedPositions.Contains(position.Id) || !position.StopLoss.HasValue) 
            continue;

        // Calculate the absolute risk distance in price
        double riskInPrice = Math.Abs(position.EntryPrice - position.StopLoss.Value);
        
        // Calculate the exact 1:1 target price
        double target1To1Price = position.TradeType == TradeType.Buy 
            ? position.EntryPrice + riskInPrice 
            : position.EntryPrice - riskInPrice;

        // Check if current price has reached the 1:1 threshold
        bool is1To1Reached = position.TradeType == TradeType.Buy 
            ? Symbol.Bid >= target1To1Price 
            : Symbol.Ask <= target1To1Price;

        if (is1To1Reached)
        {
            // Divide volume by 2 and safely normalize to broker step sizes
            double halfVolume = Symbol.NormalizeVolumeInUnits(position.VolumeInUnits / 2, RoundingMode.Down);
            
            if (halfVolume >= Symbol.VolumeInUnitsMin)
            {
                // 1. Close half the position asynchronously
                ClosePositionAsync(position, halfVolume);
                
                // 2. Optional: Roll remaining stop loss to Entry Price (Breakeven)
                ModifyPositionAsync(position, position.EntryPrice, position.TakeProfit);
                
                // 3. Mark this position ID so we never process it again
                _partiallyClosedPositions.Add(position.Id);
                
                Print("1:1 Reached. Partial taken and SL moved to BE for Position ID: " + position.Id);
            }
        }
    }
}
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: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Mistake: Ignoring Spreads When Choosing Setups

Post by PTScalper »

3. Prevent Memory Leaks

To keep the cBot running cleanly for weeks at a time, you need to flush the HashSet when a position fully closes (whether by hitting Take Profit, getting stopped out at Breakeven, or being manually closed). Otherwise, the memory footprint will slowly creep up.

Add this override method anywhere in your class:

Code: Select all

protected override void OnPositionsClosed(PositionClosedEventArgs args)
{
    // If the fully closed position is in our tracker, remove it
    if (_partiallyClosedPositions.Contains(args.Position.Id))
    {
        _partiallyClosedPositions.Remove(args.Position.Id);
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Mistake: Ignoring Spreads When Choosing Setups

Post by PTScalper »

By using Symbol.NormalizeVolumeInUnits with RoundingMode.Down, you guarantee that if you execute an odd lot size (like 0.03 lots), the bot safely rounds the partial down (closing 0.01 and leaving 0.02) rather than throwing an invalid volume rejection from the broker API.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Mistake: Ignoring Spreads When Choosing Setups

Post by PTScalper »

To implement a trailing stop that activates only after the 1:1 partial has been taken, we can use the _partiallyClosedPositions state tracker you just built.

The biggest trap when programming trailing stops in the OnTick() method is server spam. Because ticks come in milliseconds apart, if price moves up by a fraction of a pip, your bot might fire 50 overlapping asynchronous ModifyPositionAsync requests before the broker even processes the first one. This will trigger a "Concurrent Modification" error and can get your API connection temporarily blocked.

To solve this, we enforce a Step Buffer. The C# logic will calculate the new trailing stop price, but it will only send the request to the broker if the new stop loss is at least 1 full pip better than the current one.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Mistake: Ignoring Spreads When Choosing Setups

Post by PTScalper »

Here is how to integrate this phase-two execution logic.

1. Add the Trailing Parameter

Add a new parameter at the top of your class to define how closely you want to trail the price after the 1:1 mark is breached.

Code: Select all

[Parameter("Trailing Stop (Pips)", DefaultValue = 10.0, MinValue = 1.0, Step = 0.5)]
public double TrailingStopPips { get; set; }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Mistake: Ignoring Spreads When Choosing Setups

Post by PTScalper »

2. Upgrade the OnTick Method

Replace your OnTick() method with this two-phase structure. If the position ID is not in the tracker, it hunts for the 1:1 target. If it is in the tracker, it switches modes and dynamically trails the Stop Loss behind the current price.

Code: Select all

public override void OnTick()
{
    // Keeps the UI math updating
    UpdateDashboard();

    foreach (var position in Positions.FindAll("SpreadProtected", SymbolName))
    {
        // --- PHASE 1: Wait for 1:1 Partial ---
        if (!_partiallyClosedPositions.Contains(position.Id))
        {
            if (!position.StopLoss.HasValue) continue;

            double riskInPrice = Math.Abs(position.EntryPrice - position.StopLoss.Value);
            double target1To1Price = position.TradeType == TradeType.Buy 
                ? position.EntryPrice + riskInPrice 
                : position.EntryPrice - riskInPrice;

            bool is1To1Reached = position.TradeType == TradeType.Buy 
                ? Symbol.Bid >= target1To1Price 
                : Symbol.Ask <= target1To1Price;

            if (is1To1Reached)
            {
                double halfVolume = Symbol.NormalizeVolumeInUnits(position.VolumeInUnits / 2, RoundingMode.Down);
                if (halfVolume >= Symbol.VolumeInUnitsMin)
                {
                    ClosePositionAsync(position, halfVolume);
                    
                    // Move remaining SL to Breakeven
                    ModifyPositionAsync(position, position.EntryPrice, position.TakeProfit);
                    
                    // Mark as partialed, activating Phase 2 on the next tick
                    _partiallyClosedPositions.Add(position.Id);
                    Print("1:1 Reached. Partial taken. Trailing Stop Activated.");
                }
            }
        }
        // --- PHASE 2: Active Trailing Stop ---
        else
        {
            if (!position.StopLoss.HasValue) continue;

            double trailDistance = TrailingStopPips * Symbol.PipSize;
            
            // The 1-pip buffer prevents overlapping server modification spam
            double stepBuffer = Symbol.PipSize; 

            if (position.TradeType == TradeType.Buy)
            {
                // Calculate where the SL should be relative to current Bid
                double newSL = Symbol.Bid - trailDistance;

                // Only modify if the new SL locks in at least 1 pip more profit than the current SL
                if (newSL > position.StopLoss.Value + stepBuffer)
                {
                    double roundedSL = Math.Round(newSL, Symbol.Digits);
                    ModifyPositionAsync(position, roundedSL, position.TakeProfit);
                }
            }
            else // Sell Position
            {
                // Calculate where the SL should be relative to current Ask
                double newSL = Symbol.Ask + trailDistance;

                // Only modify if the new SL locks in at least 1 pip more profit than the current SL
                if (newSL < position.StopLoss.Value - stepBuffer)
                {
                    double roundedSL = Math.Round(newSL, Symbol.Digits);
                    ModifyPositionAsync(position, roundedSL, position.TakeProfit);
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Mistake: Ignoring Spreads When Choosing Setups

Post by PTScalper »

Now you have a fully-fledged execution terminal. You identify the structural liquidity sweep, map the Red/Green lines on your chart, and hit BUY.

The cBot verifies the spread won't kill your math, calculates exactly how many lots equal 1% of your equity, fires the market order, monitors it until it hits 1:1, rips off half the volume, moves the stop to breakeven, and immediately switches into an algorithmic trailing mode that rides the runner pip-by-pip until the trend reverses.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply