Blog Post

Read more about Whale's Secret.

Trading Bot in C# — Part 6— Reconnecting Strategies

The previous blog post covered order canceling and how to retrieve active orders. This time, we will discuss how to react to exchange service disruptions.

Motivation

If you are serious about algorithmic trading, you need to think about various outages as they are inevitable in practice. Even if your trading strategy appears solid on paper, it’s common for a strategy that seems profitable in theory to underperform in real-world conditions.

Failing to take into account that each exchange can sometimes enter a maintenance mode — to release new features, resolve bugs, upgrade systems, etc. — can lead to significant financial consequences. Under this circumstance, trading is halted on the exchange while participants on other exchanges continue to trade, impacting asset prices. For instance, if you hold a position, you might face a loss if you cannot close the position in a timely manner.

In our experience, however, network issues are more prevalent than exchange issues. Networks typically do not provide stability guarantees, and your connection to an exchange can potentially be disrupted literally at any moment. So you may encounter the following scenarios:

  • Your API request won’t reach its target exchange server. Thus, for example, you may fail to cancel an existing order.
  • Your API request reaches the target server but the response won’t be received. In this case, you can create an order but not know about it.
  • Your API request can reach the target sever much later than expected. For a market order, this typically means that your order execution price may differ from your expectation.

These issues are not to be taken lightly. ScriptApiLib does its best to recover from such situations, and it is certainly not trivial to do so.

Controlling Internet Access

To demonstrate the connection strategies provided by ScriptApiLib, you need to be able to disconnect your Internet connection. This can be done manually through your operating system or by using a command-line command. On Windows, one can turn off and turn on Wi-Fi using these simple commands in PowerShell or using the command prompt (i.e. cmd.exe):

// sudo requires Windows 11, version 24H2 or higher. See https://learn.microsoft.com/en-us/windows/sudo/.
// Check out "netsh interface show interface" if your Wi-Fi is called "Wi-Fi".
sudo netsh interface set interface name="Wi-Fi" admin=DISABLED
sudo netsh interface set interface name="Wi-Fi" admin=ENABLED

Monitoring Connection Status

Having learned how to manage Internet access, we can effortlessly simulate our trading bot experiencing disconnections and subsequent reconnections. ScriptApiLib provides the ability to specify callbacks in ConnectionOptions that are called whenever we lose connection to the exchange or successfully reconnect. These callbacks are OnConnectedDelegateAsync and OnDisconnectedDelegateAsync:

ConnectionOptions connectionOptions = new(ConnectionType.FullTrading, 
    onConnectedAsync: OnConnectedAsync,        // <-- IMPORTANT.
    onDisconnectedAsync: OnDisconnectedAsync); // <-- IMPORTANT.
ITradeApiClient client = await scriptApi.ConnectAsync(ExchangeMarket.BinanceSpot, connectionOptions);
Print("Connected to Binance sandbox.");

// Use: sudo netsh interface set interface name="Wi-Fi" admin=DISABLED
Print("Waiting 60 seconds for you to make sure your machine is offline.");
await Task.Delay(60_000);

// Use: sudo netsh interface set interface name="Wi-Fi" admin=ENABLE
Print("Waiting 120 seconds for you to make sure your machine is online.");
await Task.Delay(120_000);

Task OnConnectedAsync(ITradeApiClient tradeApiClient)
{
    Print("Connected again.");
    return Task.CompletedTask;
}

Task OnDisconnectedAsync(ITradeApiClient tradeApiClient)
{
    Print("Disconnected.");
    return Task.CompletedTask;
}

The output of the sample is as follows:

[08:20:15.907] Connected to Binance sandbox.
[08:20:15.908] Waiting 60 seconds for you to make sure your machine is offline.
[08:20:18.295] Disconnected.
[08:21:15.919] Waiting 120 seconds for you to make sure your machine is online.
[08:22:22.267] Connected again.
Please note that “Connected again.” is not printed right after we get connected for the first time, only on subsequent re-connections. This is a feature, not a bug.

You might observe that disconnections and re-connections are not detected right away. This is a typical characteristic of networking protocols, such as TCP/IP, and it is a consequence of the inherent unreliability of networks as a fundamental design principle.

Disconnection Handling Strategies

ScriptApiLib currently provides two connection strategies that influence its behavior when it is directed to execute an action that requires exchange data or API access. Let’s explore these strategies in detail.

