Trading bot grain (#33)
* Trading bot Grain * Fix a bit more of the trading bot * Advance on the tradingbot grain * Fix build * Fix db script * Fix user login * Fix a bit backtest * Fix cooldown and backtest * start fixing bot start * Fix startup * Setup local db * Fix build and update candles and scenario * Add bot registry * Add reminder * Updateing the grains * fix bootstraping * Save stats on tick * Save bot data every tick * Fix serialization * fix save bot stats * Fix get candles * use dict instead of list for position * Switch hashset to dict * Fix a bit * Fix bot launch and bot view * add migrations * Remove the tolist * Add agent grain * Save agent summary * clean * Add save bot * Update get bots * Add get bots * Fix stop/restart * fix Update config * Update scanner table on new backtest saved * Fix backtestRowDetails.tsx * Fix agentIndex * Update agentIndex * Fix more things * Update user cache * Fix * Fix account load/start/restart/run
This commit is contained in:
163
src/Managing.Application/Bots/Grains/AgentGrain.cs
Normal file
163
src/Managing.Application/Bots/Grains/AgentGrain.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Grains;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Bots.Models;
|
||||
using Managing.Domain.Statistics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Bots.Grains;
|
||||
|
||||
public class AgentGrain : Grain, IAgentGrain, IRemindable
|
||||
{
|
||||
private readonly IPersistentState<AgentGrainState> _state;
|
||||
private readonly ILogger<AgentGrain> _logger;
|
||||
private readonly IBotService _botService;
|
||||
private readonly IStatisticService _statisticService;
|
||||
private const string _updateSummaryReminderName = "UpdateAgentSummary";
|
||||
|
||||
public AgentGrain(
|
||||
[PersistentState("agent-state", "agent-store")]
|
||||
IPersistentState<AgentGrainState> state,
|
||||
ILogger<AgentGrain> logger,
|
||||
IBotService botService,
|
||||
IStatisticService statisticService)
|
||||
{
|
||||
_state = state;
|
||||
_logger = logger;
|
||||
_botService = botService;
|
||||
_statisticService = statisticService;
|
||||
}
|
||||
|
||||
public override Task OnActivateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("AgentGrain activated for user {UserId}", this.GetPrimaryKeyLong());
|
||||
return base.OnActivateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(int userId, string agentName)
|
||||
{
|
||||
_state.State.AgentName = agentName;
|
||||
await _state.WriteStateAsync();
|
||||
_logger.LogInformation("Agent {UserId} initialized with name {AgentName}", userId, agentName);
|
||||
await RegisterReminderAsync();
|
||||
}
|
||||
|
||||
private async Task RegisterReminderAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Register a reminder that fires every 5 minutes
|
||||
await this.RegisterOrUpdateReminder(_updateSummaryReminderName, TimeSpan.FromMinutes(5),
|
||||
TimeSpan.FromMinutes(1));
|
||||
_logger.LogInformation("Reminder registered for agent {UserId} to update summary every 5 minutes",
|
||||
this.GetPrimaryKeyLong());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to register reminder for agent {UserId}", this.GetPrimaryKeyLong());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReceiveReminder(string reminderName, TickStatus status)
|
||||
{
|
||||
if (reminderName == _updateSummaryReminderName)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Reminder triggered for agent {UserId} to update summary",
|
||||
this.GetPrimaryKeyLong());
|
||||
await UpdateSummary();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating agent summary from reminder for user {UserId}",
|
||||
this.GetPrimaryKeyLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateSummary()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get all bots for this agent
|
||||
var bots = await _botService.GetBotsByIdsAsync(_state.State.BotIds);
|
||||
|
||||
// Calculate aggregated statistics from bot data
|
||||
var totalPnL = bots.Sum(b => b.Pnl);
|
||||
var totalWins = bots.Sum(b => b.TradeWins);
|
||||
var totalLosses = bots.Sum(b => b.TradeLosses);
|
||||
|
||||
// Calculate ROI based on total volume traded with proper division by zero handling
|
||||
var totalVolume = bots.Sum(b => b.Volume);
|
||||
decimal totalROI;
|
||||
|
||||
if (totalVolume > 0)
|
||||
{
|
||||
totalROI = (totalPnL / totalVolume) * 100;
|
||||
}
|
||||
else if (totalVolume == 0 && totalPnL == 0)
|
||||
{
|
||||
// No trading activity yet
|
||||
totalROI = 0;
|
||||
}
|
||||
else if (totalVolume == 0 && totalPnL != 0)
|
||||
{
|
||||
// Edge case: PnL exists but no volume (shouldn't happen in normal cases)
|
||||
_logger.LogWarning("Agent {UserId} has PnL {PnL} but zero volume", this.GetPrimaryKeyLong(), totalPnL);
|
||||
totalROI = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback for any other edge cases
|
||||
totalROI = 0;
|
||||
}
|
||||
|
||||
// Calculate Runtime based on the farthest date from bot startup times
|
||||
DateTime? runtime = null;
|
||||
if (bots.Any())
|
||||
{
|
||||
runtime = bots.Max(b => b.StartupTime);
|
||||
}
|
||||
|
||||
var summary = new AgentSummary
|
||||
{
|
||||
UserId = (int)this.GetPrimaryKeyLong(),
|
||||
AgentName = _state.State.AgentName,
|
||||
TotalPnL = totalPnL,
|
||||
Wins = totalWins,
|
||||
Losses = totalLosses,
|
||||
TotalROI = totalROI,
|
||||
Runtime = runtime,
|
||||
ActiveStrategiesCount = bots.Count(b => b.Status == BotStatus.Up),
|
||||
TotalVolume = totalVolume,
|
||||
};
|
||||
|
||||
// Save summary to database
|
||||
await _statisticService.SaveOrUpdateAgentSummary(summary);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error calculating agent summary for user {UserId}", this.GetPrimaryKeyLong());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RegisterBotAsync(Guid botId)
|
||||
{
|
||||
if (_state.State.BotIds.Add(botId))
|
||||
{
|
||||
await _state.WriteStateAsync();
|
||||
_logger.LogInformation("Bot {BotId} registered to Agent {UserId}", botId, this.GetPrimaryKeyLong());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnregisterBotAsync(Guid botId)
|
||||
{
|
||||
if (_state.State.BotIds.Remove(botId))
|
||||
{
|
||||
await _state.WriteStateAsync();
|
||||
_logger.LogInformation("Bot {BotId} unregistered from Agent {UserId}", botId, this.GetPrimaryKeyLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user