Update fetch borkerPosition in bot + better HandleClosePosition + Add debug channel to receive all debug
This commit is contained in:
@@ -67,6 +67,7 @@
|
|||||||
"CopyTradingChannelId": 1132022887012909126,
|
"CopyTradingChannelId": 1132022887012909126,
|
||||||
"RequestsChannelId": 1018589494968078356,
|
"RequestsChannelId": 1018589494968078356,
|
||||||
"FundingRateChannelId": 1263566138709774336,
|
"FundingRateChannelId": 1263566138709774336,
|
||||||
|
"DebugChannelId": 1431289070297813044,
|
||||||
"ButtonExpirationMinutes": 10
|
"ButtonExpirationMinutes": 10
|
||||||
},
|
},
|
||||||
"RunOrleansGrains": true,
|
"RunOrleansGrains": true,
|
||||||
|
|||||||
@@ -21,4 +21,5 @@ public interface IDiscordService
|
|||||||
Task SendDowngradedFundingRate(FundingRate oldRate);
|
Task SendDowngradedFundingRate(FundingRate oldRate);
|
||||||
Task SendNewTopFundingRate(FundingRate newRate);
|
Task SendNewTopFundingRate(FundingRate newRate);
|
||||||
Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate);
|
Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate);
|
||||||
|
Task SendDebugMessage(string message);
|
||||||
}
|
}
|
||||||
@@ -28,4 +28,5 @@ public interface IMessengerService
|
|||||||
Task SendBacktestNotification(Backtest backtest);
|
Task SendBacktestNotification(Backtest backtest);
|
||||||
Task SendGeneticAlgorithmNotification(GeneticRequest request, double bestFitness, object? bestChromosome);
|
Task SendGeneticAlgorithmNotification(GeneticRequest request, double bestFitness, object? bestChromosome);
|
||||||
Task SendClosedPosition(Position position, User user);
|
Task SendClosedPosition(Position position, User user);
|
||||||
|
Task SendDebugMessage(string message);
|
||||||
}
|
}
|
||||||
@@ -371,9 +371,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
if (positionForSignal.Status == PositionStatus.Canceled ||
|
if (positionForSignal.Status == PositionStatus.Canceled ||
|
||||||
positionForSignal.Status == PositionStatus.Rejected)
|
positionForSignal.Status == PositionStatus.Rejected)
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
"Skipping update for position {PositionId} - status is {Status} (never filled)",
|
$"Skipping update for position {positionForSignal.Identifier} - status is {positionForSignal.Status} (never filled)");
|
||||||
positionForSignal.Identifier, positionForSignal.Status);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,8 +401,12 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest)
|
||||||
{
|
{
|
||||||
var brokerPosition = brokerPositions.FirstOrDefault(p =>
|
// Improved broker position matching with more robust logic
|
||||||
p.Ticker == Config.Ticker && p.OriginDirection == positionForSignal.OriginDirection);
|
var brokerPosition = brokerPositions
|
||||||
|
.Where(p => p.Ticker == Config.Ticker)
|
||||||
|
.OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue)
|
||||||
|
.FirstOrDefault(p => p.OriginDirection == positionForSignal.OriginDirection);
|
||||||
|
|
||||||
if (brokerPosition != null)
|
if (brokerPosition != null)
|
||||||
{
|
{
|
||||||
var previousPositionStatus = internalPosition.Status;
|
var previousPositionStatus = internalPosition.Status;
|
||||||
@@ -419,8 +422,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
internalPosition.Open.SetStatus(TradeStatus.Filled);
|
internalPosition.Open.SetStatus(TradeStatus.Filled);
|
||||||
positionForSignal.Open.SetStatus(TradeStatus.Filled);
|
positionForSignal.Open.SetStatus(TradeStatus.Filled);
|
||||||
|
|
||||||
internalPosition.Open.SetPrice(brokerPosition.Open.Price, 5);
|
internalPosition.Open.Price = brokerPosition.Open.Price;
|
||||||
positionForSignal.Open.SetPrice(brokerPosition.Open.Price, 5);
|
positionForSignal.Open.Price = brokerPosition.Open.Price;
|
||||||
|
|
||||||
// Update Open trade ExchangeOrderId if broker position has one
|
// Update Open trade ExchangeOrderId if broker position has one
|
||||||
if (brokerPosition.Open?.ExchangeOrderId != null && internalPosition.Open != null)
|
if (brokerPosition.Open?.ExchangeOrderId != null && internalPosition.Open != null)
|
||||||
@@ -462,12 +465,50 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No position on the broker, the position have been closed by the exchange
|
// Position not found in broker's active positions list
|
||||||
|
// Need to verify if it was actually closed or just not returned by the API
|
||||||
if (internalPosition.Status.Equals(PositionStatus.Filled))
|
if (internalPosition.Status.Equals(PositionStatus.Filled))
|
||||||
{
|
{
|
||||||
|
Logger.LogWarning(
|
||||||
|
$"⚠️ Position Sync Issue Detected\n" +
|
||||||
|
$"Internal position {internalPosition.Identifier} shows Filled\n" +
|
||||||
|
$"But not found in broker positions list (Count: {brokerPositions.Count})\n" +
|
||||||
|
$"Checking position history before marking as closed...");
|
||||||
|
|
||||||
|
// Verify in exchange history before assuming it's closed
|
||||||
|
bool existsInHistory = await CheckPositionInExchangeHistory(positionForSignal);
|
||||||
|
|
||||||
|
if (existsInHistory)
|
||||||
|
{
|
||||||
|
// Position was actually filled and closed by the exchange
|
||||||
|
Logger.LogInformation(
|
||||||
|
$"✅ Position Confirmed Closed via History\n" +
|
||||||
|
$"Position {internalPosition.Identifier} found in exchange history\n" +
|
||||||
|
$"Proceeding with HandleClosedPosition");
|
||||||
|
|
||||||
internalPosition.Status = PositionStatus.Finished;
|
internalPosition.Status = PositionStatus.Finished;
|
||||||
// Call HandleClosedPosition to ensure trade dates are properly updated
|
|
||||||
await HandleClosedPosition(internalPosition);
|
await HandleClosedPosition(internalPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Position not in history either - could be API issue or timing problem
|
||||||
|
// Don't immediately close, just log warning and retry next cycle
|
||||||
|
await LogWarning(
|
||||||
|
$"⚠️ Position Synchronization Warning\n" +
|
||||||
|
$"Position `{internalPosition.Identifier}` ({internalPosition.OriginDirection} {Config.Ticker})\n" +
|
||||||
|
$"Not found in broker positions OR exchange history\n" +
|
||||||
|
$"Status: `{internalPosition.Status}`\n" +
|
||||||
|
$"This could indicate:\n" +
|
||||||
|
$"• API returned incomplete data\n" +
|
||||||
|
$"• Timing issue with broker API\n" +
|
||||||
|
$"• Position direction mismatch\n" +
|
||||||
|
$"Will retry verification on next cycle before taking action");
|
||||||
|
|
||||||
|
// Don't change the position status yet - wait for next cycle to verify
|
||||||
|
await LogDebug(
|
||||||
|
$"Position {internalPosition.Identifier} will be rechecked on next bot cycle for broker synchronization");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -852,7 +893,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
private async Task<Position> OpenPosition(LightSignal signal)
|
private async Task<Position> OpenPosition(LightSignal signal)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Opening position for {signal.Identifier}");
|
await LogDebug($"🔓 Opening position for signal: `{signal.Identifier}`");
|
||||||
|
|
||||||
// Check for any existing open position (not finished) for this ticker
|
// Check for any existing open position (not finished) for this ticker
|
||||||
var openedPosition =
|
var openedPosition =
|
||||||
@@ -967,7 +1008,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
async messengerService => { await messengerService.SendPosition(position); });
|
async messengerService => { await messengerService.SendPosition(position); });
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug($"Position requested");
|
await LogDebug($"✅ Position requested successfully for signal: `{signal.Identifier}`");
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1104,12 +1145,22 @@ public class TradingBotBase : ITradingBot
|
|||||||
List<Position> positions = null;
|
List<Position> positions = null;
|
||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
async exchangeService => { positions = [.. await exchangeService.GetBrokerPositions(Account)]; });
|
async exchangeService => { positions = [.. await exchangeService.GetBrokerPositions(Account)]; });
|
||||||
if (!positions.Any(p => p.Ticker == Config.Ticker))
|
|
||||||
|
// Check if there's a position for this ticker on the broker
|
||||||
|
var brokerPositionForTicker = positions.FirstOrDefault(p => p.Ticker == Config.Ticker);
|
||||||
|
if (brokerPositionForTicker == null)
|
||||||
{
|
{
|
||||||
|
// No position on broker for this ticker, safe to open
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle existing position on broker
|
// Handle existing position on broker
|
||||||
|
await LogDebug(
|
||||||
|
$"🔍 Broker Position Found\n" +
|
||||||
|
$"Ticker: {Config.Ticker}\n" +
|
||||||
|
$"Direction: {brokerPositionForTicker.OriginDirection}\n" +
|
||||||
|
$"Checking internal positions for synchronization...");
|
||||||
|
|
||||||
var previousPosition = Positions.Values.LastOrDefault();
|
var previousPosition = Positions.Values.LastOrDefault();
|
||||||
List<Trade> orders = null;
|
List<Trade> orders = null;
|
||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
@@ -1118,31 +1169,61 @@ public class TradingBotBase : ITradingBot
|
|||||||
orders = [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)];
|
orders = [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)];
|
||||||
});
|
});
|
||||||
|
|
||||||
var reason = $"Cannot open position. There is already a position open for {Config.Ticker} on the broker.";
|
var reason =
|
||||||
|
$"Cannot open position. There is already a position open for {Config.Ticker} on the broker (Direction: {brokerPositionForTicker.OriginDirection}).";
|
||||||
|
|
||||||
if (previousPosition != null)
|
if (previousPosition != null)
|
||||||
{
|
{
|
||||||
|
// Check if this position matches the broker position
|
||||||
|
if (previousPosition.OriginDirection == brokerPositionForTicker.OriginDirection)
|
||||||
|
{
|
||||||
|
// Same direction - this is likely the same position
|
||||||
if (orders.Count >= 2)
|
if (orders.Count >= 2)
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation(
|
||||||
|
$"✅ Broker Position Matched with Internal Position\n" +
|
||||||
|
$"Position: {previousPosition.Identifier}\n" +
|
||||||
|
$"Direction: {previousPosition.OriginDirection}\n" +
|
||||||
|
$"Orders found: {orders.Count}\n" +
|
||||||
|
$"Setting status to Filled");
|
||||||
await SetPositionStatus(previousPosition.SignalIdentifier, PositionStatus.Filled);
|
await SetPositionStatus(previousPosition.SignalIdentifier, PositionStatus.Filled);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Broker already has an open position, cancel the internally created (DB) position
|
// Position exists on broker but not enough orders - something is wrong
|
||||||
await SetPositionStatus(previousPosition.SignalIdentifier, PositionStatus.Canceled);
|
Logger.LogWarning(
|
||||||
if (!Config.IsForBacktest)
|
$"⚠️ Incomplete Order Set\n" +
|
||||||
{
|
$"Position: {previousPosition.Identifier}\n" +
|
||||||
await UpdatePositionDatabase(previousPosition);
|
$"Direction: {previousPosition.OriginDirection}\n" +
|
||||||
}
|
$"Expected orders: ≥2, Found: {orders.Count}\n" +
|
||||||
|
$"This position may need manual intervention");
|
||||||
|
|
||||||
reason +=
|
reason += $" Position exists on broker but only has {orders.Count} orders (expected ≥2).";
|
||||||
" Position open on broker; internal position has been marked as Canceled.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Different direction - possible flip scenario or orphaned position
|
||||||
|
Logger.LogWarning(
|
||||||
|
$"⚠️ Direction Mismatch Detected\n" +
|
||||||
|
$"Internal: {previousPosition.OriginDirection}\n" +
|
||||||
|
$"Broker: {brokerPositionForTicker.OriginDirection}\n" +
|
||||||
|
$"This could indicate a flipped position or orphaned broker position");
|
||||||
|
|
||||||
reason +=
|
reason +=
|
||||||
" Position open on broker but not enough orders or no previous position internally saved by the bot";
|
$" Direction mismatch: Internal ({previousPosition.OriginDirection}) vs Broker ({brokerPositionForTicker.OriginDirection}).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Broker has a position but we don't have any internal tracking
|
||||||
|
Logger.LogWarning(
|
||||||
|
$"⚠️ Orphaned Broker Position Detected\n" +
|
||||||
|
$"Broker has position for {Config.Ticker} ({brokerPositionForTicker.OriginDirection})\n" +
|
||||||
|
$"But no internal position found in bot tracking\n" +
|
||||||
|
$"This may require manual cleanup");
|
||||||
|
|
||||||
|
reason += " Position open on broker but no internal position tracked by the bot.";
|
||||||
}
|
}
|
||||||
|
|
||||||
await LogWarning(reason);
|
await LogWarning(reason);
|
||||||
@@ -1158,13 +1239,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
public async Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
public async Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
||||||
bool tradeClosingPosition = false)
|
bool tradeClosingPosition = false)
|
||||||
{
|
{
|
||||||
if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled &&
|
|
||||||
tradeToClose.TradeType == TradeType.StopMarket)
|
|
||||||
{
|
|
||||||
// If trade is the 2nd Take profit
|
|
||||||
tradeToClose.Quantity = position.TakeProfit2.Quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
await LogInformation(
|
await LogInformation(
|
||||||
$"🔧 Closing {position.OriginDirection} Trade\nTicker: `{Config.Ticker}`\nPrice: `${lastPrice}`\n📋 Type: `{tradeToClose.TradeType}`\n📊 Quantity: `{tradeToClose.Quantity:F5}`");
|
$"🔧 Closing {position.OriginDirection} Trade\nTicker: `{Config.Ticker}`\nPrice: `${lastPrice}`\n📋 Type: `{tradeToClose.TradeType}`\n📊 Quantity: `{tradeToClose.Quantity:F5}`");
|
||||||
|
|
||||||
@@ -1175,6 +1249,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
async exchangeService =>
|
async exchangeService =>
|
||||||
{
|
{
|
||||||
|
// TODO should also pass the direction to get quantity in correct position
|
||||||
quantity = await exchangeService.GetQuantityInPosition(Account, Config.Ticker);
|
quantity = await exchangeService.GetQuantityInPosition(Account, Config.Ticker);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1182,7 +1257,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Get status of position before closing it. The position might be already close by the exchange
|
// Get status of position before closing it. The position might be already close by the exchange
|
||||||
if (!Config.IsForBacktest && quantity == 0)
|
if (!Config.IsForBacktest && quantity == 0)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Trade already close on exchange");
|
await LogDebug($"✅ Trade already closed on exchange for position: `{position.Identifier}`");
|
||||||
await HandleClosedPosition(position);
|
await HandleClosedPosition(position);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1247,39 +1322,44 @@ public class TradingBotBase : ITradingBot
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"🔍 Fetching Position History from GMX\nPosition: `{position.Identifier}`\nTicker: `{Config.Ticker}`");
|
$"🔍 Fetching Position History from GMX\nPosition: `{position.Identifier}`\nTicker: `{Config.Ticker}`");
|
||||||
|
|
||||||
var positionHistory = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Position>>(
|
var positionHistory = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Position>>(
|
||||||
_scopeFactory,
|
_scopeFactory,
|
||||||
async exchangeService =>
|
async exchangeService =>
|
||||||
{
|
{
|
||||||
// Get position history from the last 24 hours
|
// Get position history from the last 24 hours for better coverage
|
||||||
var fromDate = DateTime.UtcNow.AddHours(-1);
|
var fromDate = DateTime.UtcNow.AddHours(-24);
|
||||||
var toDate = DateTime.UtcNow;
|
var toDate = DateTime.UtcNow;
|
||||||
return await exchangeService.GetPositionHistory(Account, Config.Ticker, fromDate, toDate);
|
return await exchangeService.GetPositionHistory(Account, Config.Ticker, fromDate, toDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find the matching position in history based on the most recent closed position
|
// Find the matching position in history based on the most recent closed position with same direction
|
||||||
if (positionHistory != null && positionHistory.Any())
|
if (positionHistory != null && positionHistory.Any())
|
||||||
{
|
{
|
||||||
// Get the most recent closed position from GMX
|
// Get the most recent closed position from GMX that matches the direction
|
||||||
var gmxPosition = positionHistory.OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue)
|
var brokerPosition = positionHistory
|
||||||
|
.Where(p => p.OriginDirection == position.OriginDirection) // Ensure same direction
|
||||||
|
.OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (gmxPosition != null && gmxPosition.ProfitAndLoss != null)
|
if (brokerPosition != null && brokerPosition.ProfitAndLoss != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"✅ GMX Position History Found\n" +
|
$"✅ Broker Position History Found\n" +
|
||||||
$"Position: `{position.Identifier}`\n" +
|
$"Position: `{position.Identifier}`\n" +
|
||||||
$"GMX Realized PnL (after fees): `${gmxPosition.ProfitAndLoss.Realized:F2}`\n" +
|
$"Realized PnL (after fees): `${brokerPosition.ProfitAndLoss.Realized:F2}`\n" +
|
||||||
$"Bot's UI Fees: `${position.UiFees:F2}`\n" +
|
$"Bot's UI Fees: `${position.UiFees:F2}`\n" +
|
||||||
$"Bot's Gas Fees: `${position.GasFees:F2}`");
|
$"Bot's Gas Fees: `${position.GasFees:F2}`");
|
||||||
|
|
||||||
// Use the actual GMX PnL data (this is already net of fees from GMX)
|
// Use the actual GMX PnL data (this is already net of fees from GMX)
|
||||||
// We use this for reconciliation with the bot's own calculations
|
// We use this for reconciliation with the bot's own calculations
|
||||||
var totalBotFees = position.GasFees + position.UiFees;
|
var closingVolume = brokerPosition.Open.Price * position.Open.Quantity *
|
||||||
var gmxNetPnl = gmxPosition.ProfitAndLoss.Realized; // This is already after GMX fees
|
position.Open.Leverage;
|
||||||
|
var totalBotFees = position.GasFees + position.UiFees +
|
||||||
|
TradingHelpers.CalculateClosingUiFees(closingVolume);
|
||||||
|
var gmxNetPnl = brokerPosition.ProfitAndLoss.Realized; // This is already after GMX fees
|
||||||
|
|
||||||
position.ProfitAndLoss = new ProfitAndLoss
|
position.ProfitAndLoss = new ProfitAndLoss
|
||||||
{
|
{
|
||||||
@@ -1290,19 +1370,19 @@ public class TradingBotBase : ITradingBot
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Update the closing trade price if available
|
// Update the closing trade price if available
|
||||||
if (gmxPosition.Open != null)
|
if (brokerPosition.Open != null)
|
||||||
{
|
{
|
||||||
var gmxClosingPrice = gmxPosition.Open.Price;
|
var brokerClosingPrice = brokerPosition.Open.Price;
|
||||||
|
var isProfitable = position.OriginDirection == TradeDirection.Long
|
||||||
// Determine which trade was the closing trade based on profitability
|
? position.Open.Price < brokerClosingPrice
|
||||||
bool isProfitable = position.ProfitAndLoss.Realized > 0;
|
: position.Open.Price > brokerClosingPrice;
|
||||||
|
|
||||||
if (isProfitable)
|
if (isProfitable)
|
||||||
{
|
{
|
||||||
if (position.TakeProfit1 != null)
|
if (position.TakeProfit1 != null)
|
||||||
{
|
{
|
||||||
position.TakeProfit1.SetPrice(gmxClosingPrice, 2);
|
position.TakeProfit1.Price = brokerClosingPrice;
|
||||||
position.TakeProfit1.SetDate(gmxPosition.Open.Date);
|
position.TakeProfit1.SetDate(brokerPosition.Open.Date);
|
||||||
position.TakeProfit1.SetStatus(TradeStatus.Filled);
|
position.TakeProfit1.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1316,8 +1396,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
{
|
{
|
||||||
if (position.StopLoss != null)
|
if (position.StopLoss != null)
|
||||||
{
|
{
|
||||||
position.StopLoss.SetPrice(gmxClosingPrice, 2);
|
position.StopLoss.Price = brokerClosingPrice;
|
||||||
position.StopLoss.SetDate(gmxPosition.Open.Date);
|
position.StopLoss.SetDate(brokerPosition.Open.Date);
|
||||||
position.StopLoss.SetStatus(TradeStatus.Filled);
|
position.StopLoss.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1333,12 +1413,12 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"📊 Position Reconciliation Complete\n" +
|
$"📊 Position Reconciliation Complete\n" +
|
||||||
$"Position: `{position.Identifier}`\n" +
|
$"Position: `{position.Identifier}`\n" +
|
||||||
$"Closing Price: `${gmxClosingPrice:F2}`\n" +
|
$"Closing Price: `${brokerClosingPrice:F2}`\n" +
|
||||||
$"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" +
|
$"Used: `{(isProfitable ? "Take Profit" : "Stop Loss")}`\n" +
|
||||||
$"PnL from GMX: `${position.ProfitAndLoss.Realized:F2}`");
|
$"PnL from broker: `${position.ProfitAndLoss.Realized:F2}`");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the candle-based PnL calculation since we have actual GMX data
|
// Skip the candle-based PnL calculation since we have actual GMX data
|
||||||
@@ -1457,7 +1537,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
position.TakeProfit2.SetStatus(TradeStatus.Cancelled);
|
position.TakeProfit2.SetStatus(TradeStatus.Cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"🛑 Stop Loss Execution Confirmed\n" +
|
$"🛑 Stop Loss Execution Confirmed\n" +
|
||||||
$"Position: `{position.Identifier}`\n" +
|
$"Position: `{position.Identifier}`\n" +
|
||||||
$"SL Price: `${closingPrice:F2}` was hit (was `${position.StopLoss.Price:F2}`)\n" +
|
$"SL Price: `${closingPrice:F2}` was hit (was `${position.StopLoss.Price:F2}`)\n" +
|
||||||
@@ -1480,7 +1560,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
position.StopLoss.SetStatus(TradeStatus.Cancelled);
|
position.StopLoss.SetStatus(TradeStatus.Cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"🎯 Take Profit Execution Confirmed\n" +
|
$"🎯 Take Profit Execution Confirmed\n" +
|
||||||
$"Position: `{position.Identifier}`\n" +
|
$"Position: `{position.Identifier}`\n" +
|
||||||
$"TP Price: `${closingPrice:F2}` was hit (was `${position.TakeProfit1.Price:F2}`)\n" +
|
$"TP Price: `${closingPrice:F2}` was hit (was `${position.TakeProfit1.Price:F2}`)\n" +
|
||||||
@@ -1535,7 +1615,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"✋ Manual/Exchange Close Detected\n" +
|
$"✋ Manual/Exchange Close Detected\n" +
|
||||||
$"Position: `{position.Identifier}`\n" +
|
$"Position: `{position.Identifier}`\n" +
|
||||||
$"SL: `${position.StopLoss.Price:F2}` | TP: `${position.TakeProfit1.Price:F2}`\n" +
|
$"SL: `${position.StopLoss.Price:F2}` | TP: `${position.TakeProfit1.Price:F2}`\n" +
|
||||||
@@ -1604,7 +1684,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
position.ProfitAndLoss.Net = netPnl;
|
position.ProfitAndLoss.Net = netPnl;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"💰 P&L Calculated for Position {position.Identifier}\n" +
|
$"💰 P&L Calculated for Position {position.Identifier}\n" +
|
||||||
$"Entry: `${entryPrice:F2}` | Exit: `${closingPrice:F2}`\n" +
|
$"Entry: `${entryPrice:F2}` | Exit: `${closingPrice:F2}`\n" +
|
||||||
$"Realized P&L: `${pnl:F2}` | Net P&L (after fees): `${position.ProfitAndLoss.Net:F2}`\n" +
|
$"Realized P&L: `${pnl:F2}` | Net P&L (after fees): `${position.ProfitAndLoss.Net:F2}`\n" +
|
||||||
@@ -1635,30 +1715,29 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
"Skipping PositionClosed notification for position {PositionId} - position was never filled (Open trade status: {OpenStatus})",
|
$"Skipping PositionClosed notification for position {position.Identifier} - position was never filled (Open trade status: {position.Open?.Status})");
|
||||||
position.Identifier, position.Open?.Status);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update balance and log success if position was actually filled
|
// Only update balance and log success if position was actually filled
|
||||||
if (position.Open?.Status == TradeStatus.Filled)
|
if (position.Open?.Status == TradeStatus.Filled)
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"✅ Position Closed Successfully\nPosition: `{position.SignalIdentifier}`\nPnL: `${position.ProfitAndLoss?.Net:F2}`");
|
$"✅ Position Closed Successfully\nPosition: `{position.SignalIdentifier}`\nPnL: `${position.ProfitAndLoss?.Net:F2}`");
|
||||||
|
|
||||||
if (position.ProfitAndLoss != null)
|
if (position.ProfitAndLoss != null)
|
||||||
{
|
{
|
||||||
Config.BotTradingBalance += position.ProfitAndLoss.Net;
|
Config.BotTradingBalance += position.ProfitAndLoss.Net;
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
string.Format("💰 Balance Updated\nNew bot trading balance: `${0:F2}`",
|
string.Format("💰 Balance Updated\nNew bot trading balance: `${0:F2}`",
|
||||||
Config.BotTradingBalance));
|
Config.BotTradingBalance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"✅ Position Cleanup\nPosition: `{position.SignalIdentifier}` was never filled - no balance or PnL changes");
|
$"✅ Position Cleanup\nPosition: `{position.SignalIdentifier}` was never filled - no balance or PnL changes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1701,24 +1780,24 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
if (cancelClose)
|
if (cancelClose)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Position still open, cancel close orders");
|
await LogDebug($"Position still open, cancel close orders");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Canceling all orders for {Config.Ticker}");
|
await LogDebug($"Canceling all orders for {Config.Ticker}");
|
||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
async exchangeService =>
|
async exchangeService =>
|
||||||
{
|
{
|
||||||
await exchangeService.CancelOrder(Account, Config.Ticker);
|
await exchangeService.CancelOrder(Account, Config.Ticker);
|
||||||
var closePendingOrderStatus = await exchangeService.CancelOrder(Account, Config.Ticker);
|
var closePendingOrderStatus = await exchangeService.CancelOrder(Account, Config.Ticker);
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}");
|
$"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"No need to cancel orders for {Config.Ticker}");
|
await LogDebug($"No need to cancel orders for {Config.Ticker}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1896,6 +1975,27 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task LogDebug(string message)
|
||||||
|
{
|
||||||
|
if (Config.IsForBacktest)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Logger.LogDebug(message);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
|
||||||
|
async messengerService =>
|
||||||
|
{
|
||||||
|
await messengerService.SendDebugMessage($"🤖 {Account.User.AgentName} - {Config.Name}\n{message}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
private async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
||||||
{
|
{
|
||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest)
|
||||||
@@ -1977,12 +2077,12 @@ public class TradingBotBase : ITradingBot
|
|||||||
signalValidationResult.IsBlocked)
|
signalValidationResult.IsBlocked)
|
||||||
{
|
{
|
||||||
signal.Status = SignalStatus.Expired;
|
signal.Status = SignalStatus.Expired;
|
||||||
Logger.LogDebug($"Signal {signal.Identifier} blocked by Synth risk assessment");
|
await LogDebug($"Signal {signal.Identifier} blocked by Synth risk assessment");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
signal.Confidence = signalValidationResult.Confidence;
|
signal.Confidence = signalValidationResult.Confidence;
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"Signal {signal.Identifier} passed Synth risk assessment with confidence {signalValidationResult.Confidence}");
|
$"Signal {signal.Identifier} passed Synth risk assessment with confidence {signalValidationResult.Confidence}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2001,7 +2101,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"Processed signal for {Config.Ticker}: {signal.Direction} with status {signal.Status}");
|
$"Processed signal for {Config.Ticker}: {signal.Direction} with status {signal.Status}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -2376,8 +2476,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
if (LastCandle != null)
|
if (LastCandle != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Successfully refreshed last candle for {Ticker} at {Date}",
|
await LogDebug($"Successfully refreshed last candle for {Config.Ticker} at {LastCandle.Date}");
|
||||||
Config.Ticker, LastCandle.Date);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2472,8 +2571,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await agentGrain.OnPositionOpenedAsync(positionOpenEvent);
|
await agentGrain.OnPositionOpenedAsync(positionOpenEvent);
|
||||||
await platformGrain.OnPositionOpenAsync(positionOpenEvent);
|
await platformGrain.OnPositionOpenAsync(positionOpenEvent);
|
||||||
|
|
||||||
Logger.LogDebug("Sent position opened event to both grains for position {PositionId}",
|
await LogDebug($"Sent position opened event to both grains for position {position.Identifier}");
|
||||||
position.Identifier);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NotificationEventType.PositionClosed:
|
case NotificationEventType.PositionClosed:
|
||||||
@@ -2488,8 +2586,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await agentGrain.OnPositionClosedAsync(positionClosedEvent);
|
await agentGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||||
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||||
|
|
||||||
Logger.LogDebug("Sent position closed event to both grains for position {PositionId}",
|
await LogDebug($"Sent position closed event to both grains for position {position.Identifier}");
|
||||||
position.Identifier);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NotificationEventType.PositionUpdated:
|
case NotificationEventType.PositionUpdated:
|
||||||
@@ -2501,8 +2598,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await agentGrain.OnPositionUpdatedAsync(positionUpdatedEvent);
|
await agentGrain.OnPositionUpdatedAsync(positionUpdatedEvent);
|
||||||
// No need to notify platform grain, it will be notified when position is closed or opened only
|
// No need to notify platform grain, it will be notified when position is closed or opened only
|
||||||
|
|
||||||
Logger.LogDebug("Sent position updated event to both grains for position {PositionId}",
|
await LogDebug($"Sent position updated event to both grains for position {position.Identifier}");
|
||||||
position.Identifier);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2530,39 +2626,51 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"🔍 Checking Position History for Position: `{position.Identifier}`\nTicker: `{Config.Ticker}`");
|
$"🔍 Checking Position History for Position: `{position.Identifier}`\nTicker: `{Config.Ticker}`");
|
||||||
|
|
||||||
List<Position> positionHistory = null;
|
List<Position> positionHistory = null;
|
||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
async exchangeService =>
|
async exchangeService =>
|
||||||
{
|
{
|
||||||
// Get position history from the last 24 hours
|
// Get position history from the last 24 hours for comprehensive check
|
||||||
var fromDate = DateTime.UtcNow.AddMinutes(-10);
|
var fromDate = DateTime.UtcNow.AddHours(-24);
|
||||||
var toDate = DateTime.UtcNow;
|
var toDate = DateTime.UtcNow;
|
||||||
positionHistory =
|
positionHistory =
|
||||||
await exchangeService.GetPositionHistory(Account, Config.Ticker, fromDate, toDate);
|
await exchangeService.GetPositionHistory(Account, Config.Ticker, fromDate, toDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if there's a recent position with PnL data
|
// Check if there's a recent position with PnL data and matching direction
|
||||||
if (positionHistory != null && positionHistory.Any())
|
if (positionHistory != null && positionHistory.Any())
|
||||||
{
|
{
|
||||||
var recentPosition = positionHistory
|
var recentPosition = positionHistory
|
||||||
|
.Where(p => p.OriginDirection == position.OriginDirection) // Ensure same direction
|
||||||
.OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue)
|
.OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (recentPosition != null && recentPosition.ProfitAndLoss != null)
|
if (recentPosition != null && recentPosition.ProfitAndLoss != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"✅ Position Found in Exchange History\n" +
|
$"✅ Position Found in Exchange History\n" +
|
||||||
$"Position: `{position.Identifier}`\n" +
|
$"Position: `{position.Identifier}`\n" +
|
||||||
|
$"Direction: `{position.OriginDirection}` (Matched: ✅)\n" +
|
||||||
$"Exchange PnL: `${recentPosition.ProfitAndLoss.Realized:F2}`\n" +
|
$"Exchange PnL: `${recentPosition.ProfitAndLoss.Realized:F2}`\n" +
|
||||||
$"Position was actually filled and closed");
|
$"Position was actually filled and closed");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Found positions in history but none match the direction
|
||||||
|
var allHistoryDirections = positionHistory.Select(p => p.OriginDirection).Distinct().ToList();
|
||||||
|
await LogDebug(
|
||||||
|
$"⚠️ Direction Mismatch in History\n" +
|
||||||
|
$"Looking for: `{position.OriginDirection}`\n" +
|
||||||
|
$"Found in history: `{string.Join(", ", allHistoryDirections)}`\n" +
|
||||||
|
$"No matching position found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug(
|
await LogDebug(
|
||||||
$"❌ No Position Found in Exchange History\nPosition: `{position.Identifier}`\nPosition was never filled");
|
$"❌ No Position Found in Exchange History\nPosition: `{position.Identifier}`\nPosition was never filled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,6 +246,11 @@ public class MessengerService : IMessengerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SendDebugMessage(string message)
|
||||||
|
{
|
||||||
|
await _discordService.SendDebugMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
private string BuildBacktestMessage(Backtest backtest)
|
private string BuildBacktestMessage(Backtest backtest)
|
||||||
{
|
{
|
||||||
var config = backtest.Config;
|
var config = backtest.Config;
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ namespace Managing.Common
|
|||||||
public const double AutoSwapAmount = 3;
|
public const double AutoSwapAmount = 3;
|
||||||
|
|
||||||
// Fee Configuration
|
// Fee Configuration
|
||||||
public const decimal UiFeeRate = 0.001m; // 0.1% UI fee rate
|
public const decimal UiFeeRate = 0.00075m; // 0.1% UI fee rate
|
||||||
public const decimal GasFeePerTransaction = 0.15m; // $0.15 gas fee per transaction
|
public const decimal GasFeePerTransaction = 0.15m; // $0.15 gas fee per transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -503,6 +503,19 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SendDebugMessage(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var channel = _client.GetChannel(_settings.DebugChannelId) as IMessageChannel;
|
||||||
|
await channel.SendMessageAsync(message);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
SentrySdk.CaptureException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SendClosedPosition(string address, Trade oldTrade)
|
public async Task SendClosedPosition(string address, Trade oldTrade)
|
||||||
{
|
{
|
||||||
var fields = new List<EmbedFieldBuilder>()
|
var fields = new List<EmbedFieldBuilder>()
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
ButtonExpirationMinutes = config.GetValue<int>("Discord:ButtonExpirationMinutes");
|
ButtonExpirationMinutes = config.GetValue<int>("Discord:ButtonExpirationMinutes");
|
||||||
HandleUserAction = config.GetValue<bool>("Discord:HandleUserAction");
|
HandleUserAction = config.GetValue<bool>("Discord:HandleUserAction");
|
||||||
BotActivity = config.GetValue<string>("Discord:BotActivity");
|
BotActivity = config.GetValue<string>("Discord:BotActivity");
|
||||||
|
DebugChannelId = config.GetValue<ulong>("Discord:DebugChannelId");
|
||||||
BotEnabled = true;
|
BotEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ButtonExpirationMinutes { get; set; }
|
public int ButtonExpirationMinutes { get; set; }
|
||||||
public bool HandleUserAction { get; }
|
public bool HandleUserAction { get; }
|
||||||
public string BotActivity { get; }
|
public string BotActivity { get; }
|
||||||
|
public ulong DebugChannelId { get; }
|
||||||
public string Token { get; }
|
public string Token { get; }
|
||||||
public ulong SignalChannelId { get; }
|
public ulong SignalChannelId { get; }
|
||||||
public ulong CopyTradingChannelId { get; }
|
public ulong CopyTradingChannelId { get; }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {getClientForAddress, getPositionHistoryImpl} from '../../src/plugins/cus
|
|||||||
|
|
||||||
test('GMX get position history - Closed positions with actual PnL', async (t) => {
|
test('GMX get position history - Closed positions with actual PnL', async (t) => {
|
||||||
await t.test('should get closed positions with actual GMX PnL data', async () => {
|
await t.test('should get closed positions with actual GMX PnL data', async () => {
|
||||||
const sdk = await getClientForAddress('0x987b67313ee4827FE55e1FBcd8883D3bb0Bde83b')
|
const sdk = await getClientForAddress('0x8767F195D1a3103789230aaaE9c0E0825a9802c6')
|
||||||
|
|
||||||
const result = await getPositionHistoryImpl(
|
const result = await getPositionHistoryImpl(
|
||||||
sdk,
|
sdk,
|
||||||
@@ -50,7 +50,7 @@ test('GMX get position history - Closed positions with actual PnL', async (t) =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
await t.test('should get closed positions with date range', async () => {
|
await t.test('should get closed positions with date range', async () => {
|
||||||
const sdk = await getClientForAddress('0x987b67313ee4827FE55e1FBcd8883D3bb0Bde83b')
|
const sdk = await getClientForAddress('0x8767F195D1a3103789230aaaE9c0E0825a9802c6')
|
||||||
|
|
||||||
// Get positions from the last 1 hour
|
// Get positions from the last 1 hour
|
||||||
const toDate = new Date()
|
const toDate = new Date()
|
||||||
@@ -89,7 +89,7 @@ test('GMX get position history - Closed positions with actual PnL', async (t) =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
await t.test('should verify PnL data is suitable for reconciliation', async () => {
|
await t.test('should verify PnL data is suitable for reconciliation', async () => {
|
||||||
const sdk = await getClientForAddress('0x987b67313ee4827FE55e1FBcd8883D3bb0Bde83b')
|
const sdk = await getClientForAddress('0x8767F195D1a3103789230aaaE9c0E0825a9802c6')
|
||||||
|
|
||||||
const result = await getPositionHistoryImpl(sdk, 0, 5)
|
const result = await getPositionHistoryImpl(sdk, 0, 5)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user