Strategy: Fail Instantly If Not Connected

The first strategy is FailInstantlyIfNotConnected and it behaves in the following fashion:

  • When the connection to the exchange is active, all exchange API requests are handled as usual.
  • If the connection to the exchange is lost and a new API request is sent to the exchange, the request will fail immediately, resulting in a NotConnectedException being thrown.
Instead of NotConnectedException, you may get OperationFailedException which is thrown when the connection is lost, but the library has not yet recognized it as disconnected.

The following sample demonstrates the connection strategy by intentionally causing the connection to be lost and then attempting to place a market order:

// Use the FailInstantlyIfNotConnected connection strategy.
ConnectionOptions connectionOptions = new(FailInstantlyIfNotConnected.Instance, 
    onConnectedAsync: OnConnectedAsync, 
    onDisconnectedAsync: OnDisconnectedAsync);
ITradeApiClient client = await scriptApi.ConnectAsync(ExchangeMarket.BinanceSpot, connectionOptions);
Print("Connected to Binance sandbox.");

// Use: sudo netsh interface set interface name="Wi-Fi" admin=DISABLED
Print("Waiting 60 seconds for you to make sure your machine is offline.");
await Task.Delay(60_000);

try
{
    Print("Attempt to create a market order while disconnected.");
    await client.CreateMarketOrderAsync(SymbolPair.BTC_EUR, OrderSide.Buy, size: 0.0001m);
    
    throw new SanityCheckException("Should not be called.");
}
catch (NotConnectedException)
{
    Print($"{nameof(NotConnectedException)} was thrown as expected!");
}

try
{
    // Similarly, getting open orders will throw the same exception, even
    // though the method is querying a local storage and does not send an
    // explicit API request to Binance. The issue is that "not being online
    // could lead to returning wrong data".
    Print("Attempt to retrieve all open orders while disconnected.");
    IReadOnlyList openOrders = await client.GetOpenOrdersAsync();

    throw new SanityCheckException("Should not be called.");
}
catch (NotConnectedException)
{
    Print($"{nameof(NotConnectedException)} was thrown as expected!");
}

Task OnConnectedAsync(ITradeApiClient tradeApiClient)
{
    Print("Connected again.");
    return Task.CompletedTask;
}

Task OnDisconnectedAsync(ITradeApiClient tradeApiClient)
{
    Print("Disconnected.");
    return Task.CompletedTask;
}

// Use: sudo netsh interface set interface name="Wi-Fi" admin=ENABLED
Print("The demonstration is over. You can re-enable your Internet access.");

The sample output is:

[08:06:10.926] Connected to Binance sandbox.

[08:06:10.927] Waiting 60 seconds for you to make sure your machine is offline.
[08:06:58.606] Disconnected.
[08:07:10.936] Attempt to create a market order while disconnected.
[08:07:10.946] NotConnectedException was thrown as expected!

[08:07:10.946] Attempt to retrieve all open orders while disconnected.
[08:07:10.949] NotConnectedException was thrown as expected!
Please note that NotConnectedException is intentionally thrown even when one queries the local storage of open orders by calling GetOpenOrdersAsync. This is a precautionary measure to prevent retrieving an active order set that may have changed since we lost our connection.

Strategy: Block Until Reconnected Or Timeout

Now, let’s discuss the BlockUntilReconnectedOrTimeout strategy, which requires a parameter known as PreRequestTimeout and operates as follows:

  • When there is an active connection to the exchange, then all exchange API requests are processed normally.
  • If the connection to the exchange is lost and a new API request is sent to the exchange, NotConnectedException is thrown only if the PreRequestTimeout elapses without establishing a new exchange connection to the exchange. If a new connection is established before PreRequestTimeout elapses, the API request will be sent at that time.
Tip: If you can tolerate arbitrary long disconnections, you can use BlockUntilReconnectedOrTimeout.InfinityTimeoutInstance and thus avoid NotConnectedException exceptions.

The following sample simulates a straightforward scenario:

  • Connect to Binance,
  • disconnect from the Internet,
  • ask to place a limit order and quickly make sure to re-connect.

Utilizing BlockUntilReconnectedOrTimeout makes sure that such a limit order is actually placed:

// Use the BlockUntilReconnectedOrTimeout connection strategy.
BlockUntilReconnectedOrTimeout connectionStrategy = new(
    preRequestTimeout: TimeSpan.FromSeconds(60)); // <-- IMPORTANT.
