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:
@@ -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
|
||||
|
||||
@@ -1922,7 +1922,40 @@ export const getSpotPositionHistoryImpl = async (
|
||||
if (quantity > 0) {
|
||||
// Quote per base: input value / base qty (Long: USDC/BTC; Short: USDC/BTC)
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user