Orlean (#32)
* Start building with orlean * Add missing file * Serialize grain state * Remove grain and proxies * update and add plan * Update a bit * Fix backtest grain * Fix backtest grain * Clean a bit
This commit is contained in:
@@ -4,6 +4,7 @@ using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Domain.Workflows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -18,20 +19,21 @@ namespace Managing.Application.ManageBot
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly IMessengerService _messengerService;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ILogger<TradingBot> _tradingBotLogger;
|
||||
private readonly ILogger<TradingBotBase> _tradingBotLogger;
|
||||
private readonly ITradingService _tradingService;
|
||||
private readonly IMoneyManagementService _moneyManagementService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IBackupBotService _backupBotService;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IGrainFactory _grainFactory;
|
||||
|
||||
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
|
||||
new ConcurrentDictionary<string, BotTaskWrapper>();
|
||||
|
||||
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
|
||||
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
|
||||
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBotBase> tradingBotLogger,
|
||||
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService,
|
||||
IBackupBotService backupBotService, IServiceScopeFactory scopeFactory)
|
||||
IBackupBotService backupBotService, IServiceScopeFactory scopeFactory, IGrainFactory grainFactory)
|
||||
{
|
||||
_botRepository = botRepository;
|
||||
_exchangeService = exchangeService;
|
||||
@@ -43,26 +45,26 @@ namespace Managing.Application.ManageBot
|
||||
_userService = userService;
|
||||
_backupBotService = backupBotService;
|
||||
_scopeFactory = scopeFactory;
|
||||
_grainFactory = grainFactory;
|
||||
}
|
||||
|
||||
public class BotTaskWrapper
|
||||
{
|
||||
public Task Task { get; private set; }
|
||||
public Type BotType { get; private set; }
|
||||
public object BotInstance { get; private set; } // Add this line
|
||||
public object BotInstance { get; private set; }
|
||||
|
||||
public BotTaskWrapper(Task task, Type botType, object botInstance) // Update constructor
|
||||
public BotTaskWrapper(Task task, Type botType, object botInstance)
|
||||
{
|
||||
Task = task;
|
||||
BotType = botType;
|
||||
BotInstance = botInstance; // Set the bot instance
|
||||
BotInstance = botInstance;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSimpleBotToCache(IBot bot)
|
||||
{
|
||||
var botTask =
|
||||
new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); // Pass bot as the instance
|
||||
var botTask = new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot);
|
||||
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
||||
}
|
||||
|
||||
@@ -72,24 +74,34 @@ namespace Managing.Application.ManageBot
|
||||
_botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
|
||||
}
|
||||
|
||||
|
||||
private async Task InitBot(ITradingBot bot, BotBackup backupBot)
|
||||
{
|
||||
var user = await _userService.GetUser(backupBot.User.Name);
|
||||
bot.User = user;
|
||||
// Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
|
||||
bot.LoadBackup(backupBot);
|
||||
try
|
||||
{
|
||||
var user = await _userService.GetUser(backupBot.User.Name);
|
||||
bot.User = user;
|
||||
|
||||
// Load backup data into the bot
|
||||
bot.LoadBackup(backupBot);
|
||||
|
||||
// Only start the bot if the backup status is Up
|
||||
if (backupBot.LastStatus == BotStatus.Up)
|
||||
{
|
||||
// Start the bot asynchronously without waiting for completion
|
||||
_ = Task.Run(() => bot.Start());
|
||||
// Only start the bot if the backup status is Up
|
||||
if (backupBot.LastStatus == BotStatus.Up)
|
||||
{
|
||||
// Start the bot asynchronously without waiting for completion
|
||||
_ = Task.Run(() => bot.Start());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep the bot in Down status if it was originally Down
|
||||
bot.Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Keep the bot in Down status if it was originally Down
|
||||
_tradingBotLogger.LogError(ex, "Error initializing bot {Identifier} from backup", backupBot.Identifier);
|
||||
// Ensure the bot is stopped if initialization fails
|
||||
bot.Stop();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +149,7 @@ namespace Managing.Application.ManageBot
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(scalpingConfig.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
scalpingConfig.Scenario = scenario;
|
||||
scalpingConfig.Scenario = LightScenario.FromScenario(scenario);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -155,6 +167,10 @@ namespace Managing.Application.ManageBot
|
||||
// Ensure critical properties are set correctly for restored bots
|
||||
scalpingConfig.IsForBacktest = false;
|
||||
|
||||
// IMPORTANT: Save the backup to database BEFORE creating the Orleans grain
|
||||
// This ensures the backup exists when the grain tries to serialize it
|
||||
await SaveOrUpdateBotBackup(backupBot.User, backupBot.Identifier, backupBot.LastStatus, backupBot.Data);
|
||||
|
||||
bot = await CreateTradingBot(scalpingConfig);
|
||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||
|
||||
@@ -206,7 +222,7 @@ namespace Managing.Application.ManageBot
|
||||
if (botWrapper.BotInstance is IBot bot)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
|
||||
bot.Stop());
|
||||
|
||||
var stopMessage = $"🛑 **Bot Stopped**\n\n" +
|
||||
$"🎯 **Agent:** {bot.User.AgentName}\n" +
|
||||
@@ -231,7 +247,7 @@ namespace Managing.Application.ManageBot
|
||||
if (botWrapper.BotInstance is IBot bot)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
|
||||
bot.Stop());
|
||||
|
||||
var deleteMessage = $"🗑️ **Bot Deleted**\n\n" +
|
||||
$"🎯 **Agent:** {bot.User.AgentName}\n" +
|
||||
@@ -306,7 +322,7 @@ namespace Managing.Application.ManageBot
|
||||
public async Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig)
|
||||
{
|
||||
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
|
||||
botTaskWrapper.BotInstance is TradingBot tradingBot)
|
||||
botTaskWrapper.BotInstance is TradingBotBase tradingBot)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
|
||||
@@ -314,7 +330,7 @@ namespace Managing.Application.ManageBot
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(newConfig.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
newConfig.Scenario = scenario;
|
||||
newConfig.Scenario = LightScenario.FromScenario(scenario);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -370,7 +386,6 @@ namespace Managing.Application.ManageBot
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public async Task<ITradingBot> CreateTradingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
@@ -379,7 +394,7 @@ namespace Managing.Application.ManageBot
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
config.Scenario = LightScenario.FromScenario(scenario);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -392,7 +407,15 @@ namespace Managing.Application.ManageBot
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||
// For now, use TradingBot for both live trading and backtesting
|
||||
// TODO: Implement Orleans grain for live trading when ready
|
||||
if (!config.IsForBacktest)
|
||||
{
|
||||
// Ensure critical properties are set correctly for live trading
|
||||
config.IsForBacktest = false;
|
||||
}
|
||||
|
||||
return new TradingBotBase(_tradingBotLogger, _scopeFactory, config);
|
||||
}
|
||||
|
||||
public async Task<ITradingBot> CreateBacktestTradingBot(TradingBotConfig config)
|
||||
@@ -403,7 +426,7 @@ namespace Managing.Application.ManageBot
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
config.Scenario = LightScenario.FromScenario(scenario);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -417,109 +440,7 @@ namespace Managing.Application.ManageBot
|
||||
}
|
||||
|
||||
config.IsForBacktest = true;
|
||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||
}
|
||||
|
||||
public async Task<ITradingBot> CreateScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.FlipPosition = false;
|
||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||
}
|
||||
|
||||
public async Task<ITradingBot> CreateBacktestScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.IsForBacktest = true;
|
||||
config.FlipPosition = false;
|
||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||
}
|
||||
|
||||
public async Task<ITradingBot> CreateFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.FlipPosition = true;
|
||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||
}
|
||||
|
||||
public async Task<ITradingBot> CreateBacktestFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = await _tradingService.GetScenarioByNameAsync(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.IsForBacktest = true;
|
||||
config.FlipPosition = true;
|
||||
return new TradingBot(_tradingBotLogger, _scopeFactory, config);
|
||||
return new TradingBotBase(_tradingBotLogger, _scopeFactory, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
||||
|
||||
// Try to get the active bot multiple times to ensure it's properly started
|
||||
int attempts = 0;
|
||||
const int maxAttempts = 5;
|
||||
const int maxAttempts = 2;
|
||||
|
||||
while (attempts < maxAttempts)
|
||||
{
|
||||
@@ -58,7 +58,8 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
||||
if (backupBot.LastStatus == BotStatus.Down)
|
||||
{
|
||||
result[activeBot.Identifier] = BotStatus.Down;
|
||||
_logger.LogInformation("Backup bot {Identifier} loaded but kept in Down status as it was originally Down.",
|
||||
_logger.LogInformation(
|
||||
"Backup bot {Identifier} loaded but kept in Down status as it was originally Down.",
|
||||
backupBot.Identifier);
|
||||
}
|
||||
else
|
||||
@@ -68,6 +69,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
||||
_logger.LogInformation("Backup bot {Identifier} started successfully.",
|
||||
backupBot.Identifier);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user