ConnectionOptions connectionOptions = new(connectionStrategy, 
    onConnectedAsync: OnConnectedAsync, 
    onDisconnectedAsync: OnDisconnectedAsync);
ITradeApiClient client = await scriptApi.ConnectAsync(ExchangeMarket.BinanceSpot, connectionOptions);
Print("Connected to Binance sandbox.");

// Subscribe BTC/EUR order book data.
IOrderBookSubscription subscription = await client.CreateOrderBookSubscriptionAsync(SymbolPair.BTC_EUR);

// Use: sudo netsh interface set interface name="Wi-Fi" admin=DISABLED
Print("Waiting 40 seconds for you to make sure your machine is offline.");
await Task.Delay(40_000);

Task<ILiveLimitOrder> createOrderTask = client.CreateLimitOrderAsync(SymbolPair.BTC_EUR, OrderSide.Buy, price: 90_000m, size: 0.0001m);
Task<OrderBook> orderBookTask = subscription.GetOrderBookAsync(CancellationToken.None);

// Use: sudo netsh interface set interface name="Wi-Fi" admin=ENABLE
Print("Make sure your machine is online. Waiting 60 seconds.");
await Task.Delay(60_000);

// The assumption is that we are connected again.
ILiveLimitOrder limitOrder = await createOrderTask;
Print($"Limit order '{limitOrder}' was placed!");

OrderBook orderBook = await orderBookTask;
Print("Received order book:");
Print(orderBook.ToFullString());

The sample output is:

