Page 1 of 2

Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:02 pm
by FXS Prop Desk
When an order fails — requote, off quotes, trade context busy, connection error — many EAs simply try again. Without limits, a retry loop can send dozens of orders in seconds, sometimes resulting in multiple filled positions.

Safe retry design:
  • Limit the number of retries (e.g. three).
  • Wait between retries, increasing the delay each time.
  • Check whether a position was actually opened before retrying, since a timeout does not always mean failure.
  • Retry only for errors where a retry makes sense; stop for errors like insufficient margin or invalid volume.
  • Log every attempt with error code and time.
Testing it:
  • Simulate errors in a test version of the EA.
  • Check behaviour after a connection loss during order sending.
A unique comment or magic number per signal helps the EA recognise a position it already opened.

How does your EA handle failed orders, and have you ever seen duplicate positions from retries?

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:20 pm
by PTScalper
FXS Prop Desk wrote: Wed Oct 07, 2026 6:02 pm When an order fails — requote, off quotes, trade context busy, connection error — many EAs simply try again. Without limits, a retry loop can send dozens of orders in seconds, sometimes resulting in multiple filled positions.

Safe retry design:
  • Limit the number of retries (e.g. three).
  • Wait between retries, increasing the delay each time.
  • Check whether a position was actually opened before retrying, since a timeout does not always mean failure.
  • Retry only for errors where a retry makes sense; stop for errors like insufficient margin or invalid volume.
  • Log every attempt with error code and time.
Testing it:
  • Simulate errors in a test version of the EA.
  • Check behaviour after a connection loss during order sending.
A unique comment or magic number per signal helps the EA recognise a position it already opened.

How does your EA handle failed orders, and have you ever seen duplicate positions from retries?
Hi,

yeah, you are 100 percent right.

When treating algorithmic trading infrastructure like an enterprise microservice, an EA cannot rely on naive while loops. The discrepancy between local terminal state and the broker's server state during a timeout is a classic distributed systems race condition that causes duplicate positions. Here is how to architect a fault-tolerant order execution pipeline.

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:21 pm
by PTScalper
State Synchronization and Idempotency

Instead of blindly firing another OrderSend or ExecuteMarketOrder upon receiving a timeout, the system must verify the broker's source of truth before taking further action.

Unique Signal Signatures: Generate a specific identifier for the exact price action setup. Encode this into the integer Magic Number or the string OrderComment.

State Verification: If a connection drops, the recovery routine iterates through the active positions pool to search for this exact signature. If found, the order was executed on the broker's end despite the dropped local acknowledgment, and the retry instantly aborts.

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:21 pm
by PTScalper
Smart Backoff and Error Routing

A robust execution engine routes broker return codes into strict execution paths to prevent logic loops.

Terminal Errors: Codes for insufficient margin, invalid stops, or trade restrictions trigger a hard, permanent abort.

Transient Errors: For "trade context busy" or requotes, apply an exponential backoff delay with randomized millisecond jitter so the terminal doesn't aggressively hammer the broker's API.

Price Invalidation: When trading raw price action structures on 15-minute or daily charts, a delayed retry risks executing in an entirely different market context. The loop must verify that the current Bid/Ask remains within the strict slippage tolerance of the original setup before executing the next attempt.

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:22 pm
by PTScalper
Enterprise-Grade Telemetry and Simulation

Standard local terminal logs are often insufficient for serious forensic analysis after a catastrophic gap or rejection wave.

Centralized Logging: Serialize every failed attempt—including timestamp, error code, spread, and latency—and push the payload via a REST API to a centralized SQL database. This allows for asynchronous analysis of execution quality across different platforms.

Chaos Engineering: Built-in Strategy Testers assume a zero-latency, perfect-execution environment. To truly test retry logic, especially in C# cAlgo environments, abstract the order interface and deliberately inject network timeouts into 20% of simulated orders to prove the state machine recovers without double-filling.

How are you currently handling the database schemas and data ingestion for your cross-platform automated rejection logging tools?

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:22 pm
by PTScalper
Unlike an MQL4/5 or C# cAlgo environment where you have direct synchronous access to the broker's API and order return codes, TradingView Pine Script operates on a completely different architectural paradigm.

In Pine Script, the execution engine is abstracted. The script evaluates bar-by-bar (or tick-by-tick) and sends abstract "Signals" to TradingView's servers, which then route them to the broker via Webhooks or native integration. You cannot catch live broker rejections (like requotes or margin errors) natively inside Pine Script code to trigger a while loop.

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:22 pm
by PTScalper
However, you can build the state synchronization, idempotency, and logging layers directly into the script to ensure it never sends a duplicate signal, and use limit orders to bypass requotes entirely.

Here is how to architect the idempotent signal engine in Pine Script (v5):

Code: Select all

//@version=5
strategy("Idempotent PA Execution Engine", overlay=true, calc_on_every_tick=true, margin_long=100, margin_short=100)

// -----------------------------------------------------------------------------
// 1. STATE MACHINE & IDEMPOTENCY TRACKING
// -----------------------------------------------------------------------------
// Variables to track the last executed state and prevent race conditions
// if the script calculates multiple times on the same bar.
var string  last_signal_id = ""
var int     last_trade_bar = 0

// -----------------------------------------------------------------------------
// 2. SIGNAL GENERATION (Price Action Scalping Placeholder)
// -----------------------------------------------------------------------------
// Example: Bullish engulfing on the 15m/Daily structure
bullish_engulfing = close > open and close[1] < open[1] and close > open[1] and open < close[1]
valid_setup = bullish_engulfing 

