Adapt spot for recovery

This commit is contained in:
2025-12-05 15:24:51 +07:00
parent 78edd850fe
commit 15d8b38d8b
4 changed files with 46 additions and 82 deletions

View File

@@ -209,37 +209,6 @@ public class FuturesBot : TradingBotBase, ITradingBot
}); });
} }
protected override async Task VerifyAndUpdateBalanceAsync()
{
// Live trading: verify real USDC balance
if (Config.TradingType == TradingType.BacktestFutures) return;
try
{
var actualBalance = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(_scopeFactory,
async exchangeService =>
{
var balances = await exchangeService.GetBalances(Account);
var usdcBalance = balances.FirstOrDefault(b => b.TokenName?.ToUpper() == "USDC");
return usdcBalance?.Amount ?? 0;
});
if (actualBalance < Config.BotTradingBalance)
{
Logger.LogWarning(
"Actual USDC balance ({ActualBalance:F2}) is less than configured balance ({ConfiguredBalance:F2}). Updating configuration.",
actualBalance, Config.BotTradingBalance);
var newConfig = Config;
newConfig.BotTradingBalance = actualBalance;
await UpdateConfiguration(newConfig);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error verifying and updating balance");
}
}
protected override async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal, protected override async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal,
List<Position> brokerPositions) List<Position> brokerPositions)

View File

