Add reconcilliation for cancelled position if needed
This commit is contained in:
@@ -423,11 +423,135 @@ public class SpotBot : TradingBotBase
|
|||||||
return Task.FromResult(false);
|
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
|
// Spot-specific: reconcile with spot position history
|
||||||
// Return false to continue with candle-based calculation
|
try
|
||||||
return Task.FromResult(false);
|
{
|
||||||
|
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(
|
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());
|
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);
|
var brokerHistoryReconciled = await ReconcileWithBrokerHistory(position, currentCandle);
|
||||||
if (brokerHistoryReconciled && !forceMarketClose)
|
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 () => {
|
await t.test('should get spot swap executions', async () => {
|
||||||
const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78')
|
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(
|
const result = await getSpotPositionHistoryImpl(
|
||||||
sdk,
|
sdk,
|
||||||
0, // pageIndex
|
0, // pageIndex
|
||||||
100, // pageSize
|
100, // pageSize
|
||||||
Ticker.BTC, // ticker
|
Ticker.BTC, // ticker
|
||||||
'2025-12-09T00:00:00.000Z', // fromDateTime
|
fromDateTime, // fromDateTime (today's start)
|
||||||
'2025-12-11T00:00:00.000Z' // toDateTime
|
toDateTime // toDateTime (today's end)
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('\n📊 Spot Swap History Summary:')
|
console.log('\n📊 Spot Swap History Summary:')
|
||||||
|
|||||||
Reference in New Issue
Block a user