Skip to content

Commit f27cbc5

Browse files
konardclaude
andcommitted
Add custom buy price functionality to avoid queue waiting
- Add EnableCustomBuyPrice configuration to enable/disable feature - Add CustomBuyPriceSpreadPercentage to control how much of spread to cross - Add MaxCustomBuyPriceSteps to limit maximum price increase from best bid - Update buy order logic to use custom price calculation - Maintain backward compatibility when feature is disabled - Add comprehensive logging for debugging price calculations This allows traders to buy at their own price (e.g., 5.320 instead of 5.300) to avoid waiting in long queues at the best bid price. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 2a90b44 commit f27cbc5

File tree

5 files changed

+94
-11
lines changed

5 files changed

+94
-11
lines changed

csharp/TraderBot/TradingService.cs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ 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($"EnableCustomBuyPrice: {settings.EnableCustomBuyPrice}");
67+
Logger.LogInformation($"CustomBuyPriceSpreadPercentage: {settings.CustomBuyPriceSpreadPercentage}");
68+
Logger.LogInformation($"MaxCustomBuyPriceSteps: {settings.MaxCustomBuyPriceSteps}");
6669

6770
var currentTime = DateTime.UtcNow.TimeOfDay;
6871
Logger.LogInformation($"Current time: {currentTime}");
@@ -472,15 +475,16 @@ await marketDataStream.RequestStream.WriteAsync(new MarketDataRequest
472475
{
473476
// Process potential buy order
474477
var (cashBalance, _) = await GetCashBalance();
475-
var lotPrice = bestBid * LotSize;
478+
var customBuyPrice = GetCustomBuyPrice(bestBid, bestAsk);
479+
var lotPrice = customBuyPrice * LotSize;
476480
if (cashBalance > lotPrice)
477481
{
478482
Logger.LogInformation($"buy activated");
479-
Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}.");
483+
Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}, customBuyPrice: {customBuyPrice}.");
480484
var lots = (long)(cashBalance / lotPrice);
481-
var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == bestBid)?.Quantity ?? 0;
485+
var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == customBuyPrice)?.Quantity ?? 0;
482486
Logger.LogInformation($"marketLotsAtTargetPrice: {marketLotsAtTargetPrice}");
483-
var response = await PlaceBuyOrder(lots, bestBid);
487+
var response = await PlaceBuyOrder(lots, customBuyPrice);
484488
Logger.LogInformation($"buy complete");
485489
areOrdersPlaced = true;
486490
}
@@ -519,16 +523,17 @@ await marketDataStream.RequestStream.WriteAsync(new MarketDataRequest
519523
if (IsTimeToBuy())
520524
{
521525
var initialOrderPrice = MoneyValueToDecimal(activeBuyOrder.InitialSecurityPrice);
526+
var customBuyPrice = GetCustomBuyPrice(bestBid, bestAsk);
522527
if (LotsSets.TryGetValue(initialOrderPrice, out var boughtLots) || LotsSets.Count == 0)
523528
{
524-
if (initialOrderPrice != bestBid && bestBidOrder.Quantity > Settings.MinimumMarketOrderSizeToChangeBuyPrice)
529+
if (initialOrderPrice != customBuyPrice && bestBidOrder.Quantity > Settings.MinimumMarketOrderSizeToChangeBuyPrice)
525530
{
526531
if (boughtLots > 0)
527532
{
528533
Logger.LogInformation($"buy trades are in progress");
529534
continue;
530535
}
531-
Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}.");
536+
Logger.LogInformation($"bid: {bestBid}, ask: {bestAsk}, customBuyPrice: {customBuyPrice}.");
532537
Logger.LogInformation($"initial buy order price: {initialOrderPrice}");
533538
Logger.LogInformation($"buy order price change activated");
534539
// Cancel order
@@ -541,13 +546,13 @@ await marketDataStream.RequestStream.WriteAsync(new MarketDataRequest
541546
SetCashBalance(CashBalanceFree + CashBalanceLocked, 0);
542547
// Place new order
543548
var (cashBalance, _) = await GetCashBalance();
544-
var lotPrice = bestBid * LotSize;
549+
var lotPrice = customBuyPrice * LotSize;
545550
if (cashBalance > lotPrice)
546551
{
547552
var lots = (long)(cashBalance / lotPrice);
548-
var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == bestBid)?.Quantity ?? 0;
553+
var marketLotsAtTargetPrice = orderBook.Bids.FirstOrDefault(o => o.Price == customBuyPrice)?.Quantity ?? 0;
549554
Logger.LogInformation($"marketLotsAtTargetPrice: {marketLotsAtTargetPrice}");
550-
var response = await PlaceBuyOrder(lots, bestBid);
555+
var response = await PlaceBuyOrder(lots, customBuyPrice);
551556
}
552557
SyncActiveOrders();
553558
Logger.LogInformation($"buy order price change is complete");
@@ -691,6 +696,28 @@ private decimal GetTargetSellPrice(decimal minimumSellPrice, decimal bestAsk)
691696
return targetSellPrice;
692697
}
693698

699+
private decimal GetCustomBuyPrice(decimal bestBid, decimal bestAsk)
700+
{
701+
if (!Settings.EnableCustomBuyPrice)
702+
{
703+
return bestBid;
704+
}
705+
706+
var spread = bestAsk - bestBid;
707+
var customBuyPrice = bestBid + (spread * Settings.CustomBuyPriceSpreadPercentage / 100m);
708+
709+
var maxPriceIncrease = Settings.MaxCustomBuyPriceSteps * PriceStep;
710+
var maxAllowedPrice = bestBid + maxPriceIncrease;
711+
712+
customBuyPrice = Math.Min(customBuyPrice, maxAllowedPrice);
713+
customBuyPrice = Math.Min(customBuyPrice, bestAsk);
714+
715+
customBuyPrice = Math.Max(customBuyPrice, bestBid);
716+
717+
Logger.LogInformation($"CustomBuyPrice calculation: bestBid={bestBid}, bestAsk={bestAsk}, spread={spread}, customBuyPrice={customBuyPrice}");
718+
return customBuyPrice;
719+
}
720+
694721
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
695722
{
696723
var tasks = new []

csharp/TraderBot/TradingSettings.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ public class TradingSettings
1717
public long EarlySellOwnedLotsDelta { get; set; }
1818
public decimal EarlySellOwnedLotsMultiplier { get; set; }
1919
public DateTime LoadOperationsFrom { get; set; }
20+
public bool EnableCustomBuyPrice { get; set; }
21+
public decimal CustomBuyPriceSpreadPercentage { get; set; }
22+
public long MaxCustomBuyPriceSteps { get; set; }
2023
}

csharp/TraderBot/appsettings.TMON.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
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+
"EnableCustomBuyPrice": false,
29+
"CustomBuyPriceSpreadPercentage": 50.0,
30+
"MaxCustomBuyPriceSteps": 10
2831
}
2932
}

