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
45 changes: 36 additions & 9 deletions csharp/TraderBot/TradingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public TradingService(ILogger<TradingService> 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}");
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand All @@ -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");
Expand Down Expand Up @@ -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 []
Expand Down
3 changes: 3 additions & 0 deletions csharp/TraderBot/TradingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
5 changes: 4 additions & 1 deletion csharp/TraderBot/appsettings.TMON.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
5 changes: 4 additions & 1 deletion csharp/TraderBot/appsettings.TRUR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
47 changes: 47 additions & 0 deletions examples/custom_buy_price_test.md
Original file line number Diff line number Diff line change
@@ -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
Loading