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,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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user