Add reconcilliation for cancelled position if needed
This commit is contained in:
@@ -423,11 +423,135 @@ public class SpotBot : TradingBotBase
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
protected override Task<bool> ReconcileWithBrokerHistory(Position position, Candle currentCandle)
|
||||
protected override async Task<bool> ReconcileWithBrokerHistory(Position position, Candle currentCandle)
|
||||
{
|
||||
// Spot trading doesn't have broker position history like futures
|
||||
// Return false to continue with candle-based calculation
|
||||
return Task.FromResult(false);
|
||||
// Spot-specific: reconcile with spot position history
|
||||
try
|
||||
{
|
||||
await LogDebugAsync(
|
||||
$"🔍 Fetching Spot Position History\nPosition: `{position.Identifier}`\nTicker: `{Config.Ticker}`");
|
||||
|
||||
var positionHistory = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Position>>(
|
||||
_scopeFactory,
|
||||
async exchangeService =>
|
||||
{
|
||||
// Get position history from the last 24 hours for better coverage
|
||||
var fromDate = DateTime.UtcNow.AddHours(-24);
|
||||
var toDate = DateTime.UtcNow;
|
||||
return await exchangeService.GetSpotPositionHistory(Account, Config.Ticker, fromDate, toDate);
|
||||
});
|
||||
|
||||
// Find the most recent position in history
|
||||
if (positionHistory != null && positionHistory.Any())
|
||||
{
|
||||
// Get the most recent position from spot history (ordered by date)
|
||||
var brokerPosition = positionHistory
|
||||
.OrderByDescending(p => p.Open?.Date ?? p.Date)
|
||||
.FirstOrDefault();
|
||||
|
||||
// For spot trading, SHORT direction means the spot was sold/closed
|
||||
// We need to verify the last position is SHORT to confirm the position was correctly closed
|
||||
if (brokerPosition != null && brokerPosition.OriginDirection == TradeDirection.Short)
|
||||
{
|
||||
if (brokerPosition.ProfitAndLoss != null)
|
||||
{
|
||||
await LogDebugAsync(
|
||||
$"✅ Spot Position History Found\n" +
|
||||
$"Position: `{position.Identifier}`\n" +
|
||||
$"Last Position Direction: `{brokerPosition.OriginDirection}` (SHORT = Sold/Closed) ✅\n" +
|
||||
$"Realized PnL (after fees): `${brokerPosition.ProfitAndLoss.Realized:F2}`\n" +
|
||||
$"Bot's UI Fees: `${position.UiFees:F2}`\n" +
|
||||
$"Bot's Gas Fees: `${position.GasFees:F2}`");
|
||||
|
||||
// Use the actual spot PnL data from broker history
|
||||
// For spot, fees are simpler (no leverage, no closing UI fees)
|
||||
var totalBotFees = position.GasFees + position.UiFees;
|
||||
var brokerRealizedPnl = brokerPosition.ProfitAndLoss.Realized;
|
||||
|
||||
position.ProfitAndLoss = new ProfitAndLoss
|
||||
{
|
||||
Realized = brokerRealizedPnl,
|
||||
Net = brokerRealizedPnl - totalBotFees
|
||||
};
|
||||
|
||||
// Update the closing trade price if available
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
else if (brokerPosition != null)
|
||||
{
|
||||
await LogDebugAsync(
|
||||
$"⚠️ Last Position Not SHORT\n" +
|
||||
$"Position: `{position.Identifier}`\n" +
|
||||
$"Last Position Direction: `{brokerPosition.OriginDirection}`\n" +
|
||||
$"Expected SHORT to confirm spot was sold/closed\n" +
|
||||
$"Will continue with candle-based calculation");
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No matching position found or not reconciled, continue with candle-based calculation
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error reconciling spot position with broker history for position {PositionId}", position.Identifier);
|
||||
await LogWarningAsync(
|
||||
$"⚠️ Error During Spot Position History Reconciliation\n" +
|
||||
$"Position: `{position.Identifier}`\n" +
|
||||
$"Error: {ex.Message}\n" +
|
||||
$"Will continue with candle-based calculation");
|
||||
return false; // On error, continue with candle-based calculation
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task<(decimal closingPrice, bool pnlCalculated)> CalculatePositionClosingFromCandles(
|
||||
|
||||
@@ -1175,7 +1175,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
{
|
||||
Candle currentCandle = await GetCurrentCandleForPositionClose(Account, Config.Ticker.ToString());
|
||||
|
||||
// Try broker history reconciliation first (futures-specific)
|
||||
// Try broker history reconciliation first
|
||||
var brokerHistoryReconciled = await ReconcileWithBrokerHistory(position, currentCandle);
|
||||
if (brokerHistoryReconciled && !forceMarketClose)
|
||||
{
|
||||
|
||||
@@ -7,13 +7,20 @@ test('GMX get spot position history - Market swaps', async (t) => {
|
||||
await t.test('should get spot swap executions', async () => {
|
||||
const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78')
|
||||
|
||||
// Get today's date range (start and end of today)
|
||||
const today = new Date()
|
||||
const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0)
|
||||
const endOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 999)
|
||||
const fromDateTime = startOfDay.toISOString()
|
||||
const toDateTime = endOfDay.toISOString()
|
||||
|
||||
const result = await getSpotPositionHistoryImpl(
|
||||
sdk,
|
||||
0, // pageIndex
|
||||
100, // pageSize
|
||||
Ticker.BTC, // ticker
|
||||
'2025-12-09T00:00:00.000Z', // fromDateTime
|
||||
'2025-12-11T00:00:00.000Z' // toDateTime
|
||||
fromDateTime, // fromDateTime (today's start)
|
||||
toDateTime // toDateTime (today's end)
|
||||
)
|
||||
|
||||
console.log('\n📊 Spot Swap History Summary:')
|
||||
|
||||
Reference in New Issue
Block a user