[10:29:42] Connected to Binance sandbox.
[10:29:43] Waiting 10 seconds for you to make sure your machine is offline.
[10:30:23] Waiting 60 seconds for you to make sure your machine is online.
[10:31:23] Limit order '[ExchangeMarket=BinanceSpot,ClientOrderId=`FyWZLbhFUd`,ExchangeOrderId=`BTC/EUR-1101075`,OrderStatus=Accepted,CreateTime=05/15/2025 10:30:34,LastUpdateTime=05/15/2025 10:30:34,Size=0.0001,latestFillData=`[CumulativeSize=0,CumulativeAveragePrice=,LastSize=0,LastAveragePrice=,ImpliedFill=False,LastFee=,LastFeeSymbol=``]`,terminalEvent.IsSet=False]' was placed!
[10:31:23] Received order book:
[10:31:23] [ExchangeSymbolPair=`BinanceSpot@BTC/EUR`,LastUpdateId=2,LastUpdate=05/15/2025 10:30:15,Bids=[[Price=90994.70000000,Quantity=0.09902000],[Price=90985.31000000,Quantity=0.08991000],[Price=90985.30000000,Quantity=0.09183000],[Price=90978.57000000,Quantity=0.06123000],[Price=90978.46000000,Quantity=0.10355000],[Price=90975.01000000,Quantity=0.07717000],[Price=90974.43000000,Quantity=0.08717000],[Price=90974.42000000,Quantity=0.10047000],[Price=90974.34000000,Quantity=0.08948000],[Price=90971.16000000,Quantity=0.08569000],[Price=90971.15000000,Quantity=0.10537000],[Price=90970.49000000,Quantity=0.09229000],[Price=90969.47000000,Quantity=0.08283000],[Price=90967.79000000,Quantity=0.08828000],[Price=90966.18000000,Quantity=0.08025000],[Price=90966.17000000,Quantity=0.07701000],[Price=90957.02000000,Quantity=0.08911000],[Price=89838.84000000,Quantity=0.00107000],[Price=88800.24000000,Quantity=0.00107000]],Asks=[[Price=90994.72000000,Quantity=0.05684000],[Price=90996.12000000,Quantity=0.08177000],[Price=90996.13000000,Quantity=0.07831000],[Price=90996.88000000,Quantity=0.08913000],[Price=90997.92000000,Quantity=0.10721000],[Price=90997.93000000,Quantity=0.08913000],[Price=90998.73000000,Quantity=0.08841000],[Price=90999.35000000,Quantity=0.07852000],[Price=91000.04000000,Quantity=0.09995000],[Price=91009.58000000,Quantity=0.08472000],[Price=91009.59000000,Quantity=0.08983000],[Price=91009.62000000,Quantity=0.08181000],[Price=91009.65000000,Quantity=0.08055000],[Price=91010.16000000,Quantity=0.07703000],[Price=91010.64000000,Quantity=0.10356000],[Price=91012.50000000,Quantity=0.08906000],[Price=91014.96000000,Quantity=0.09482000],[Price=91029.94000000,Quantity=0.08355000],[Price=91085.89000000,Quantity=0.07762000],[Price=91226.00000000,Quantity=0.08238000],[Price=93055.27000000,Quantity=0.00538000],[Price=93091.91000000,Quantity=0.00269000],[Price=93113.84000000,Quantity=0.00538000],[Price=93135.84000000,Quantity=0.00269000],[Price=93137.97000000,Quantity=0.00269000],[Price=93137.99000000,Quantity=0.00269000],[Price=93204.52000000,Quantity=0.00268000],[Price=93218.30000000,Quantity=0.00268000],[Price=93305.45000000,Quantity=0.00268000],[Price=93311.94000000,Quantity=0.00804000],[Price=93314.06000000,Quantity=0.00268000],[Price=93332.73000000,Quantity=0.00268000],[Price=93376.75000000,Quantity=0.00268000],[Price=93377.20000000,Quantity=0.00268000],[Price=93378.17000000,Quantity=0.00268000],[Price=93380.97000000,Quantity=0.00268000],[Price=93382.44000000,Quantity=0.00268000],[Price=93385.24000000,Quantity=0.00268000],[Price=93388.57000000,Quantity=0.00268000],[Price=93391.98000000,Quantity=0.00268000],[Price=93402.55000000,Quantity=0.00268000],[Price=93408.69000000,Quantity=0.00268000],[Price=93409.37000000,Quantity=0.00268000],[Price=93420.39000000,Quantity=0.00268000],[Price=93451.37000000,Quantity=0.00268000],[Price=93452.12000000,Quantity=0.00268000],[Price=93454.88000000,Quantity=0.00268000],[Price=93458.53000000,Quantity=0.00268000],[Price=93461.76000000,Quantity=0.00268000],[Price=93539.44000000,Quantity=0.00268000],[Price=93551.87000000,Quantity=0.00536000],[Price=93551.90000000,Quantity=0.00268000],[Price=93589.42000000,Quantity=0.00267000],[Price=93671.22000000,Quantity=0.00267000],[Price=93672.06000000,Quantity=0.00267000],[Price=93687.08000000,Quantity=0.00801000],[Price=93697.54000000,Quantity=0.00267000],[Price=93703.35000000,Quantity=0.00267000],[Price=93707.05000000,Quantity=0.00267000],[Price=93733.08000000,Quantity=0.00267000],[Price=93993.24000000,Quantity=0.00107000],[Price=94018.57000000,Quantity=0.00263000],[Price=94058.12000000,Quantity=0.00263000],[Price=94286.39000000,Quantity=0.00019000],[Price=94784.11000000,Quantity=0.00264000],[Price=94818.67000000,Quantity=0.00264000],[Price=94826.79000000,Quantity=0.00264000],[Price=95031.84000000,Quantity=0.00107000],[Price=96070.44000000,Quantity=0.00107000],[Price=97109.04000000,Quantity=0.00107000],[Price=98147.64000000,Quantity=0.00107000],[Price=99025.82000000,Quantity=0.00109000],[Price=103055.02000000,Quantity=0.01070000]]]

So even though our connection was disconnected, ScriptApiLib waited for a re-connection and then it sent the order request. Moreover, BTC/EUR subscription was lost during the first disconnection, but it was re-subscribed and an order book snapshot was returned as expected. One word. Resiliency.

BlockUntilReconnectedOrTimeout is a connection strategy that requires special considerations. PreRequestTimeout can be set to, say, 60 seconds, and it is possible that during that 60 seconds, you may get re-connected multiple times. There is no danger in subscribing market data multiple times during one minute. However, we are very cautious with respect to creating orders and so we attempt to send a request to place an order always just once when BlockUntilReconnectedOrTimeout is utilized to avoid placing an order multiple times which could lead easily to financial losses.

Conclusion

In this blog post, we demonstrated two connection strategies which can help tune trading experience based on Internet connectivity. Next time, we will discuss retrieving of historical trades.

Disclaimer: The information provided in this blog post should not be considered financial advice. Always conduct your own research and consider your personal circumstances before acting on any financial information.