Skip to content

Commit b2e926c

Browse files
konardclaude
andcommitted
Implement maximum loss protection with market sell feature
- Add MaximumLossPercentage setting to TradingSettings - Implement loss calculation based on average purchase price vs current market price - Add market sell order functionality for immediate execution - Integrate maximum loss protection into main trading loop - When loss exceeds threshold, cancel all orders and sell at market price - Add configuration examples with 10% maximum loss threshold - Add comprehensive logging for maximum loss protection events Fixes #225 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 9e4c724 commit b2e926c

File tree

4 files changed

+105
-2
lines changed

4 files changed

+105
-2
lines changed

csharp/TraderBot/TradingService.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public TradingService(ILogger<TradingService> logger, InvestApiClient investApi,
6363
Logger.LogInformation($"EarlySellOwnedLotsDelta: {settings.EarlySellOwnedLotsDelta}");
6464
Logger.LogInformation($"EarlySellOwnedLotsMultiplier: {settings.EarlySellOwnedLotsMultiplier}");
6565
Logger.LogInformation($"LoadOperationsFrom: {settings.LoadOperationsFrom}");
66+
Logger.LogInformation($"MaximumLossPercentage: {settings.MaximumLossPercentage}");
6667

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

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

445+
// Check for maximum loss protection
446+
if (LotsSets.Count > 0 && ShouldTriggerMaximumLossProtection(bestBid))
447+
{
448+
Logger.LogCritical($"MAXIMUM LOSS PROTECTION TRIGGERED! Current market price: {bestBid}");
449+
450+
// Cancel all existing orders first
451+
var allOrders = new List<string>();
452+
allOrders.AddRange(ActiveBuyOrders.Keys);
453+
allOrders.AddRange(ActiveSellOrders.Keys);
454+
455+
foreach (var orderId in allOrders)
456+
{
457+
await TryCancelOrder(orderId);
458+
}
459+
460+
// Clear active orders
461+
ActiveBuyOrders.Clear();
462+
ActiveSellOrders.Clear();
463+
ActiveSellOrderSourcePrice.Clear();
464+
465+
// Sell all lots at market price
466+
var totalLots = LotsSets.Values.Sum();
467+
if (totalLots > 0)
468+
{
469+
await PlaceMarketSellOrder(totalLots);
470+
471+
// Clear lots as they will be sold
472+
LotsSets.Clear();
473+
}
474+
475+
// Reset cash balance
476+
SetCashBalance(CashBalanceFree + CashBalanceLocked, 0);
477+
478+
Logger.LogCritical($"MAXIMUM LOSS PROTECTION: Sold {totalLots} lots at market price to minimize further losses");
479+
480+
continue;
481+
}
482+
444483
// Logger.LogInformation($"Time: {DateTime.Now}");
445484
// Logger.LogInformation($"ActiveBuyOrders.Count: {ActiveBuyOrders.Count}");
446485
// Logger.LogInformation($"ActiveSellOrders.Count: {ActiveSellOrders.Count}");
@@ -691,6 +730,51 @@ private decimal GetTargetSellPrice(decimal minimumSellPrice, decimal bestAsk)
691730
return targetSellPrice;
692731
}
693732

733+
private decimal CalculateCurrentLossPercentage(decimal currentMarketPrice)
734+
{
735+
if (LotsSets.Count == 0)
736+
{
737+
return 0;
738+
}
739+
740+
decimal totalCost = 0;
741+
decimal totalLots = 0;
742+
743+
foreach (var lotsSet in LotsSets)
744+
{
745+
decimal purchasePrice = lotsSet.Key;
746+
long lots = lotsSet.Value;
747+
totalCost += purchasePrice * lots;
748+
totalLots += lots;
749+
}
750+
751+
if (totalLots == 0)
752+
{
753+
return 0;
754+
}
755+
756+
decimal averagePurchasePrice = totalCost / totalLots;
757+
decimal currentValue = currentMarketPrice * totalLots;
758+
decimal totalPurchaseCost = averagePurchasePrice * totalLots;
759+
760+
decimal lossPercentage = ((totalPurchaseCost - currentValue) / totalPurchaseCost) * 100;
761+
762+
Logger.LogInformation($"Average purchase price: {averagePurchasePrice}, Current price: {currentMarketPrice}, Loss: {lossPercentage:F2}%");
763+
764+
return lossPercentage;
765+
}
766+
767+
private bool ShouldTriggerMaximumLossProtection(decimal currentMarketPrice)
768+
{
769+
if (!Settings.MaximumLossPercentage.HasValue || LotsSets.Count == 0)
770+
{
771+
return false;
772+
}
773+
774+
decimal currentLoss = CalculateCurrentLossPercentage(currentMarketPrice);
775+
return currentLoss >= Settings.MaximumLossPercentage.Value;
776+
}
777+
694778
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
695779
{
696780
var tasks = new []
@@ -831,6 +915,22 @@ private async Task<PostOrderResponse> PlaceSellOrder(long amount, decimal price)
831915
return response;
832916
}
833917

918+
private async Task<PostOrderResponse> PlaceMarketSellOrder(long amount)
919+
{
920+
PostOrderRequest marketSellOrderRequest = new()
921+
{
922+
OrderId = Guid.NewGuid().ToString(),
923+
AccountId = CurrentAccount.Id,
924+
Direction = OrderDirection.Sell,
925+
OrderType = OrderType.Market,
926+
Figi = Figi,
927+
Quantity = amount
928+
};
929+
var response = await InvestApi.Orders.PostOrderAsync(marketSellOrderRequest).ResponseAsync;
930+
Logger.LogCritical($"MAXIMUM LOSS PROTECTION: Market sell order placed for {amount} lots: {response}");
931+
return response;
932+
}
933+
834934
private async Task<PostOrderResponse> PlaceBuyOrder(long amount, decimal price)
835935
{
836936
PostOrderRequest buyOrderRequest = new()

csharp/TraderBot/TradingSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ public class TradingSettings
1717
public long EarlySellOwnedLotsDelta { get; set; }
1818
public decimal EarlySellOwnedLotsMultiplier { get; set; }
1919
public DateTime LoadOperationsFrom { get; set; }
20+
public decimal? MaximumLossPercentage { get; set; }
2021
}

csharp/TraderBot/appsettings.TMON.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"MaximumTimeToBuy": "23:59:59",
2525
"EarlySellOwnedLotsDelta": 300000,
2626
"EarlySellOwnedLotsMultiplier": 0,
27-
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
27+
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z",
28+
"MaximumLossPercentage": 10.0
2829
}
2930
}

csharp/TraderBot/appsettings.TRUR.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"MaximumTimeToBuy": "14:45:00",
2525
"EarlySellOwnedLotsDelta": 300000,
2626
"EarlySellOwnedLotsMultiplier": 0,
27-
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
27+
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z",
28+
"MaximumLossPercentage": 10.0
2829
}
2930
}

0 commit comments

Comments
 (0)