csharp/TraderBot/appsettings.TRUR.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
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+
"EnableCustomBuyPrice": false,
29+
"CustomBuyPriceSpreadPercentage": 50.0,
30+
"MaxCustomBuyPriceSteps": 10
2831
}
2932
}

examples/custom_buy_price_test.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Custom Buy Price Test Cases
2+
3+
## Test Scenario 1: Feature Disabled
4+
- `EnableCustomBuyPrice`: false
5+
- `bestBid`: 5.300
6+
- `bestAsk`: 5.320
7+
- **Expected Result**: 5.300 (should return bestBid)
8+
9+
## Test Scenario 2: Feature Enabled - 50% Spread
10+
- `EnableCustomBuyPrice`: true
11+
- `CustomBuyPriceSpreadPercentage`: 50.0
12+
- `MaxCustomBuyPriceSteps`: 10
13+
- `PriceStep`: 0.001
14+
- `bestBid`: 5.300
15+
- `bestAsk`: 5.320
16+
- **Spread**: 0.020
17+
- **50% of spread**: 0.010
18+
- **Expected Result**: 5.310 (bestBid + 50% of spread)
19+
20+
## Test Scenario 3: Feature Enabled - Limited by MaxSteps
21+
- `EnableCustomBuyPrice`: true
22+
- `CustomBuyPriceSpreadPercentage`: 50.0
23+
- `MaxCustomBuyPriceSteps`: 5
24+
- `PriceStep`: 0.001
25+
- `bestBid`: 5.300
26+
- `bestAsk`: 5.350
27+
- **Spread**: 0.050
28+
- **50% of spread**: 0.025
29+
- **Max allowed increase**: 5 * 0.001 = 0.005
30+
- **Expected Result**: 5.305 (bestBid + maxSteps, capped)
31+
32+
## Test Scenario 4: Feature Enabled - Limited by bestAsk
33+
- `EnableCustomBuyPrice`: true
34+
- `CustomBuyPriceSpreadPercentage`: 100.0
35+
- `MaxCustomBuyPriceSteps`: 100
36+
- `PriceStep`: 0.001
37+
- `bestBid`: 5.300
38+
- `bestAsk`: 5.310
39+
- **Spread**: 0.010
40+
- **100% of spread**: 0.010
41+
- **Expected Result**: 5.310 (limited by bestAsk)
42+
43+
This feature allows traders to:
44+
1. Avoid long queues at the best bid price
45+
2. Get faster execution by paying a premium (crossing the spread partially)
46+
3. Control the maximum premium they're willing to pay
47+
4. Maintain the existing behavior when disabled

0 commit comments

Comments
 (0)