Enhance SpotBot closing price calculation and logging

- Implemented logic to calculate broker closing price from PNL when the price is zero or invalid, improving accuracy in trade reconciliation.
- Added detailed logging for calculated closing prices, including entry price, PNL, and leverage, to enhance visibility into trading performance.
- Updated handling of take profit and stop loss updates based on valid closing prices, ensuring more reliable position management.
This commit is contained in:
2026-01-06 16:36:02 +07:00
parent 58eee1a878
commit b928eac031
2 changed files with 126 additions and 39 deletions

View File

@@ -1011,52 +1011,106 @@ public class SpotBot : TradingBotBase
if (brokerPosition.Open != null)
{
var brokerClosingPrice = brokerPosition.Open.Price;
var isProfitable = position.OriginDirection == TradeDirection.Long
? position.Open.Price < brokerClosingPrice
: position.Open.Price > brokerClosingPrice;
if (isProfitable)
// If brokerClosingPrice is 0 or invalid, calculate it from PNL
// This handles cases where very small prices (like PEPE) lose precision
if (brokerClosingPrice <= 0 && brokerPosition.ProfitAndLoss != null &&
brokerPosition.ProfitAndLoss.Realized != 0 && position.Open != null)
{
if (position.TakeProfit1 != null)
// Calculate closing price from PNL formula
// For LONG: PNL = (exitPrice - entryPrice) * quantity * leverage
// exitPrice = (PNL / (quantity * leverage)) + entryPrice
// For SHORT: PNL = (entryPrice - exitPrice) * quantity * leverage
// exitPrice = entryPrice - (PNL / (quantity * leverage))
var realizedPnl = brokerPosition.ProfitAndLoss.Realized;
var entryPrice = position.Open.Price;
var quantity = position.Open.Quantity;
var leverage = position.Open.Leverage;
if (quantity > 0 && leverage > 0)
{
position.TakeProfit1.Price = brokerClosingPrice;
position.TakeProfit1.SetDate(brokerPosition.Open.Date);
position.TakeProfit1.SetStatus(TradeStatus.Filled);
var pnlPerUnit = realizedPnl / (quantity * leverage);
if (position.OriginDirection == TradeDirection.Long)
{
brokerClosingPrice = entryPrice + pnlPerUnit;
}
else // Short
{
brokerClosingPrice = entryPrice - pnlPerUnit;
}
await LogDebugAsync(
$"⚠️ Broker closing price was 0, calculated from PNL\n" +
$"Position: `{position.Identifier}`\n" +
$"Entry Price: `${entryPrice:F8}`\n" +
$"PNL: `${realizedPnl:F8}`\n" +
$"Quantity: `{quantity:F8}`\n" +
$"Leverage: `{leverage}`\n" +
$"Calculated Closing Price: `${brokerClosingPrice:F8}`");
}
}
// Only update TP/SL prices if we have a valid closing price
if (brokerClosingPrice > 0)
{
var isProfitable = position.OriginDirection == TradeDirection.Long
? position.Open.Price < brokerClosingPrice
: position.Open.Price > brokerClosingPrice;
if (isProfitable)
{
if (position.TakeProfit1 != null)
{
position.TakeProfit1.Price = brokerClosingPrice;
position.TakeProfit1.SetDate(brokerPosition.Open.Date);
position.TakeProfit1.SetStatus(TradeStatus.Filled);
}
// Cancel SL trade when TP is hit
if (position.StopLoss != null)
{
position.StopLoss.SetStatus(TradeStatus.Cancelled);
}
}
else
{
if (position.StopLoss != null)
{
position.StopLoss.Price = brokerClosingPrice;
position.StopLoss.SetDate(brokerPosition.Open.Date);
position.StopLoss.SetStatus(TradeStatus.Filled);
}
// Cancel TP trades when SL is hit
if (position.TakeProfit1 != null)
{
position.TakeProfit1.SetStatus(TradeStatus.Cancelled);
}
if (position.TakeProfit2 != null)
{
position.TakeProfit2.SetStatus(TradeStatus.Cancelled);
}
}
// Cancel SL trade when TP is hit
if (position.StopLoss != null)
{
position.StopLoss.SetStatus(TradeStatus.Cancelled);
}
await LogDebugAsync(
$"📊 Spot Position Reconciliation Complete\n" +
$"Position: `{position.Identifier}`\n" +
$"Closing Price: `${brokerClosingPrice:F8}`\n" +
$"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" +
$"PnL from broker: `${position.ProfitAndLoss.Realized:F8}`");
}
else
{
if (position.StopLoss != null)
{
position.StopLoss.Price = brokerClosingPrice;
position.StopLoss.SetDate(brokerPosition.Open.Date);
position.StopLoss.SetStatus(TradeStatus.Filled);
}
// Cancel TP trades when SL is hit
if (position.TakeProfit1 != null)
{
position.TakeProfit1.SetStatus(TradeStatus.Cancelled);
}
if (position.TakeProfit2 != null)
{
position.TakeProfit2.SetStatus(TradeStatus.Cancelled);
}
await LogWarningAsync(
$"⚠️ Cannot update closing trade price - broker price is invalid\n" +
$"Position: `{position.Identifier}`\n" +
$"Broker PNL: `{brokerPosition.ProfitAndLoss?.Realized:F8}`\n" +
$"Entry Price: `${position.Open?.Price:F8}`\n" +
$"Broker Closing Price: `{brokerPosition.Open.Price}`\n" +
$"Will skip updating TP/SL prices to avoid zero-price fills");
}
await LogDebugAsync(
$"📊 Spot Position Reconciliation Complete\n" +
$"Position: `{position.Identifier}`\n" +
$"Closing Price: `${brokerClosingPrice:F2}`\n" +
$"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" +
$"PnL from broker: `${position.ProfitAndLoss.Realized:F2}`");
}
return true; // Successfully reconciled, skip candle-based calculation