// -----------------------------------------------------------------------------
// 3. EXECUTION LOGIC
// -----------------------------------------------------------------------------
// Generate a highly specific signature for this exact setup
string current_signal_id = "Long_PA_" + str.tostring(time)

// Idempotency Check: 
// 1. Are we flat? (strategy.position_size == 0)
// 2. Have we already fired on this specific candle? (bar_index > last_trade_bar)
// 3. Is the signal ID unique?
bool can_execute = valid_setup and strategy.position_size == 0 and bar_index > last_trade_bar and current_signal_id != last_signal_id

if can_execute
    // To prevent Requotes/Off Quotes, use Limit orders instead of Market orders.
    // This forces the broker to execute at your price or better, eliminating the
    // need for a retry loop caused by spread widening.
    entry_price = close
    
    // Fire the execution
    strategy.entry(id=current_signal_id, direction=strategy.long, limit=entry_price)
    
    // Lock the state machine
    last_signal_id := current_signal_id
    last_trade_bar := bar_index

    // -------------------------------------------------------------------------
    // 4. ENTERPRISE TELEMETRY & LOGGING
    // -------------------------------------------------------------------------
    // Push the execution attempt to the Pine Script log console for debugging
    log.info("ATTEMPT | ID: {0} | Price: {1} | Spread: {2}", current_signal_id, entry_price, close - open)
    
    // Generate a structured JSON payload for external Webhook databases (e.g., a C# backend API)
    string alert_payload = "{"
      + "\"signal_id\": \"" + current_signal_id + "\", "
      + "\"action\": \"buy\", "
      + "\"price\": " + str.tostring(entry_price) + ", "
      + "\"timestamp\": " + str.tostring(time)
      + "}"
    
    // Send to external database/handler
    alert(alert_payload, alert.freq_once_per_bar)

// -----------------------------------------------------------------------------
// 5. STATE RECONCILIATION
// -----------------------------------------------------------------------------
// If an order is pending for too long (price ran away), cancel it to prevent 
// a late execution in a different market context.
int max_bars_to_wait = 3
if strategy.opentrades == 0 and (bar_index - last_trade_bar) >= max_bars_to_wait
    strategy.cancel_all()
    if bar_index - last_trade_bar == max_bars_to_wait
        log.warning("TIMEOUT | ID: {0} | Order cancelled due to price moving away.", last_signal_id)

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:23 pm
by PTScalper
The Webhook Middle-Tier Architecture

Because Pine Script cannot read broker error codes, serious algorithmic implementations using TradingView do not connect TradingView directly to the broker.

Instead, the Pine Script alert() function sends the JSON payload (like the one generated above) to a custom middle-tier webhook listener (typically a C# or Python microservice). This middle-tier server is where your retry loop lives.

1.) Pine Script identifies the setup and ensures a duplicate signal is never sent.

2.) The Webhook API receives the signal_id.

3.) The server connects to the broker via FIX API or cTrader/MT5 bridging.

4.) If the broker throws ERR_TRADE_CONTEXT_BUSY, your server handles the exponential backoff, queries the account for the signal_id to prevent double-filling, and logs the specific broker rejection directly into your SQL database.

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:24 pm
by PTScalper
Building fault-tolerant order execution in MetaTrader requires mapping terminal-specific error codes and navigating the architectural differences between MT4's synchronous thread locks and MT5's asynchronous queues.

Here are production-ready state machine wrappers for both environments.

Re: Order Retry Logic in EAs: Avoiding Order Storms After Server Errors

Posted: Wed Oct 07, 2026 6:24 pm
by PTScalper
MT4 (MQL4): The Synchronous Wrapper

In MT4, the client terminal is locked to a single thread during execution. The most common cause of failed orders is ERR_TRADE_CONTEXT_BUSY (146), which happens when the terminal is busy processing another EA's request.

This wrapper verifies the position pool first, clears the terminal lock, attempts execution, and routes errors based on broker feedback.

Code: Select all

int ExecuteSafeOrder_MT4(string symbol, int cmd, double volume, double price, int slippage, double sl, double tp, int magic, string comment) {
    int maxRetries = 3;
    int delayMs = 500;
    
    for(int attempt = 1; attempt <= maxRetries; attempt++) {
        // 1. Idempotency Check: Did a previous timeout actually fill at the broker?
        for(int i = 0; i < OrdersTotal(); i++) {
            if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
                if(OrderSymbol() == symbol && OrderMagicNumber() == magic) {
                    PrintFormat("ABORT RETRY: Position already open for Magic %d", magic);
                    return OrderTicket(); // Successfully filled
                }
            }
        }
        
        // 2. Clear terminal locks & update pricing
        while(IsTradeContextBusy()) Sleep(100);
        RefreshRates();
        
        // 3. Execution Attempt
        int ticket = OrderSend(symbol, cmd, volume, price, slippage, sl, tp, comment, magic, 0, clrNONE);
        
        if(ticket > 0) return ticket; // Execution Success
        
        // 4. Error Routing
        int err = GetLastError();
        // Transient errors: Broker busy, Requote, Context busy, Timeout
        if(err == 136 || err == 138 || err == 146 || err == 128) {
            PrintFormat("Transient Error %d - Attempt %d/%d. Retrying...", err, attempt, maxRetries);
            Sleep(delayMs + (MathRand() % 250)); // Exponential backoff with Jitter
            delayMs *= 2; 
        } else {
            // Terminal Errors: Not enough money (134), Invalid stops (130)
            PrintFormat("Terminal Error %d - Aborting signal execution.", err);
            break; 
        }
    }
    return -1;
}