Enhance SpotBot logging and orphaned position handling

- Updated SpotBot to log detailed information when detecting small token balances, indicating potential gas reserves or dust.
- Introduced a minimum threshold for orphaned positions, improving decision-making on whether to open new positions.
- Enhanced logging for potential zombie positions, providing clearer warnings when token balances are missing.
- Improved force close logging to clarify the status of remaining balances after attempts to clear them.
This commit is contained in:
2026-01-05 19:49:59 +07:00
parent e880dea126
commit 25a2b202a1

View File

@@ -180,13 +180,30 @@ public class SpotBot : TradingBotBase
return false; // Don't allow opening new position until resolved return false; // Don't allow opening new position until resolved
} }
if (tokenBalance is { Value: > 1m }) if (tokenBalance is { Amount: > 0 })
{ {
// We have a token balance but no internal position - attempt to recover orphaned position // Check if this is a meaningful balance or just gas reserves / dust
// Minimum threshold: $10 USD value to be considered an orphaned position
const decimal minOrphanedBalanceValue = 10m;
if (tokenBalance.Value < minOrphanedBalanceValue)
{
await LogDebugAsync(
$" Small Token Balance Detected (Likely Gas Reserve or Dust)\n" +
$"Ticker: {Config.Ticker}\n" +
$"Token balance: `{tokenBalance.Amount:F8}`\n" +
$"USD Value: `${tokenBalance.Value:F2}`\n" +
$"Below orphaned threshold of `${minOrphanedBalanceValue:F2}`\n" +
$"Ignoring - safe to open new position");
return true; // Safe to open new position - this is just dust/gas reserve
}
// We have a significant token balance but no internal position - attempt to recover orphaned position
await LogWarningAsync( await LogWarningAsync(
$"⚠️ Orphaned Token Balance Detected\n" + $"⚠️ Orphaned Token Balance Detected\n" +
$"Ticker: {Config.Ticker}\n" + $"Ticker: {Config.Ticker}\n" +
$"Token balance: `{tokenBalance.Amount:F5}`\n" + $"Token balance: `{tokenBalance.Amount:F5}` (Value: ${tokenBalance.Value:F2})\n" +
$"Above orphaned threshold of `${minOrphanedBalanceValue:F2}`\n" +
$"But no internal position tracked\n" + $"But no internal position tracked\n" +
$"Attempting to recover position from database..."); $"Attempting to recover position from database...");
@@ -413,12 +430,17 @@ public class SpotBot : TradingBotBase
// Update quantity to match actual token balance // Update quantity to match actual token balance
var actualTokenBalance = tokenBalance.Amount; var actualTokenBalance = tokenBalance.Amount;
if (Math.Abs(internalPosition.Open.Quantity - actualTokenBalance) > 0.0001m) var quantityTolerance = internalPosition.Open.Quantity * 0.006m; // 0.6% tolerance for slippage
var quantityDifference = Math.Abs(internalPosition.Open.Quantity - actualTokenBalance);
if (quantityDifference > quantityTolerance)
{ {
await LogDebugAsync( await LogDebugAsync(
$"🔄 Token Balance Mismatch\n" + $"🔄 Token Balance Mismatch\n" +
$"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" + $"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" +
$"Broker Balance: `{actualTokenBalance:F5}`\n" + $"Broker Balance: `{actualTokenBalance:F5}`\n" +
$"Difference: `{quantityDifference:F5}`\n" +
$"Tolerance (0.6%): `{quantityTolerance:F5}`\n" +
$"Updating to match broker balance"); $"Updating to match broker balance");
internalPosition.Open.Quantity = actualTokenBalance; internalPosition.Open.Quantity = actualTokenBalance;
positionForSignal.Open.Quantity = actualTokenBalance; positionForSignal.Open.Quantity = actualTokenBalance;
@@ -478,10 +500,15 @@ public class SpotBot : TradingBotBase
return; return;
} }
await LogDebugAsync( // Position is Filled but no token balance and not found in history
$"⚠️ Position Status Check\n" + // This could be a zombie position - check if token balance exists via direct exchange call
$"Internal position `{internalPosition.Identifier}` shows Filled\n" + await LogWarningAsync(
$"But no token balance found on broker or in history\n" + $"⚠️ Potential Zombie Position Detected\n" +
$"Position: `{internalPosition.Identifier}`\n" +
$"Status: Filled\n" +
$"Token balance: 0\n" +
$"Not found in exchange history\n" +
$"This position may have been closed externally or data is delayed\n" +
$"Will retry verification on next cycle"); $"Will retry verification on next cycle");
} }
} }
@@ -1089,8 +1116,10 @@ public class SpotBot : TradingBotBase
{ {
try try
{ {
// Prevent infinite retry loop - only attempt force close once
// The next bot cycle will handle verification
await LogInformationAsync( await LogInformationAsync(
$"🔄 Force Closing Remaining Balance\n" + $"🔄 Force Closing Remaining Balance (One-Time Attempt)\n" +
$"Position: `{position.Identifier}`\n" + $"Position: `{position.Identifier}`\n" +
$"Ticker: {Config.Ticker}\n" + $"Ticker: {Config.Ticker}\n" +
$"Remaining Balance: `{remainingBalance:F5}`"); $"Remaining Balance: `{remainingBalance:F5}`");
@@ -1127,7 +1156,7 @@ public class SpotBot : TradingBotBase
$"Cleared Balance: `{remainingBalance:F5}`\n" + $"Cleared Balance: `{remainingBalance:F5}`\n" +
$"Close Price: `${currentPrice:F2}`"); $"Close Price: `${currentPrice:F2}`");
// Verify one more time that balance is now cleared // Verify one more time that balance is now cleared (without triggering another force close)
await Task.Delay(2000); // Wait for swap to complete await Task.Delay(2000); // Wait for swap to complete
var finalBalance = await ServiceScopeHelpers.WithScopedService<IExchangeService, Balance?>( var finalBalance = await ServiceScopeHelpers.WithScopedService<IExchangeService, Balance?>(
@@ -1137,15 +1166,16 @@ public class SpotBot : TradingBotBase
if (finalBalance is { Amount: > 0.0001m }) if (finalBalance is { Amount: > 0.0001m })
{ {
await LogWarningAsync( await LogWarningAsync(
$"⚠️ Balance Still Remaining After Retry\n" + $"⚠️ Balance Still Remaining After Force Close Attempt\n" +
$"Position: `{position.Identifier}`\n" + $"Position: `{position.Identifier}`\n" +
$"Remaining: `{finalBalance.Amount:F5}`\n" + $"Remaining: `{finalBalance.Amount:F5}`\n" +
$"Manual intervention may be required"); $"This will be handled on the next bot cycle\n" +
$"Manual intervention may be required if issue persists");
} }
else else
{ {
await LogInformationAsync( await LogInformationAsync(
$"✅ Balance Fully Cleared After Retry\n" + $"✅ Balance Fully Cleared After Force Close\n" +
$"Position: `{position.Identifier}`\n" + $"Position: `{position.Identifier}`\n" +
$"Final Balance: `{finalBalance?.Amount ?? 0:F5}`"); $"Final Balance: `{finalBalance?.Amount ?? 0:F5}`");
} }