Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions csharp/TraderBot/TradingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public TradingService(ILogger<TradingService> logger, InvestApiClient investApi,
Logger.LogInformation($"EarlySellOwnedLotsDelta: {settings.EarlySellOwnedLotsDelta}");
Logger.LogInformation($"EarlySellOwnedLotsMultiplier: {settings.EarlySellOwnedLotsMultiplier}");
Logger.LogInformation($"LoadOperationsFrom: {settings.LoadOperationsFrom}");
Logger.LogInformation($"MaximumLossPercentage: {settings.MaximumLossPercentage}");

var currentTime = DateTime.UtcNow.TimeOfDay;
Logger.LogInformation($"Current time: {currentTime}");
Expand Down Expand Up @@ -441,6 +442,44 @@ await marketDataStream.RequestStream.WriteAsync(new MarketDataRequest

// Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}.");

// Check for maximum loss protection
if (LotsSets.Count > 0 && ShouldTriggerMaximumLossProtection(bestBid))
{
Logger.LogCritical($"MAXIMUM LOSS PROTECTION TRIGGERED! Current market price: {bestBid}");

// Cancel all existing orders first
var allOrders = new List<string>();
allOrders.AddRange(ActiveBuyOrders.Keys);
allOrders.AddRange(ActiveSellOrders.Keys);

foreach (var orderId in allOrders)
{
await TryCancelOrder(orderId);
}

// Clear active orders
ActiveBuyOrders.Clear();
ActiveSellOrders.Clear();
ActiveSellOrderSourcePrice.Clear();

// Sell all lots at market price
var totalLots = LotsSets.Values.Sum();
if (totalLots > 0)
{
await PlaceMarketSellOrder(totalLots);

// Clear lots as they will be sold
LotsSets.Clear();
}

// Reset cash balance
SetCashBalance(CashBalanceFree + CashBalanceLocked, 0);

Logger.LogCritical($"MAXIMUM LOSS PROTECTION: Sold {totalLots} lots at market price to minimize further losses");

continue;
}

// Logger.LogInformation($"Time: {DateTime.Now}");
// Logger.LogInformation($"ActiveBuyOrders.Count: {ActiveBuyOrders.Count}");
// Logger.LogInformation($"ActiveSellOrders.Count: {ActiveSellOrders.Count}");
Expand Down Expand Up @@ -691,6 +730,51 @@ private decimal GetTargetSellPrice(decimal minimumSellPrice, decimal bestAsk)
return targetSellPrice;
}

private decimal CalculateCurrentLossPercentage(decimal currentMarketPrice)
{
if (LotsSets.Count == 0)
{
return 0;
}

decimal totalCost = 0;
decimal totalLots = 0;

foreach (var lotsSet in LotsSets)
{
decimal purchasePrice = lotsSet.Key;
long lots = lotsSet.Value;
totalCost += purchasePrice * lots;
totalLots += lots;
}

if (totalLots == 0)
{
return 0;
}

decimal averagePurchasePrice = totalCost / totalLots;
decimal currentValue = currentMarketPrice * totalLots;
decimal totalPurchaseCost = averagePurchasePrice * totalLots;

decimal lossPercentage = ((totalPurchaseCost - currentValue) / totalPurchaseCost) * 100;

Logger.LogInformation($"Average purchase price: {averagePurchasePrice}, Current price: {currentMarketPrice}, Loss: {lossPercentage:F2}%");

return lossPercentage;
}

private bool ShouldTriggerMaximumLossProtection(decimal currentMarketPrice)
{
if (!Settings.MaximumLossPercentage.HasValue || LotsSets.Count == 0)
{
return false;
}

decimal currentLoss = CalculateCurrentLossPercentage(currentMarketPrice);
return currentLoss >= Settings.MaximumLossPercentage.Value;
}

protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var tasks = new []
Expand Down Expand Up @@ -831,6 +915,22 @@ private async Task<PostOrderResponse> PlaceSellOrder(long amount, decimal price)
return response;
}

private async Task<PostOrderResponse> PlaceMarketSellOrder(long amount)
{
PostOrderRequest marketSellOrderRequest = new()
{
OrderId = Guid.NewGuid().ToString(),
AccountId = CurrentAccount.Id,
Direction = OrderDirection.Sell,
OrderType = OrderType.Market,
Figi = Figi,
Quantity = amount
};
var response = await InvestApi.Orders.PostOrderAsync(marketSellOrderRequest).ResponseAsync;
Logger.LogCritical($"MAXIMUM LOSS PROTECTION: Market sell order placed for {amount} lots: {response}");
return response;
}

private async Task<PostOrderResponse> PlaceBuyOrder(long amount, decimal price)
{
PostOrderRequest buyOrderRequest = new()
Expand Down
1 change: 1 addition & 0 deletions csharp/TraderBot/TradingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ public class TradingSettings
public long EarlySellOwnedLotsDelta { get; set; }
public decimal EarlySellOwnedLotsMultiplier { get; set; }
public DateTime LoadOperationsFrom { get; set; }
public decimal? MaximumLossPercentage { get; set; }
}
3 changes: 2 additions & 1 deletion csharp/TraderBot/appsettings.TMON.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"MaximumTimeToBuy": "23:59:59",
"EarlySellOwnedLotsDelta": 300000,
"EarlySellOwnedLotsMultiplier": 0,
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z",
"MaximumLossPercentage": 10.0
}
}
3 changes: 2 additions & 1 deletion csharp/TraderBot/appsettings.TRUR.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"MaximumTimeToBuy": "14:45:00",
"EarlySellOwnedLotsDelta": 300000,
"EarlySellOwnedLotsMultiplier": 0,
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z",
"MaximumLossPercentage": 10.0
}
}
Loading