Update pricing timing

This commit is contained in:
2025-09-14 22:27:54 +07:00
parent daeb26375b
commit 2847778c7c
10 changed files with 145 additions and 351 deletions

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Application.Shared;
using Managing.Common;
using Managing.Core;
using Managing.Domain.Accounts;
@@ -120,15 +121,15 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
// Set startup time when bot actually starts running
_state.State.StartupTime = DateTime.UtcNow;
await _state.WriteStateAsync();
// Start the in-memory timer and persistent reminder
RegisterAndStartTimer();
await RegisterReminder();
// Update both database and registry status
await SaveBotAsync(BotStatus.Running);
await UpdateBotRegistryStatus(BotStatus.Running);
_logger.LogInformation("LiveTradingBotGrain {GrainId} resumed successfully", this.GetPrimaryKey());
}
catch (Exception ex)
@@ -184,12 +185,14 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
if (_timer != null) return;
var botOptions = GrainHelpers.GetDynamicRandomizedTimerOptions(TimeSpan.FromMinutes(1), 20);
_timer = this.RegisterGrainTimer(
async _ => await ExecuteBotCycle(),
new GrainTimerCreationOptions
{
Period = TimeSpan.FromMinutes(1),
DueTime = TimeSpan.FromMinutes(1),
Period = botOptions.period,
DueTime = botOptions.dueTime,
KeepAlive = true
});
}
@@ -294,21 +297,18 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
}
// Check broker balance before running
var balances = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Balance>>(_scopeFactory, async exchangeService =>
{
return await exchangeService.GetBalances(_tradingBot.Account, false);
});
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;
}
@@ -319,16 +319,19 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
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
{
var swapInfo = await ServiceScopeHelpers.WithScopedService<IAccountService, SwapInfos>(_scopeFactory, async accountService =>
{
return await accountService.SwapGmxTokensAsync(_state.State.User, _tradingBot.Account.Name, Ticker.USDC, Ticker.ETH, 5);
});
var swapInfo = await ServiceScopeHelpers.WithScopedService<IAccountService, SwapInfos>(
_scopeFactory,
async accountService =>
{
return await accountService.SwapGmxTokensAsync(_state.State.User,
_tradingBot.Account.Name, Ticker.USDC, Ticker.ETH, 5);
});
if (swapInfo.Success)
{
@@ -351,7 +354,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
// 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;
}
@@ -395,7 +398,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
_logger.LogInformation("LastCandle is null, loading latest candle data for manual position opening");
await _tradingBot.LoadLastCandle();
// Sync the loaded candle to grain state
SyncStateFromBase();
await _state.WriteStateAsync();
@@ -480,7 +483,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
// For non-running bots, just update the configuration
_state.State.Config = newConfig;
await _state.WriteStateAsync();
var botRegistry = GrainFactory.GetGrain<ILiveBotRegistryGrain>(0);
var status = await botRegistry.GetBotStatus(this.GetPrimaryKey());
await SaveBotAsync(status);
@@ -507,6 +510,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
throw new InvalidOperationException("Bot is not running - cannot get account information");
}
return Task.FromResult(_tradingBot.Account);
}
@@ -543,21 +547,21 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
public async Task RestartAsync()
{
_logger.LogInformation("Restarting LiveTradingBotGrain {GrainId}", this.GetPrimaryKey());
try
{
await StopAsync();
// Add a small delay to ensure stop operations complete
await Task.Delay(100);
await StartAsync();
// Verify the restart was successful
var botRegistry = GrainFactory.GetGrain<ILiveBotRegistryGrain>(0);
var finalStatus = await botRegistry.GetBotStatus(this.GetPrimaryKey());
_logger.LogInformation("LiveTradingBotGrain {GrainId} restart completed with final status: {Status}",
_logger.LogInformation("LiveTradingBotGrain {GrainId} restart completed with final status: {Status}",
this.GetPrimaryKey(), finalStatus);
}
catch (Exception ex)
@@ -605,39 +609,42 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
const int maxRetries = 3;
var botId = this.GetPrimaryKey();
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
var botRegistry = GrainFactory.GetGrain<ILiveBotRegistryGrain>(0);
await botRegistry.UpdateBotStatus(botId, status);
// Verify the update was successful
var actualStatus = await botRegistry.GetBotStatus(botId);
if (actualStatus == status)
{
_logger.LogDebug("Bot {BotId} status successfully updated to {Status} in BotRegistry (attempt {Attempt})",
_logger.LogDebug(
"Bot {BotId} status successfully updated to {Status} in BotRegistry (attempt {Attempt})",
botId, status, attempt);
return;
}
else
{
_logger.LogWarning("Bot {BotId} status update verification failed. Expected: {Expected}, Actual: {Actual} (attempt {Attempt})",
_logger.LogWarning(
"Bot {BotId} status update verification failed. Expected: {Expected}, Actual: {Actual} (attempt {Attempt})",
botId, status, actualStatus, attempt);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to update bot {BotId} status to {Status} in BotRegistry (attempt {Attempt})",
_logger.LogError(ex,
"Failed to update bot {BotId} status to {Status} in BotRegistry (attempt {Attempt})",
botId, status, attempt);
if (attempt == maxRetries)
{
throw;
}
}
// Wait before retry
if (attempt < maxRetries)
{
@@ -734,7 +741,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{
_logger.LogDebug(
"Successfully saved bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}, Long={LongPositions}, Short={ShortPositions}",
_state.State.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees, bot.LongPositionCount, bot.ShortPositionCount);
_state.State.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees,
bot.LongPositionCount, bot.ShortPositionCount);
}
else
{
@@ -750,7 +758,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
/// <summary>
/// Notifies the user about swap operations via webhook/telegram
/// </summary>
private async Task NotifyUserAboutSwap(bool isSuccess, decimal amount, string? transactionHash, string? errorMessage = null)
private async Task NotifyUserAboutSwap(bool isSuccess, decimal amount, string? transactionHash,
string? errorMessage = null)
{
try
{
@@ -769,10 +778,11 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
$"⏰ **Time:** {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC";
// Send notification via webhook service
await ServiceScopeHelpers.WithScopedService<IWebhookService>(_scopeFactory, async webhookService =>
{
await webhookService.SendMessage(message, _state.State.User?.TelegramChannel);
});
await ServiceScopeHelpers.WithScopedService<IWebhookService>(_scopeFactory,
async webhookService =>
{
await webhookService.SendMessage(message, _state.State.User?.TelegramChannel);
});
}
catch (Exception ex)
{