@@ -233,37 +233,6 @@ public class SpotBot : TradingBotBase, ITradingBot
}); });
} }
protected override async Task VerifyAndUpdateBalanceAsync()
{
// Live trading: verify real USDC balance for spot trading
if (Config.TradingType == TradingType.BacktestSpot) return;
try
{
var actualBalance = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(_scopeFactory,
async exchangeService =>
{
var balances = await exchangeService.GetBalances(Account);
var usdcBalance = balances.FirstOrDefault(b => b.TokenName?.ToUpper() == "USDC");
return usdcBalance?.Amount ?? 0;
});
if (actualBalance < Config.BotTradingBalance)
{
Logger.LogWarning(
"Actual USDC balance ({ActualBalance:F2}) is less than configured balance ({ConfiguredBalance:F2}). Updating configuration.",
actualBalance, Config.BotTradingBalance);
var newConfig = Config;
newConfig.BotTradingBalance = actualBalance;
await UpdateConfiguration(newConfig);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error verifying and updating balance");
}
}
protected override async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal, protected override async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal,
List<Position> brokerPositions) List<Position> brokerPositions)
@@ -282,7 +251,31 @@ public class SpotBot : TradingBotBase, ITradingBot
if (tokenBalance != null && tokenBalance.Amount > 0) if (tokenBalance != null && tokenBalance.Amount > 0)
{ {
// Token balance exists - verify position is filled // Verify that the token balance matches the position amount with 0.1% tolerance
var positionQuantity = internalPosition.Open.Quantity;
var tokenBalanceAmount = tokenBalance.Amount;
if (positionQuantity > 0)
{
var tolerance = positionQuantity * 0.001m; // 0.1% tolerance
var difference = Math.Abs(tokenBalanceAmount - positionQuantity);
if (difference > tolerance)
{
await LogWarningAsync(
$"⚠️ Token Balance Mismatch - Position Verification Failed\n" +
$"Position: `{internalPosition.Identifier}`\n" +
$"Position Quantity: `{positionQuantity:F5}`\n" +
$"Token Balance: `{tokenBalanceAmount:F5}`\n" +
$"Difference: `{difference:F5}`\n" +
$"Tolerance (0.1%): `{tolerance:F5}`\n" +
$"Token balance does not match position amount within tolerance\n" +
$"Skipping position synchronization");
return; // Skip processing if amounts don't match
}
}
// Token balance exists and matches position - verify position is filled
var previousPositionStatus = internalPosition.Status; var previousPositionStatus = internalPosition.Status;
// Position found on broker (token balance exists), means the position is filled // Position found on broker (token balance exists), means the position is filled

View File

@@ -168,7 +168,7 @@ public abstract class TradingBotBase : ITradingBot
public async Task LoadAccount() public async Task LoadAccount()
{ {
if (Config.TradingType == TradingType.BacktestFutures) return; if (TradingBox.IsBacktestTrading(Config.TradingType)) return;
await ServiceScopeHelpers.WithScopedService<IAccountService>(_scopeFactory, async accountService => await ServiceScopeHelpers.WithScopedService<IAccountService>(_scopeFactory, async accountService =>
{ {
var account = await accountService.GetAccountByAccountName(Config.AccountName, false, false); var account = await accountService.GetAccountByAccountName(Config.AccountName, false, false);
@@ -182,7 +182,7 @@ public abstract class TradingBotBase : ITradingBot
/// </summary> /// </summary>
public async Task VerifyAndUpdateBalance() public async Task VerifyAndUpdateBalance()
{ {
if (Config.TradingType == TradingType.BacktestFutures) return; if (TradingBox.IsBacktestTrading(Config.TradingType)) return;
if (Account == null) if (Account == null)
{ {
Logger.LogWarning("Cannot verify balance: Account is null"); Logger.LogWarning("Cannot verify balance: Account is null");
@@ -367,12 +367,8 @@ public abstract class TradingBotBase : ITradingBot
return; return;
// First, process all existing positions that are not finished // First, process all existing positions that are not finished
// Optimized: Inline the filter to avoid LINQ overhead foreach (var position in Positions.Values.Where(p => !p.IsFinished()))
foreach (var position in Positions.Values)
{ {
if (position.IsFinished())
continue;
var signalForPosition = Signals[position.SignalIdentifier]; var signalForPosition = Signals[position.SignalIdentifier];
if (signalForPosition == null) if (signalForPosition == null)
{ {
@@ -436,7 +432,7 @@ public abstract class TradingBotBase : ITradingBot
protected void UpdateWalletBalances() protected void UpdateWalletBalances()
{ {
var date = Config.TradingType == TradingType.BacktestFutures var date = TradingBox.IsBacktestTrading(Config.TradingType)
? LastCandle?.Date ?? DateTime.UtcNow ? LastCandle?.Date ?? DateTime.UtcNow
: DateTime.UtcNow; : DateTime.UtcNow;
@@ -485,13 +481,13 @@ public abstract class TradingBotBase : ITradingBot
Candle lastCandle = null; Candle lastCandle = null;
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService => await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
{ {
lastCandle = Config.TradingType == TradingType.BacktestFutures lastCandle = TradingBox.IsBacktestTrading(Config.TradingType)
? LastCandle ? LastCandle
: await exchangeService.GetCandle(Account, Config.Ticker, : await exchangeService.GetCandle(Account, Config.Ticker,
DateTime.UtcNow); DateTime.UtcNow);
}); });
var currentTime = Config.TradingType == TradingType.BacktestFutures ? lastCandle.Date : DateTime.UtcNow; var currentTime = TradingBox.IsBacktestTrading(Config.TradingType) ? lastCandle.Date : DateTime.UtcNow;
var currentPnl = positionForSignal.ProfitAndLoss?.Net ?? 0; var currentPnl = positionForSignal.ProfitAndLoss?.Net ?? 0;
var pnlPercentage = TradingBox.CalculatePnLPercentage(currentPnl, positionForSignal.Open.Price, var pnlPercentage = TradingBox.CalculatePnLPercentage(currentPnl, positionForSignal.Open.Price,
positionForSignal.Open.Quantity); positionForSignal.Open.Quantity);
@@ -1020,7 +1016,7 @@ public abstract class TradingBotBase : ITradingBot
signal.Date, signal.Date,
Account.User, Account.User,
Config.BotTradingBalance, Config.BotTradingBalance,
Config.TradingType == TradingType.BacktestFutures, TradingBox.IsBacktestTrading(Config.TradingType),
lastPrice, lastPrice,
signalIdentifier: signal.Identifier, signalIdentifier: signal.Identifier,
initiatorIdentifier: Identifier, initiatorIdentifier: Identifier,
@@ -1245,7 +1241,7 @@ public abstract class TradingBotBase : ITradingBot
// Update the last position closing time for cooldown period tracking // Update the last position closing time for cooldown period tracking
// Only update if position was actually filled // Only update if position was actually filled
LastPositionClosingTime = Config.TradingType == TradingType.BacktestFutures LastPositionClosingTime = TradingBox.IsBacktestTrading(Config.TradingType)
? currentCandle.Date ? currentCandle.Date
: DateTime.UtcNow; : DateTime.UtcNow;
} }
@@ -1498,7 +1494,7 @@ public abstract class TradingBotBase : ITradingBot
signal, signal,
currentPrice, currentPrice,
Config, Config,
Config.TradingType == TradingType.BacktestFutures); TradingBox.IsBacktestTrading(Config.TradingType));
if (signalValidationResult.Confidence == Confidence.None || if (signalValidationResult.Confidence == Confidence.None ||
signalValidationResult.Confidence == Confidence.Low || signalValidationResult.Confidence == Confidence.Low ||
@@ -1859,7 +1855,7 @@ public abstract class TradingBotBase : ITradingBot
// Calculate cooldown end time based on last position closing time // Calculate cooldown end time based on last position closing time
var cooldownEndTime = var cooldownEndTime =
TradingBox.CalculateCooldownEndTime(LastPositionClosingTime.Value, Config.Timeframe, Config.CooldownPeriod); TradingBox.CalculateCooldownEndTime(LastPositionClosingTime.Value, Config.Timeframe, Config.CooldownPeriod);
var isInCooldown = (Config.TradingType == TradingType.BacktestFutures ? LastCandle.Date : DateTime.UtcNow) < var isInCooldown = (TradingBox.IsBacktestTrading(Config.TradingType) ? LastCandle.Date : DateTime.UtcNow) <
cooldownEndTime; cooldownEndTime;
if (isInCooldown) if (isInCooldown)
@@ -1917,7 +1913,7 @@ public abstract class TradingBotBase : ITradingBot
private async Task NotifyAgentAndPlatformGrainAsync(NotificationEventType eventType, private async Task NotifyAgentAndPlatformGrainAsync(NotificationEventType eventType,
Position position) Position position)
{ {
if (Config.TradingType == TradingType.BacktestFutures) if (TradingBox.IsBacktestTrading(Config.TradingType))
{ {
return; // Skip notifications for backtest return; // Skip notifications for backtest
} }
@@ -2064,7 +2060,7 @@ public abstract class TradingBotBase : ITradingBot
protected virtual async Task LogInformationAsync(string message) protected virtual async Task LogInformationAsync(string message)
{ {
if (Config.TradingType == TradingType.BacktestFutures) if (TradingBox.IsBacktestTrading(Config.TradingType))
return; return;
Logger.LogInformation(message); Logger.LogInformation(message);
@@ -2081,7 +2077,7 @@ public abstract class TradingBotBase : ITradingBot
protected virtual async Task LogWarningAsync(string message) protected virtual async Task LogWarningAsync(string message)
{ {
if (Config.TradingType == TradingType.BacktestFutures) if (TradingBox.IsBacktestTrading(Config.TradingType))
return; return;
message = $"[{Config.Name}] {message}"; message = $"[{Config.Name}] {message}";
@@ -2098,7 +2094,7 @@ public abstract class TradingBotBase : ITradingBot
protected virtual async Task LogDebugAsync(string message) protected virtual async Task LogDebugAsync(string message)
{ {
if (Config.TradingType == TradingType.BacktestFutures) if (TradingBox.IsBacktestTrading(Config.TradingType))
return; return;
Logger.LogDebug(message); Logger.LogDebug(message);

View File

@@ -1348,9 +1348,15 @@ public static class TradingBox
}; };
} }
public static bool IsBacktestTrading(TradingType tradingType)
{
return !IsLiveTrading(tradingType);
}
public static TradingType GetLiveTradingType(TradingType tradingType) public static TradingType GetLiveTradingType(TradingType tradingType)
{ {
return tradingType switch { return tradingType switch
{
TradingType.BacktestFutures => TradingType.Futures, TradingType.BacktestFutures => TradingType.Futures,
TradingType.BacktestSpot => TradingType.Spot, TradingType.BacktestSpot => TradingType.Spot,
_ => throw new InvalidOperationException($"Unsupported TradingType for live trading: {tradingType}") _ => throw new InvalidOperationException($"Unsupported TradingType for live trading: {tradingType}")