Add single time swap + fetch balance cache in AgentGrain
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Abstractions.Models;
|
using Managing.Application.Abstractions.Models;
|
||||||
|
using Managing.Application.Bots.Models;
|
||||||
using Orleans.Concurrency;
|
using Orleans.Concurrency;
|
||||||
|
|
||||||
namespace Managing.Application.Abstractions.Grains
|
namespace Managing.Application.Abstractions.Grains
|
||||||
@@ -39,5 +40,14 @@ namespace Managing.Application.Abstractions.Grains
|
|||||||
/// <param name="updateEvent">The update event from the stream</param>]
|
/// <param name="updateEvent">The update event from the stream</param>]
|
||||||
[OneWay]
|
[OneWay]
|
||||||
Task OnAgentSummaryUpdateAsync(AgentSummaryUpdateEvent updateEvent);
|
Task OnAgentSummaryUpdateAsync(AgentSummaryUpdateEvent updateEvent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coordinates ETH balance checking and swapping for all bots under this agent.
|
||||||
|
/// Uses cached balance data to reduce external API calls and ensures only one swap operation happens at a time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestingBotId">The bot requesting the ETH balance check</param>
|
||||||
|
/// <param name="accountName">The account name to check balances for</param>
|
||||||
|
/// <returns>BalanceCheckResult indicating the status and reason for any failure</returns>
|
||||||
|
Task<BalanceCheckResult> CheckAndEnsureEthBalanceAsync(Guid requestingBotId, string accountName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ using Managing.Application.Abstractions.Models;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Bots.Models;
|
using Managing.Application.Bots.Models;
|
||||||
using Managing.Application.Orleans;
|
using Managing.Application.Orleans;
|
||||||
|
using Managing.Common;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -233,4 +234,242 @@ public class AgentGrain : Grain, IAgentGrain
|
|||||||
await UpdateSummary();
|
await UpdateSummary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<BalanceCheckResult> CheckAndEnsureEthBalanceAsync(Guid requestingBotId, string accountName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if a swap is already in progress
|
||||||
|
if (_state.State.IsSwapInProgress)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Swap already in progress for agent {UserId}, bot {RequestingBotId} will wait",
|
||||||
|
this.GetPrimaryKeyLong(), requestingBotId);
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.SwapInProgress,
|
||||||
|
Message = "Swap operation already in progress",
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cooldown period (5 minutes between swaps)
|
||||||
|
if (_state.State.LastSwapTime.HasValue &&
|
||||||
|
DateTime.UtcNow - _state.State.LastSwapTime.Value < TimeSpan.FromMinutes(5))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Swap cooldown period active for agent {UserId}, bot {RequestingBotId} will wait",
|
||||||
|
this.GetPrimaryKeyLong(), requestingBotId);
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.SwapCooldownActive,
|
||||||
|
Message = "Swap cooldown period active",
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or refresh cached balance data
|
||||||
|
var balanceData = await GetOrRefreshBalanceDataAsync(accountName);
|
||||||
|
if (balanceData == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Failed to get balance data for account {AccountName}, user {UserId}",
|
||||||
|
accountName, this.GetPrimaryKeyLong());
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.BalanceFetchError,
|
||||||
|
Message = "Failed to fetch balance data",
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Agent {UserId} balance check - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (cached: {IsCached})",
|
||||||
|
this.GetPrimaryKeyLong(), balanceData.EthValueInUsd, balanceData.UsdcValue,
|
||||||
|
_state.State.CachedBalanceData?.IsValid == true);
|
||||||
|
|
||||||
|
// Check USDC minimum balance first (this will stop the bot if insufficient)
|
||||||
|
if (balanceData.UsdcValue < Constants.GMX.Config.MinimumPositionAmount)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("USDC balance is below minimum required amount - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (minimum: {Minimum})",
|
||||||
|
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.MinimumPositionAmount);
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.InsufficientUsdcBelowMinimum,
|
||||||
|
Message = $"USDC balance below minimum required amount ({Constants.GMX.Config.MinimumPositionAmount} USD)",
|
||||||
|
ShouldStopBot = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ETH balance is sufficient, return success
|
||||||
|
if (balanceData.EthValueInUsd >= Constants.GMX.Config.MinimumEthBalance)
|
||||||
|
{
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = true,
|
||||||
|
FailureReason = BalanceCheckFailureReason.None,
|
||||||
|
Message = "Balance check successful",
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have enough USDC for swap (need at least 5 USD for swap)
|
||||||
|
if (balanceData.UsdcValue < (Constants.GMX.Config.MinimumPositionAmount + Constants.GMX.Config.AutoSwapAmount) )
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Insufficient USDC balance for swap - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (need {AutoSwapAmount} USD for swap)",
|
||||||
|
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.AutoSwapAmount);
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.InsufficientUsdcForSwap,
|
||||||
|
Message = $"Insufficient USDC balance for swap (need {Constants.GMX.Config.AutoSwapAmount} USD)",
|
||||||
|
ShouldStopBot = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark swap as in progress
|
||||||
|
_state.State.IsSwapInProgress = true;
|
||||||
|
await _state.WriteStateAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Initiating USDC to ETH swap for agent {UserId} - swapping 5 USDC", this.GetPrimaryKeyLong());
|
||||||
|
|
||||||
|
// Get user for the swap
|
||||||
|
var userId = (int)this.GetPrimaryKeyLong();
|
||||||
|
var user = await _userService.GetUserByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("User {UserId} not found for swap operation", userId);
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.BalanceFetchError,
|
||||||
|
Message = "User not found for swap operation",
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the swap
|
||||||
|
var swapInfo = await _accountService.SwapGmxTokensAsync(user, accountName,
|
||||||
|
Ticker.USDC, Ticker.ETH, 5);
|
||||||
|
|
||||||
|
if (swapInfo.Success)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Successfully swapped 5 USDC to ETH for agent {UserId}, transaction hash: {Hash}",
|
||||||
|
userId, swapInfo.Hash);
|
||||||
|
|
||||||
|
// Update last swap time and invalidate cache
|
||||||
|
_state.State.LastSwapTime = DateTime.UtcNow;
|
||||||
|
_state.State.CachedBalanceData = null; // Invalidate cache after successful swap
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = true,
|
||||||
|
FailureReason = BalanceCheckFailureReason.None,
|
||||||
|
Message = "Swap completed successfully",
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("Failed to swap USDC to ETH for agent {UserId}: {Error}",
|
||||||
|
userId, swapInfo.Error ?? swapInfo.Message);
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.SwapExecutionError,
|
||||||
|
Message = swapInfo.Error ?? swapInfo.Message ?? "Swap execution failed",
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Always clear the swap in progress flag
|
||||||
|
_state.State.IsSwapInProgress = false;
|
||||||
|
await _state.WriteStateAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error checking/ensuring ETH balance for agent {UserId}, bot {RequestingBotId}",
|
||||||
|
this.GetPrimaryKeyLong(), requestingBotId);
|
||||||
|
|
||||||
|
// Clear swap in progress flag on error
|
||||||
|
_state.State.IsSwapInProgress = false;
|
||||||
|
await _state.WriteStateAsync();
|
||||||
|
|
||||||
|
return new BalanceCheckResult
|
||||||
|
{
|
||||||
|
IsSuccessful = false,
|
||||||
|
FailureReason = BalanceCheckFailureReason.BalanceFetchError,
|
||||||
|
Message = ex.Message,
|
||||||
|
ShouldStopBot = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets cached balance data or fetches fresh data if cache is invalid/expired
|
||||||
|
/// </summary>
|
||||||
|
private async Task<CachedBalanceData?> GetOrRefreshBalanceDataAsync(string accountName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if we have valid cached data for the same account
|
||||||
|
if (_state.State.CachedBalanceData?.IsValid == true &&
|
||||||
|
_state.State.CachedBalanceData.AccountName == accountName)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Using cached balance data for account {AccountName}", accountName);
|
||||||
|
return _state.State.CachedBalanceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch fresh balance data
|
||||||
|
_logger.LogInformation("Fetching fresh balance data for account {AccountName}", accountName);
|
||||||
|
|
||||||
|
var userId = (int)this.GetPrimaryKeyLong();
|
||||||
|
var user = await _userService.GetUserByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("User {UserId} not found for balance check", userId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userAccounts = await _accountService.GetAccountsByUserAsync(user, hideSecrets: true, true);
|
||||||
|
var account = userAccounts.FirstOrDefault(a => a.Name == accountName);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Account {AccountName} not found for user {UserId}", accountName, userId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current balances
|
||||||
|
var balances = await _exchangeService.GetBalances(account);
|
||||||
|
var ethBalance = balances.FirstOrDefault(b => b.TokenName?.ToUpper() == "ETH");
|
||||||
|
var usdcBalance = balances.FirstOrDefault(b => b.TokenName?.ToUpper() == "USDC");
|
||||||
|
|
||||||
|
var ethValueInUsd = ethBalance?.Amount * ethBalance?.Price ?? 0;
|
||||||
|
var usdcValue = usdcBalance?.Value ?? 0;
|
||||||
|
|
||||||
|
// Cache the balance data
|
||||||
|
var balanceData = new CachedBalanceData
|
||||||
|
{
|
||||||
|
LastFetched = DateTime.UtcNow,
|
||||||
|
AccountName = accountName,
|
||||||
|
EthValueInUsd = ethValueInUsd,
|
||||||
|
UsdcValue = usdcValue
|
||||||
|
};
|
||||||
|
|
||||||
|
_state.State.CachedBalanceData = balanceData;
|
||||||
|
await _state.WriteStateAsync();
|
||||||
|
|
||||||
|
return balanceData;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error fetching balance data for account {AccountName}, user {UserId}",
|
||||||
|
accountName, this.GetPrimaryKeyLong());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ using Managing.Application.Abstractions.Grains;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Orleans;
|
using Managing.Application.Orleans;
|
||||||
using Managing.Application.Shared;
|
using Managing.Application.Shared;
|
||||||
using Managing.Common;
|
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
@@ -109,13 +108,15 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
// Registry says stopped, but check database to see if it should be running
|
// Registry says stopped, but check database to see if it should be running
|
||||||
var databaseStatus = await GetDatabaseBotStatus(botId);
|
var databaseStatus = await GetDatabaseBotStatus(botId);
|
||||||
|
|
||||||
_logger.LogInformation("LiveTradingBotGrain {GrainId} registry: {RegistryStatus}, database: {DatabaseStatus}",
|
_logger.LogInformation(
|
||||||
|
"LiveTradingBotGrain {GrainId} registry: {RegistryStatus}, database: {DatabaseStatus}",
|
||||||
botId, registryStatus, databaseStatus);
|
botId, registryStatus, databaseStatus);
|
||||||
|
|
||||||
if (databaseStatus == BotStatus.Running)
|
if (databaseStatus == BotStatus.Running)
|
||||||
{
|
{
|
||||||
// Database says running but registry says stopped - trust database
|
// Database says running but registry says stopped - trust database
|
||||||
_logger.LogWarning("Status mismatch detected for bot {BotId}. Registry: {RegistryStatus}, Database: {DatabaseStatus}. Trusting database and updating registry.",
|
_logger.LogWarning(
|
||||||
|
"Status mismatch detected for bot {BotId}. Registry: {RegistryStatus}, Database: {DatabaseStatus}. Trusting database and updating registry.",
|
||||||
botId, registryStatus, databaseStatus);
|
botId, registryStatus, databaseStatus);
|
||||||
|
|
||||||
// Update registry to match database (source of truth)
|
// Update registry to match database (source of truth)
|
||||||
@@ -345,68 +346,40 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check broker balance before running
|
// Use coordinated balance checking and swap management through AgentGrain
|
||||||
var balances = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Balance>>(_scopeFactory,
|
|
||||||
async exchangeService => { return await exchangeService.GetBalances(_tradingBot.Account, false); });
|
|
||||||
|
|
||||||
var usdcBalance = balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString());
|
|
||||||
var ethBalance = balances.FirstOrDefault(b => b.TokenName == Ticker.ETH.ToString());
|
|
||||||
|
|
||||||
// Check USDC balance first
|
|
||||||
if (usdcBalance?.Value < Constants.GMX.Config.MinimumPositionAmount)
|
|
||||||
{
|
|
||||||
await _tradingBot.LogWarning(
|
|
||||||
$"USDC balance is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {usdcBalance?.Value:F2}). Stopping bot {_tradingBot.Identifier}.");
|
|
||||||
|
|
||||||
await StopAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check ETH balance and perform automatic swap if needed
|
|
||||||
var ethValueInUsd = ethBalance?.Value * ethBalance?.Price ?? 0;
|
|
||||||
if (ethValueInUsd < 2) // ETH balance below 2 USD
|
|
||||||
{
|
|
||||||
await _tradingBot.LogWarning(
|
|
||||||
$"ETH balance is below 2 USD (actual: {ethValueInUsd:F2}). Attempting to swap USDC to ETH.");
|
|
||||||
|
|
||||||
// Check if we have enough USDC for the swap
|
|
||||||
if (usdcBalance?.Value >= 5) // Need at least 5 USD for swap
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var swapInfo = await ServiceScopeHelpers.WithScopedService<IAccountService, SwapInfos>(
|
var agentGrain = GrainFactory.GetGrain<IAgentGrain>(_state.State.User.Id);
|
||||||
_scopeFactory,
|
var balanceCheckResult = await agentGrain.CheckAndEnsureEthBalanceAsync(_state.State.Identifier, _tradingBot.Account.Name);
|
||||||
async accountService =>
|
|
||||||
{
|
|
||||||
return await accountService.SwapGmxTokensAsync(_state.State.User,
|
|
||||||
_tradingBot.Account.Name, Ticker.USDC, Ticker.ETH, 5);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (swapInfo.Success)
|
if (!balanceCheckResult.IsSuccessful)
|
||||||
{
|
{
|
||||||
await NotifyUserAboutSwap(true, 5, swapInfo.Hash);
|
// Log the specific reason for the failure
|
||||||
|
await _tradingBot.LogWarning($"Balance check failed: {balanceCheckResult.Message} (Reason: {balanceCheckResult.FailureReason})");
|
||||||
|
|
||||||
|
// Check if the bot should stop due to this failure
|
||||||
|
if (balanceCheckResult.ShouldStopBot)
|
||||||
|
{
|
||||||
|
await _tradingBot.LogWarning($"Stopping bot due to balance check failure: {balanceCheckResult.Message}");
|
||||||
|
await StopAsync();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await NotifyUserAboutSwap(false, 5, null, swapInfo.Error ?? swapInfo.Message);
|
// Skip this execution cycle but continue running
|
||||||
await StopAsync();
|
await _tradingBot.LogInformation("Skipping this execution cycle due to balance check failure.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _tradingBot.LogInformation($"Balance check successful: {balanceCheckResult.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await NotifyUserAboutSwap(false, 5, null, ex.Message);
|
_logger.LogError(ex, "Error during coordinated balance check for bot {BotId}", _state.State.Identifier);
|
||||||
}
|
// Continue execution to avoid stopping the bot due to coordination errors
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Both USDC and ETH are low - stop the strategy
|
|
||||||
await _tradingBot.LogWarning(
|
|
||||||
$"Both USDC ({usdcBalance?.Value:F2}) and ETH ({ethValueInUsd:F2}) balances are low. Stopping bot {_tradingBot.Identifier}.");
|
|
||||||
|
|
||||||
await StopAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the bot's Run method
|
// Execute the bot's Run method
|
||||||
|
|||||||
@@ -4,5 +4,91 @@ namespace Managing.Application.Bots.Models
|
|||||||
{
|
{
|
||||||
public string AgentName { get; set; }
|
public string AgentName { get; set; }
|
||||||
public HashSet<Guid> BotIds { get; set; } = new HashSet<Guid>();
|
public HashSet<Guid> BotIds { get; set; } = new HashSet<Guid>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks if a swap operation is currently in progress to prevent multiple simultaneous swaps
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSwapInProgress { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timestamp of the last swap operation to implement cooldown period
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? LastSwapTime { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached balance data to reduce external API calls
|
||||||
|
/// </summary>
|
||||||
|
public CachedBalanceData? CachedBalanceData { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached balance data to avoid repeated external API calls
|
||||||
|
/// </summary>
|
||||||
|
public class CachedBalanceData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When the balance was last fetched
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastFetched { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The account name this balance data is for
|
||||||
|
/// </summary>
|
||||||
|
public string AccountName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ETH balance in USD
|
||||||
|
/// </summary>
|
||||||
|
public decimal EthValueInUsd { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// USDC balance value
|
||||||
|
/// </summary>
|
||||||
|
public decimal UsdcValue { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the cached data is still valid (less than 1 minute old)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsValid => DateTime.UtcNow - LastFetched < TimeSpan.FromMinutes(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of a balance check operation
|
||||||
|
/// </summary>
|
||||||
|
public class BalanceCheckResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the balance check was successful
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuccessful { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The reason for failure if not successful
|
||||||
|
/// </summary>
|
||||||
|
public BalanceCheckFailureReason FailureReason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Additional details about the result
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the bot should stop due to this result
|
||||||
|
/// </summary>
|
||||||
|
public bool ShouldStopBot { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reasons why a balance check might fail
|
||||||
|
/// </summary>
|
||||||
|
public enum BalanceCheckFailureReason
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
InsufficientUsdcBelowMinimum,
|
||||||
|
InsufficientUsdcForSwap,
|
||||||
|
SwapInProgress,
|
||||||
|
SwapCooldownActive,
|
||||||
|
BalanceFetchError,
|
||||||
|
SwapExecutionError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,9 @@ namespace Managing.Common
|
|||||||
public const int USD = 30;
|
public const int USD = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
public const decimal MinimumPositionAmount = 3m;
|
public const decimal MinimumPositionAmount = 5m;
|
||||||
|
public const decimal MinimumEthBalance = 2m;
|
||||||
|
public const decimal AutoSwapAmount = 3m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TokenAddress
|
public class TokenAddress
|
||||||
|
|||||||
Reference in New Issue
Block a user