Unlike MQL4/5, modifying a collection while iterating over it in C# throws an InvalidOperationException. To prevent this, the logic takes a snapshot of the Positions and PendingOrders collections using .ToArray() before initiating the closures.
cTrader Auto-Flatten cBot
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class FridayAutoFlatten : Robot
{
[Parameter("Flatten Hour (Server Time)", DefaultValue = 22, MinValue = 0, MaxValue = 23)]
public int FlattenHour { get; set; }
[Parameter("Flatten Minute", DefaultValue = 50, MinValue = 0, MaxValue = 59)]
public int FlattenMinute { get; set; }
[Parameter("Cancel Pending Orders?", DefaultValue = true)]
public bool CancelPending { get; set; }
protected override void OnStart()
{
// 1-second timer guarantees execution independently of incoming market ticks
Timer.Start(TimeSpan.FromSeconds(1));
}
protected override void OnTimer()
{
var now = Server.Time;
// Trigger only on Friday at or after the target time
if (now.DayOfWeek == DayOfWeek.Friday)
{
if (now.Hour > FlattenHour || (now.Hour == FlattenHour && now.Minute >= FlattenMinute))
{
if (Positions.Count > 0 || (CancelPending && PendingOrders.Count > 0))
{
FlattenAll();
}
}
}
}
private void FlattenAll()
{
// Snapshot the collections to safely iterate while items are being removed
var positionsToClose = Positions.ToArray();
foreach (var position in positionsToClose)
{
// Asynchronous execution prevents thread blocking during bulk closures
ClosePositionAsync(position, OnTradeResult);
}
if (CancelPending)
{
var ordersToCancel = PendingOrders.ToArray();
foreach (var order in ordersToCancel)
{
CancelPendingOrderAsync(order, OnTradeResult);
}
}
}
private void OnTradeResult(TradeResult result)
{
if (!result.IsSuccessful)
{
Print("Flatten execution failed for {0}. Error: {1}",
result.Position?.Id ?? result.PendingOrder?.Id,
result.Error);
}
}
}
}