Page 1 of 1

[CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Tue Aug 11, 2026 10:12 pm
by PTScalper
Hi scalpers,

Let's dive into execution safety.

Using ModifyPositionAsync is fantastic for performance because it’s a "fire-and-forget" command that doesn't freeze your OnTick thread. But that’s also its biggest danger: if you just fire and forget, you have no idea if the broker actually accepted the modification.

During high-impact news, spreads widen rapidly and price gaps occur. Your cBot might calculate a perfectly valid Stop Loss locally, but by the time the request hits the broker's server 50 milliseconds later, the price has gapped, making your Stop Loss invalid (usually triggering an InvalidStopLoss error).

To catch this, we use a Callback via the Action<TradeResult> parameter that cTrader provides in its async methods.

The Code Implementation
You don't need to rewrite the whole bot. You just need to update the lines where we call ModifyPositionAsync and add a lambda expression to handle the server's response.

Here is how you update the modification logic inside your ManageTrailingStop and CheckBreakEven methods:

Code: Select all

// Inside your Buy/Sell logic where you calculate newStopLoss...

if (position.StopLoss == null || newStopLoss >= position.StopLoss + (TrailStep * Symbol.PipSize))
{
    // We add a lambda callback (result => { ... }) to read the broker's response
    ModifyPositionAsync(position, newStopLoss, position.TakeProfit, result =>
    {
        if (result.IsSuccessful)
        {
            // Optional: Log success for your own tracking
            Print("✅ SUCCESS: Position {0} SL moved to {1}", position.Id, newStopLoss);
        }
        else
        {
            // The broker rejected the request. We catch the exact error code.
            Print("🚨 ERROR: Failed to modify SL for Position {0}. Reason: {1}", position.Id, result.Error);
            
            // Example of how to handle specific critical errors
            if (result.Error == ErrorCode.InvalidStopLoss)
            {
                Print("⚠️ The requested SL of {0} is too close to current price or on the wrong side.", newStopLoss);
            }
            else if (result.Error == ErrorCode.MarketClosed)
            {
                Print("⚠️ Market is closed, modification rejected.");
            }
            
            // Advanced: You could trigger an email or push notification to your phone here
            // Notifications.SendEmail("you@email.com", "you@email.com", "cBot Error", result.Error.ToString());
        }
    });
}
Expert Notes on TradeResult
When you pass that callback function into the async request, cTrader hands you back a TradeResult object once the server replies. Here is why this is so powerful:

result.IsSuccessful: This boolean is your first line of defense. If it’s false, something went wrong on the broker's end.

result.Error: This returns an ErrorCode enum. In automated trading, not all errors are created equal. An InvalidStopLoss usually just means the market moved faster than your code, and the bot will naturally try again on the next tick. However, an error like Disconnected or NoMoney requires immediate human intervention.

No Thread Blocking: Because the callback is handled asynchronously, checking for these errors and printing them to the log does not slow down your OnTick method. The main thread keeps processing live price data.

Push Notifications: The else block of an async failure is the perfect place to drop in cTrader's native Notifications.SendPushNotification() method, so your phone buzzes instantly if the broker starts rejecting your stop-loss updates during a volatile session.

Take a care, bye bye, have a great trades.

Re: [CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Thu Sep 03, 2026 7:53 am
by FTtrader
Good morning traders.
Hi PTScalper,

i check it out and yeah, this is good basic level code.
I actually extend that little bit.

Here is the refactored, professional standard for cTrader/cAlgo:

Code: Select all

// 1. Normalize the price to the symbol's digits to prevent floating-point broker rejections
double roundedNewSl = Math.Round(newStopLoss, Symbol.Digits);
double trailStepPrice = TrailStep * Symbol.PipSize;

// 2. Determine if an update is required, dynamically handling BOTH Long and Short positions
bool requiresUpdate = !position.StopLoss.HasValue ||
    (position.TradeType == TradeType.Buy  && roundedNewSl >= position.StopLoss.Value + trailStepPrice) ||
    (position.TradeType == TradeType.Sell && roundedNewSl <= position.StopLoss.Value - trailStepPrice);

if (requiresUpdate)
{
    ModifyPositionAsync(position, roundedNewSl, position.TakeProfit, result =>
    {
        // 3. Early return on success to keep code flat and readable
        if (result.IsSuccessful)
        {
            Print($"✅ SUCCESS: Position {position.Id} SL moved to {roundedNewSl}");
            return; 
        }

        // 4. Handle failures
        Print($"🚨 ERROR: Failed to modify SL for Position {position.Id}. Reason: {result.Error}");
        
        // 5. Use a switch statement for cleaner, scalable error handling
        switch (result.Error)
        {
            case ErrorCode.InvalidStopLoss:
                Print($"⚠️ SL ({roundedNewSl}) is invalid. Often too close to price or on the wrong side.");
                break;
            
            case ErrorCode.MarketClosed:
                Print("⚠️ Modification rejected: Market is currently closed.");
                break;

            case ErrorCode.Disconnected:
                Print("⚠️ Modification rejected: Connection to broker lost.");
                break;
                
            case ErrorCode.NoMoney: // Edge cases matter in production
                Print("⚠️ Modification rejected: Insufficient margin.");
                break;
        }

        // Advanced: Notify on failure
        // Notifications.SendEmail("you@email.com", "you@email.com", $"cBot Error: {position.Id}", result.Error.ToString());
    });
}

Re: [CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Thu Sep 03, 2026 7:54 am
by FTtrader
Why this is better (The "Pro" Upgrades):

Price Normalization (Math.Round):

Broker APIs expect prices to match the exact decimal precision of the asset (e.g., 5 digits for EURUSD). If your math generates 1.0543200000001, the broker will instantly reject it with an InvalidStopLoss error. Rounding to Symbol.Digits fixes this.

Direction Awareness (TradeType.Buy / TradeType.Sell):

Your original code assumed a Long (Buy) position where trailing stops only move up (>=). If your bot takes a Short trade, it would immediately break. This code safely handles both directions.

String Interpolation ($"{...}"):

Using the modern C# string interpolation ($"{position.Id}") is much easier to read and maintain than the older comma-separated format ("{0}", position.Id).

Early Exit (return;):
By returning immediately upon success, you avoid wrapping all your error-handling logic inside a massive else { ... } block, keeping your code flat and readable.

Switch Statement:
An if/else if chain gets messy quickly. A switch block is the industry standard for handling API error codes, making it incredibly easy to add new cases later.

Re: [CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Thu Sep 03, 2026 7:56 am
by FTtrader
Plus here is even better version:

To take this from a "good script" to an institutional-grade algorithmic trading system, we need to introduce three major concepts: Separation of Concerns (splitting logic from error handling), Broker Constraints (checking minimum allowed distances before asking the broker), and State Safety (ensuring we don't modify closing positions).

Here is the elite, decoupled architecture:

Code: Select all

// 1. The main trailing logic method (Call this from OnTick or OnBar)
private void UpdateTrailingStop(Position position, double newStopLoss, double trailStepPips)
{
    // Safety 1: Skip if position is null or already in the process of closing
    if (position == null || position.ClosingVolume > 0) 
        return;

    double roundedNewSl = Math.Round(newStopLoss, Symbol.Digits);
    double trailStepPrice = trailStepPips * Symbol.PipSize;

    // Safety 2: Ensure the new SL respects the broker's strict minimum distance (StopLevel)
    double currentPrice = position.TradeType == TradeType.Buy ? Symbol.Bid : Symbol.Ask;
    double minStopDistance = Symbol.PipSize * Symbol.StopLevel;
    
    if (Math.Abs(currentPrice - roundedNewSl) <= minStopDistance)
    {
        // SL is too close to current price. Skip API call to prevent error spam.
        return;
    }

    // Safety 3: Clean directional trailing logic
    bool isLong = position.TradeType == TradeType.Buy;
    bool hasNoSl = !position.StopLoss.HasValue;
    
    bool canTrailLong = isLong && roundedNewSl >= (position.StopLoss ?? 0) + trailStepPrice;
    bool canTrailShort = !isLong && roundedNewSl <= (position.StopLoss ?? double.MaxValue) - trailStepPrice;

    if (hasNoSl || canTrailLong || canTrailShort)
    {
        // Fire and Forget, routing the response to a dedicated handler
        ModifyPositionAsync(position, roundedNewSl, position.TakeProfit, result => 
            HandleModificationResult(result, position.Id, roundedNewSl));
    }
}

// 2. A dedicated, centralized error handler
private void HandleModificationResult(TradeResult result, int positionId, double requestedSl)
{
    if (result.IsSuccessful)
    {
        Print($"✅ Position {positionId} Trailing Stop moved to {requestedSl}");
        return;
    }

    Print($"🚨 Failed to modify SL for Position {positionId}. Error: {result.Error}");

    switch (result.Error)
    {
        case ErrorCode.InvalidStopLoss:
            Print($"⚠️ SL ({requestedSl}) rejected. Price likely spiked during network transit.");
            break;
            
        case ErrorCode.EntityNotFound:
            Print("⚠️ Position already hit Take Profit/Stop Loss before modification arrived.");
            break;

        case ErrorCode.MarketClosed:
            Print("🛑 Market closed. Suspending bot to prevent API spam.");
            Stop(); // Auto-kill switch
            break;

        case ErrorCode.Disconnected:
        case ErrorCode.TechnicalError:
            Print("🔌 Transient network error. Will attempt again on the next tick.");
            break;
            
        default:
            // Optional: Trigger urgent alerts for unknown critical errors
            // Notifications.SendEmail("admin@fund.com", "admin@fund.com", $"Bot Error: {result.Error}", "");
            break;
    }
}

Re: [CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Thu Sep 03, 2026 7:57 am
by FTtrader
Why this is Top-Tier:

Broker Minimum Distance (Symbol.StopLevel):

Brokers have strict rules about how close a Stop Loss can be to the current price. The previous code would blindly send the request and get rejected. This version calculates the broker's exact limit and skips the API call entirely if it's too close, saving bandwidth and preventing log spam.

Ghost Trade Prevention (ClosingVolume > 0):

If your bot tries to modify a position at the exact millisecond the user (or another bot) clicks "Close," it causes a system conflict. Checking ClosingVolume prevents this race condition.

The Auto-Kill Switch:

If the market is closed, the previous code would spam the broker with modification requests every tick, which can get your API key banned. This version gracefully calls Stop() to shut the bot down.

Modular Design:

By moving HandleModificationResult into its own method, your OnTick or OnBar methods stay incredibly clean, and you can reuse this error handler for Take Profits and partial closes as well.

Re: [CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Thu Sep 03, 2026 7:59 am
by FTtrader
Plus to make it even better, i think it is important to extend in with retry system.

To build an institutional-grade retry system, we must follow three strict rules:

Never block the main thread:

If you use Thread.Sleep(), your entire bot freezes and misses incoming ticks.

Only retry transient errors:

Retrying a Disconnected error makes sense; retrying an InvalidStopLoss will just result in infinite rejections.

Use Exponential Backoff:

Wait a little longer after each failed attempt (e.g., 1 second, then 2 seconds, then 3 seconds) so you don't spam the broker and trigger a DDoS protection ban.

To achieve this in cTrader, we use modern C# async/await alongside a TaskCompletionSource to wrap cAlgo's callback-based API safely.

Here is the professional retry architecture:

Code: Select all

using System.Threading.Tasks; // Add this to the top of your file

// 1. The Async Retry Engine
private async void ModifyPositionWithRetryAsync(Position position, double newStopLoss, int maxRetries = 3)
{
    int attempt = 0;
    bool success = false;

    while (attempt < maxRetries && !success)
    {
        attempt++;
        
        // Create a TaskCompletionSource to turn the callback into an awaitable Task
        var tcs = new TaskCompletionSource<TradeResult>();
        
        // Ensure API calls are safely executed on the main cTrader thread
        BeginInvokeOnMainThread(() =>
        {
            ModifyPositionAsync(position, newStopLoss, position.TakeProfit, result => tcs.SetResult(result));
        });

        // Yield control back to the bot while we wait for the broker's response
        TradeResult result = await tcs.Task;

        if (result.IsSuccessful)
        {
            Print($"✅ [Attempt {attempt}/{maxRetries}] Position {position.Id} SL moved to {newStopLoss}");
            success = true;
        }
        else
        {
            // 2. Check if the error is transient (network issues, broker hiccups)
            if (result.Error == ErrorCode.Disconnected || result.Error == ErrorCode.TechnicalError)
            {
                int delayMs = attempt * 1000; // Exponential backoff: 1s, 2s, 3s...
                Print($"🔌 Transient error ({result.Error}). Retrying in {delayMs}ms...");
                
                // Wait asynchronously without freezing the bot
                await Task.Delay(delayMs); 
            }
            else 
            {
                // 3. Fatal error (e.g., MarketClosed, InvalidStopLoss). Abort immediately.
                Print($"🚨 Fatal Error on attempt {attempt}: {result.Error}. Aborting retries.");
                break; 
            }
        }
    }

    // 4. Log if all attempts were exhausted
    if (!success && attempt >= maxRetries)
    {
        Print($"❌ CRITICAL: Failed to modify Position {position.Id} after {maxRetries} attempts.");
        // Notifications.SendEmail("admin@fund.com", "admin@fund.com", "Bot Network Failure", "Check VPS connection.");
    }
}

Re: [CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Thu Sep 03, 2026 8:00 am
by FTtrader
How to use it in your existing code

You simply replace the standard ModifyPositionAsync call in your main trailing logic with this new method.

Code: Select all

// Inside your UpdateTrailingStop method from earlier...
if (hasNoSl || canTrailLong || canTrailShort)
{
    // Fire the async retry engine and let it manage itself
    ModifyPositionWithRetryAsync(position, roundedNewSl, maxRetries: 3);
}
Why this is Top-Tier (The "Pro" Upgrades):

TaskCompletionSource:

cTrader's ModifyPositionAsync uses a callback method (result => { ... }), which normally causes "callback hell." By wrapping it in TaskCompletionSource, we convert it into a modern, awaitable C# Task. This makes the while loop possible.

Thread Safety (BeginInvokeOnMainThread):

When Task.Delay finishes waiting, C# resumes the code on a random background thread. cTrader will instantly crash your bot if you try to modify a position from a background thread. BeginInvokeOnMainThread forces the actual API request safely back onto the main thread.

Exponential Backoff (attempt * 1000):

If the broker's server blips, hitting them instantly with another request might fail again or trigger their rate limits. Waiting 1 second, then 2, then 3 gives the network time to stabilize.

Re: [CODE SHARE Pt. 3] Bulletproofing execution: Handling Async Server Errors

Posted: Wed Sep 23, 2026 10:17 pm
by LondonScalper
FTtrader wrote:Plus here is even better version: To take this from a "good script" to an institutional-grade algorithmic trading system , we need to introduce three major concepts: Separation of Concerns (splitting logic from error handling), Broker Constraints (checking minimum allowed distances before asking the broker),
Separation of concerns and broker minimum-distance checks are the difference between a demo hero and a live morning of rejects. Async fills without handling partials and requotes will invent P&amp;L you never intended.

I want execution code that fails safe: if stop distance is illegal, skip the ticket; do not silently widen into nonsense. That habit has saved more sessions than any entry tweak.

Institutional-grade is a strong word; “does not lie under stress” is the bar I use.

Do you unit-test the constraint layer with deliberate illegal stops, or only discover it live?