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,6 +1011,49 @@ public class SpotBot : TradingBotBase
if (brokerPosition.Open != null) if (brokerPosition.Open != null)
{ {
var brokerClosingPrice = brokerPosition.Open.Price; var brokerClosingPrice = brokerPosition.Open.Price;
// 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)
{
// 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)
{
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 var isProfitable = position.OriginDirection == TradeDirection.Long
? position.Open.Price < brokerClosingPrice ? position.Open.Price < brokerClosingPrice
: position.Open.Price > brokerClosingPrice; : position.Open.Price > brokerClosingPrice;
@@ -1054,9 +1097,20 @@ public class SpotBot : TradingBotBase
await LogDebugAsync( await LogDebugAsync(
$"📊 Spot Position Reconciliation Complete\n" + $"📊 Spot Position Reconciliation Complete\n" +
$"Position: `{position.Identifier}`\n" + $"Position: `{position.Identifier}`\n" +
$"Closing Price: `${brokerClosingPrice:F2}`\n" + $"Closing Price: `${brokerClosingPrice:F8}`\n" +
$"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" + $"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" +
$"PnL from broker: `${position.ProfitAndLoss.Realized:F2}`"); $"PnL from broker: `${position.ProfitAndLoss.Realized:F8}`");
}
else
{
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");
}
} }
return true; // Successfully reconciled, skip candle-based calculation return true; // Successfully reconciled, skip candle-based calculation

View File

@@ -1922,7 +1922,40 @@ export const getSpotPositionHistoryImpl = async (
if (quantity > 0) { if (quantity > 0) {
// Quote per base: input value / base qty (Long: USDC/BTC; Short: USDC/BTC) // Quote per base: input value / base qty (Long: USDC/BTC; Short: USDC/BTC)
const numerator = direction === TradeDirection.Short ? outputQty : inputQty; const numerator = direction === TradeDirection.Short ? outputQty : inputQty;
price = numerator / quantity; const calculatedPrice = numerator / quantity;
// For very small prices (like PEPE), ensure we preserve precision
// If calculated price is suspiciously small or zero, try alternative calculation
if (calculatedPrice > 0 && calculatedPrice < 1e-10) {
// For very small prices, try to get price from token data (more accurate)
try {
const tokenAddress = direction === TradeDirection.Short
? initialToken?.address
: targetToken?.address;
if (tokenAddress && tokensData[tokenAddress]?.prices?.minPrice) {
// Use GMX's price data directly (more accurate for very small prices)
price = Number(tokensData[tokenAddress].prices.minPrice) / Math.pow(10, 30);
} else {
price = calculatedPrice; // Fallback to calculated
}
} catch {
price = calculatedPrice; // Fallback to calculated
}
} else {
price = calculatedPrice;
}
// Safety check: if price is still 0 or invalid, log warning
if (price <= 0 || !isFinite(price)) {
console.warn(
`⚠️ Invalid price calculated for spot position: ` +
`ticker=${tickerSymbol}, quantity=${quantity}, ` +
`numerator=${numerator}, calculated=${calculatedPrice}, ` +
`direction=${direction === TradeDirection.Short ? 'Short' : 'Long'}`
);
// Keep price as 0 - will be handled by SpotBot reconciliation
}
} }
const pnlUsd = (action as any).pnlUsd ? Number((action as any).pnlUsd) / 1e30 : 0; const pnlUsd = (action as any).pnlUsd ? Number((action as any).pnlUsd) / 1e30 : 0;