diff --git a/csharp/TraderBot/TradingService.cs b/csharp/TraderBot/TradingService.cs index 0302809b..b8097c00 100644 --- a/csharp/TraderBot/TradingService.cs +++ b/csharp/TraderBot/TradingService.cs @@ -63,6 +63,9 @@ public TradingService(ILogger logger, InvestApiClient investApi, Logger.LogInformation($"EarlySellOwnedLotsDelta: {settings.EarlySellOwnedLotsDelta}"); Logger.LogInformation($"EarlySellOwnedLotsMultiplier: {settings.EarlySellOwnedLotsMultiplier}"); Logger.LogInformation($"LoadOperationsFrom: {settings.LoadOperationsFrom}"); + Logger.LogInformation($"EnableCustomBuyPrice: {settings.EnableCustomBuyPrice}"); + Logger.LogInformation($"CustomBuyPriceSpreadPercentage: {settings.CustomBuyPriceSpreadPercentage}"); + Logger.LogInformation($"MaxCustomBuyPriceSteps: {settings.MaxCustomBuyPriceSteps}"); var currentTime = DateTime.UtcNow.TimeOfDay; Logger.LogInformation($"Current time: {currentTime}"); @@ -472,15 +475,16 @@ await marketDataStream.RequestStream.WriteAsync(new MarketDataRequest { // Process potential buy order var (cashBalance, _) = await GetCashBalance(); - var lotPrice = bestBid * LotSize; + var customBuyPrice = GetCustomBuyPrice(bestBid, bestAsk); + var lotPrice = customBuyPrice * LotSize; if (cashBalance > lotPrice) { Logger.LogInformation($"buy activated"); - Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}."); + Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}, customBuyPrice: {customBuyPrice}."); var lots = (long)(cashBalance / lotPrice); - var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == bestBid)?.Quantity ?? 0; + var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == customBuyPrice)?.Quantity ?? 0; Logger.LogInformation($"marketLotsAtTargetPrice: {marketLotsAtTargetPrice}"); - var response = await PlaceBuyOrder(lots, bestBid); + var response = await PlaceBuyOrder(lots, customBuyPrice); Logger.LogInformation($"buy complete"); areOrdersPlaced = true; } @@ -519,16 +523,17 @@ await marketDataStream.RequestStream.WriteAsync(new MarketDataRequest if (IsTimeToBuy()) { var initialOrderPrice = MoneyValueToDecimal(activeBuyOrder.InitialSecurityPrice); + var customBuyPrice = GetCustomBuyPrice(bestBid, bestAsk); if (LotsSets.TryGetValue(initialOrderPrice, out var boughtLots) || LotsSets.Count == 0) { - if (initialOrderPrice != bestBid && bestBidOrder.Quantity > Settings.MinimumMarketOrderSizeToChangeBuyPrice) + if (initialOrderPrice != customBuyPrice && bestBidOrder.Quantity > Settings.MinimumMarketOrderSizeToChangeBuyPrice) { if (boughtLots > 0) { Logger.LogInformation($"buy trades are in progress"); continue; } - Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}."); + Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}, customBuyPrice: {customBuyPrice}."); Logger.LogInformation($"initial buy order price: {initialOrderPrice}"); Logger.LogInformation($"buy order price change activated"); // Cancel order @@ -541,13 +546,13 @@ await marketDataStream.RequestStream.WriteAsync(new MarketDataRequest SetCashBalance(CashBalanceFree + CashBalanceLocked, 0); // Place new order var (cashBalance, _) = await GetCashBalance(); - var lotPrice = bestBid * LotSize; + var lotPrice = customBuyPrice * LotSize; if (cashBalance > lotPrice) { var lots = (long)(cashBalance / lotPrice); - var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == bestBid)?.Quantity ?? 0; + var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == customBuyPrice)?.Quantity ?? 0; Logger.LogInformation($"marketLotsAtTargetPrice: {marketLotsAtTargetPrice}"); - var response = await PlaceBuyOrder(lots, bestBid); + var response = await PlaceBuyOrder(lots, customBuyPrice); } SyncActiveOrders(); Logger.LogInformation($"buy order price change is complete"); @@ -691,6 +696,28 @@ private decimal GetTargetSellPrice(decimal minimumSellPrice, decimal bestAsk) return targetSellPrice; } + private decimal GetCustomBuyPrice(decimal bestBid, decimal bestAsk) + { + if (!Settings.EnableCustomBuyPrice) + { + return bestBid; + } + + var spread = bestAsk - bestBid; + var customBuyPrice = bestBid + (spread * Settings.CustomBuyPriceSpreadPercentage / 100m); + + var maxPriceIncrease = Settings.MaxCustomBuyPriceSteps * PriceStep; + var maxAllowedPrice = bestBid + maxPriceIncrease; + + customBuyPrice = Math.Min(customBuyPrice, maxAllowedPrice); + customBuyPrice = Math.Min(customBuyPrice, bestAsk); + + customBuyPrice = Math.Max(customBuyPrice, bestBid); + + Logger.LogInformation($"CustomBuyPrice calculation: bestBid={bestBid}, bestAsk={bestAsk}, spread={spread}, customBuyPrice={customBuyPrice}"); + return customBuyPrice; + } + protected override async Task ExecuteAsync(CancellationToken cancellationToken) { var tasks = new [] diff --git a/csharp/TraderBot/TradingSettings.cs b/csharp/TraderBot/TradingSettings.cs index 884a25df..d17fdc3b 100644 --- a/csharp/TraderBot/TradingSettings.cs +++ b/csharp/TraderBot/TradingSettings.cs @@ -17,4 +17,7 @@ public class TradingSettings public long EarlySellOwnedLotsDelta { get; set; } public decimal EarlySellOwnedLotsMultiplier { get; set; } public DateTime LoadOperationsFrom { get; set; } + public bool EnableCustomBuyPrice { get; set; } + public decimal CustomBuyPriceSpreadPercentage { get; set; } + public long MaxCustomBuyPriceSteps { get; set; } } \ No newline at end of file diff --git a/csharp/TraderBot/appsettings.TMON.json b/csharp/TraderBot/appsettings.TMON.json index c7b66d7a..e5d8e443 100644 --- a/csharp/TraderBot/appsettings.TMON.json +++ b/csharp/TraderBot/appsettings.TMON.json @@ -24,6 +24,9 @@ "MaximumTimeToBuy": "23:59:59", "EarlySellOwnedLotsDelta": 300000, "EarlySellOwnedLotsMultiplier": 0, - "LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z" + "LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z", + "EnableCustomBuyPrice": false, + "CustomBuyPriceSpreadPercentage": 50.0, + "MaxCustomBuyPriceSteps": 10 } } diff --git a/csharp/TraderBot/appsettings.TRUR.json b/csharp/TraderBot/appsettings.TRUR.json index 1dc848e6..74e4cfc4 100644 --- a/csharp/TraderBot/appsettings.TRUR.json +++ b/csharp/TraderBot/appsettings.TRUR.json @@ -24,6 +24,9 @@ "MaximumTimeToBuy": "14:45:00", "EarlySellOwnedLotsDelta": 300000, "EarlySellOwnedLotsMultiplier": 0, - "LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z" + "LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z", + "EnableCustomBuyPrice": false, + "CustomBuyPriceSpreadPercentage": 50.0, + "MaxCustomBuyPriceSteps": 10 } } diff --git a/examples/custom_buy_price_test.md b/examples/custom_buy_price_test.md new file mode 100644 index 00000000..36ee875a --- /dev/null +++ b/examples/custom_buy_price_test.md @@ -0,0 +1,47 @@ +# Custom Buy Price Test Cases + +## Test Scenario 1: Feature Disabled +- `EnableCustomBuyPrice`: false +- `bestBid`: 5.300 +- `bestAsk`: 5.320 +- **Expected Result**: 5.300 (should return bestBid) + +## Test Scenario 2: Feature Enabled - 50% Spread +- `EnableCustomBuyPrice`: true +- `CustomBuyPriceSpreadPercentage`: 50.0 +- `MaxCustomBuyPriceSteps`: 10 +- `PriceStep`: 0.001 +- `bestBid`: 5.300 +- `bestAsk`: 5.320 +- **Spread**: 0.020 +- **50% of spread**: 0.010 +- **Expected Result**: 5.310 (bestBid + 50% of spread) + +## Test Scenario 3: Feature Enabled - Limited by MaxSteps +- `EnableCustomBuyPrice`: true +- `CustomBuyPriceSpreadPercentage`: 50.0 +- `MaxCustomBuyPriceSteps`: 5 +- `PriceStep`: 0.001 +- `bestBid`: 5.300 +- `bestAsk`: 5.350 +- **Spread**: 0.050 +- **50% of spread**: 0.025 +- **Max allowed increase**: 5 * 0.001 = 0.005 +- **Expected Result**: 5.305 (bestBid + maxSteps, capped) + +## Test Scenario 4: Feature Enabled - Limited by bestAsk +- `EnableCustomBuyPrice`: true +- `CustomBuyPriceSpreadPercentage`: 100.0 +- `MaxCustomBuyPriceSteps`: 100 +- `PriceStep`: 0.001 +- `bestBid`: 5.300 +- `bestAsk`: 5.310 +- **Spread**: 0.010 +- **100% of spread**: 0.010 +- **Expected Result**: 5.310 (limited by bestAsk) + +This feature allows traders to: +1. Avoid long queues at the best bid price +2. Get faster execution by paying a premium (crossing the spread partially) +3. Control the maximum premium they're willing to pay +4. Maintain the existing behavior when disabled \ No newline at end of file