Enhance SpotBot slippage handling and logging

- Increased slippage tolerance from 0.6% to 0.7% to account for gas reserves.
- Improved logging to provide detailed information when adjusting position quantities due to slippage or when retaining original quantities.
- Updated CloseSpotPositionCommandHandler to use the position's opened quantity instead of the entire wallet balance, ensuring gas fees are preserved.
- Adjusted Web3ProxyService settings for retry attempts and operation timeouts to improve performance.
- Enhanced swap token implementation to handle native tokens correctly and increased operation timeout for better reliability.
This commit is contained in:
2026-01-05 22:13:18 +07:00
parent a0d5e336d5
commit 645bbe6d95
5 changed files with 106 additions and 55 deletions

View File

@@ -287,7 +287,7 @@ public class SpotBot : TradingBotBase
return false;
}
var tolerance = positionQuantity * 0.006m; // 0.6% tolerance for slippage
var tolerance = positionQuantity * 0.007m; // 0.7% tolerance for slippage and gas reserve
var difference = Math.Abs(tokenBalance - positionQuantity);
if (difference > tolerance)
@@ -307,8 +307,25 @@ public class SpotBot : TradingBotBase
lastPosition.Status = PositionStatus.Filled;
lastPosition.Open.SetStatus(TradeStatus.Filled);
// Update quantity to match actual token balance
lastPosition.Open.Quantity = tokenBalance;
// Only update quantity if actual balance is less than position quantity (slippage loss)
// Don't update if balance is higher (likely includes gas reserve for ETH)
if (tokenBalance < positionQuantity)
{
await LogInformationAsync(
$"📉 Adjusting Position Quantity Due to Slippage\n" +
$"Original Quantity: `{positionQuantity:F5}`\n" +
$"Actual Balance: `{tokenBalance:F5}`\n" +
$"Difference: `{difference:F5}`");
lastPosition.Open.Quantity = tokenBalance;
}
else
{
await LogInformationAsync(
$" Keeping Original Position Quantity\n" +
$"Position Quantity: `{positionQuantity:F5}`\n" +
$"Actual Balance: `{tokenBalance:F5}`\n" +
$"Not updating (balance includes gas reserve or is within tolerance)");
}
// Calculate current PnL
var currentPrice = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(
@@ -402,7 +419,7 @@ public class SpotBot : TradingBotBase
// If balance is greater, it could be orphaned tokens from previous positions
if (tokenBalanceAmount < positionQuantity)
{
var tolerance = positionQuantity * 0.006m; // 0.6% tolerance to account for slippage
var tolerance = positionQuantity * 0.007m; // 0.7% tolerance to account for slippage and gas reserve
var difference = positionQuantity - tokenBalanceAmount;
if (difference > tolerance)
@@ -413,7 +430,7 @@ public class SpotBot : TradingBotBase
$"Position Quantity: `{positionQuantity:F5}`\n" +
$"Token Balance: `{tokenBalanceAmount:F5}`\n" +
$"Difference: `{difference:F5}`\n" +
$"Tolerance (0.6%): `{tolerance:F5}`\n" +
$"Tolerance (0.7%): `{tolerance:F5}`\n" +
$"Token balance is significantly lower than expected\n" +
$"Skipping position synchronization");
return; // Skip processing if balance is too low
@@ -446,23 +463,35 @@ public class SpotBot : TradingBotBase
internalPosition.Open.SetStatus(TradeStatus.Filled);
positionForSignal.Open.SetStatus(TradeStatus.Filled);
// Update quantity to match actual token balance
// Update quantity to match actual token balance only if difference is within acceptable tolerance
// For ETH, we need to be careful not to include gas reserve in the position quantity
var actualTokenBalance = tokenBalance.Amount;
var quantityTolerance = internalPosition.Open.Quantity * 0.006m; // 0.6% tolerance for slippage
var quantityTolerance = internalPosition.Open.Quantity * 0.007m; // 0.7% tolerance for slippage and gas reserve
var quantityDifference = Math.Abs(internalPosition.Open.Quantity - actualTokenBalance);
if (quantityDifference > quantityTolerance)
// Only update if the actual balance is LESS than expected (slippage loss)
// Don't update if balance is higher (likely includes gas reserve or orphaned tokens)
if (actualTokenBalance < internalPosition.Open.Quantity && quantityDifference > quantityTolerance)
{
await LogDebugAsync(
$"🔄 Token Balance Mismatch\n" +
$"🔄 Token Balance Lower Than Expected (Slippage)\n" +
$"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" +
$"Broker Balance: `{actualTokenBalance:F5}`\n" +
$"Difference: `{quantityDifference:F5}`\n" +
$"Tolerance (0.6%): `{quantityTolerance:F5}`\n" +
$"Updating to match broker balance");
$"Tolerance (0.7%): `{quantityTolerance:F5}`\n" +
$"Updating to match actual balance");
internalPosition.Open.Quantity = actualTokenBalance;
positionForSignal.Open.Quantity = actualTokenBalance;
}
else if (actualTokenBalance > internalPosition.Open.Quantity)
{
await LogDebugAsync(
$" Token Balance Higher Than Position Quantity\n" +
$"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" +
$"Broker Balance: `{actualTokenBalance:F5}`\n" +
$"Difference: `{quantityDifference:F5}`\n" +
$"Keeping original position quantity (extra balance likely gas reserve or orphaned tokens)");
}
// Calculate and update PnL based on current price
var currentPrice = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(

View File

@@ -48,18 +48,13 @@ public class CloseSpotPositionCommandHandler(
{
// For live trading, call SwapGmxTokensAsync
var account = await accountService.GetAccountById(request.AccountId);
var tokenBalance = await exchangeService.GetBalance(account, request.Position.Ticker);
if (tokenBalance == null || tokenBalance.Amount <= 0)
{
throw new InvalidOperationException(
$"No available balance to close spot position for {request.Position.Ticker}");
}
amountToSwap = tokenBalance.Amount;
// Use the position's opened quantity instead of the entire wallet balance
// This naturally leaves extra ETH for gas fees and avoids the need for explicit reservation
amountToSwap = request.Position.Open.Quantity;
logger?.LogInformation(
"Closing spot position: PositionId={PositionId}, Ticker={Ticker}, TokenBalance={TokenBalance}, Swapping to USDC",
"Closing spot position: PositionId={PositionId}, Ticker={Ticker}, PositionQuantity={PositionQuantity}, Swapping to USDC",
request.Position.Identifier, request.Position.Ticker, amountToSwap);
swapResult = await tradingService.SwapGmxTokensAsync(