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:
Oda
2025-08-04 23:07:06 +02:00
committed by GitHub
parent cd378587aa
commit 082ae8714b
215 changed files with 9562 additions and 14028 deletions

View 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());
}
}
}