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 ofNotConnectedException
, you may getOperationFailedException
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 thatNotConnectedException
is intentionally thrown even when one queries the local storage of open orders by callingGetOpenOrdersAsync
. 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 thePreRequestTimeout
elapses without establishing a new exchange connection to the exchange. If a new connection is established beforePreRequestTimeout
elapses, the API request will be sent at that time.
Tip: If you can tolerate arbitrary long disconnections, you can useBlockUntilReconnectedOrTimeout.InfinityTimeoutInstance
and thus avoidNotConnectedException
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 whenBlockUntilReconnectedOrTimeout
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.