diff --git a/assets/Todo-v2.md b/assets/Todo-v2.md index 8f4c220..e6e2068 100644 --- a/assets/Todo-v2.md +++ b/assets/Todo-v2.md @@ -151,6 +151,8 @@ ________________________________________________________________________________ - [ ] Create method to update the money management use by the bot - [ ] Implement from/to tickers array pattern - [ ] Extract all managing trade method into a TradingBox class => Create composable trading bot type easily +- [ ] Bot backup worker: Every x, get saved bots and check if still running. If not running call api to reboot bot. +- [ ] Create worker to fetch the biggest spread between long\short funding rate and send alert when most profitable delta neutral position is found # Front-end ## Improve Account page diff --git a/scripts/docker-deploy-sandbox - Copy.cmd b/scripts/docker-deploy-local.cmd similarity index 84% rename from scripts/docker-deploy-sandbox - Copy.cmd rename to scripts/docker-deploy-local.cmd index e554517..cc62723 100644 --- a/scripts/docker-deploy-sandbox - Copy.cmd +++ b/scripts/docker-deploy-local.cmd @@ -2,4 +2,4 @@ cd .. cd .\src\ docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache -docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d \ No newline at end of file +docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.local.yml up -d \ No newline at end of file diff --git a/src/Managing.Api.Workers/Managing.Api.Workers.csproj b/src/Managing.Api.Workers/Managing.Api.Workers.csproj index c046e54..2e99aee 100644 --- a/src/Managing.Api.Workers/Managing.Api.Workers.csproj +++ b/src/Managing.Api.Workers/Managing.Api.Workers.csproj @@ -32,13 +32,13 @@ - + Always Always - + Always diff --git a/src/Managing.Api.Workers/Program.cs b/src/Managing.Api.Workers/Program.cs index a951a82..80d871e 100644 --- a/src/Managing.Api.Workers/Program.cs +++ b/src/Managing.Api.Workers/Program.cs @@ -14,8 +14,9 @@ using Serilog.Sinks.Elasticsearch; // Builder var builder = WebApplication.CreateBuilder(args); -builder.Configuration - .AddEnvironmentVariables(); +builder.Configuration.SetBasePath(System.AppContext.BaseDirectory); +builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json"); builder.Host.UseSerilog((hostBuilder, loggerConfiguration) => { diff --git a/src/Managing.Api.Workers/Workers/FeeWorker.cs b/src/Managing.Api.Workers/Workers/FeeWorker.cs index a0aa579..8ea937b 100644 --- a/src/Managing.Api.Workers/Workers/FeeWorker.cs +++ b/src/Managing.Api.Workers/Workers/FeeWorker.cs @@ -1,4 +1,5 @@ using Managing.Application.Abstractions.Services; +using Managing.Application.Workers; using Managing.Application.Workers.Abstractions; using static Managing.Common.Enums; diff --git a/src/Managing.Api.Workers/Workers/LeaderboardWorker.cs b/src/Managing.Api.Workers/Workers/LeaderboardWorker.cs index 96feb0e..f85bf6c 100644 --- a/src/Managing.Api.Workers/Workers/LeaderboardWorker.cs +++ b/src/Managing.Api.Workers/Workers/LeaderboardWorker.cs @@ -1,4 +1,5 @@ -using Managing.Application.Workers.Abstractions; +using Managing.Application.Workers; +using Managing.Application.Workers.Abstractions; using static Managing.Common.Enums; namespace Managing.Api.Workers.Workers; diff --git a/src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs b/src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs index 13aa38a..0ca651a 100644 --- a/src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs +++ b/src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs @@ -1,4 +1,5 @@ -using Managing.Application.Workers.Abstractions; +using Managing.Application.Workers; +using Managing.Application.Workers.Abstractions; using static Managing.Common.Enums; namespace Managing.Api.Workers.Workers; diff --git a/src/Managing.Api.Workers/Workers/PositionFetcher.cs b/src/Managing.Api.Workers/Workers/PositionFetcher.cs index 4e66188..59664d8 100644 --- a/src/Managing.Api.Workers/Workers/PositionFetcher.cs +++ b/src/Managing.Api.Workers/Workers/PositionFetcher.cs @@ -1,5 +1,6 @@ using Managing.Application.Abstractions.Services; using Managing.Application.Hubs; +using Managing.Application.Workers; using Managing.Application.Workers.Abstractions; using Microsoft.AspNetCore.SignalR; using static Managing.Common.Enums; diff --git a/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs index 61811e5..f9fa8f7 100644 --- a/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs +++ b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs @@ -1,4 +1,5 @@ using Managing.Application.Abstractions.Services; +using Managing.Application.Workers; using Managing.Application.Workers.Abstractions; using Managing.Domain.Trades; using static Managing.Common.Enums; diff --git a/src/Managing.Api.Workers/Workers/PricesBaseWorker.cs b/src/Managing.Api.Workers/Workers/PricesBaseWorker.cs index d3816e2..c247fca 100644 --- a/src/Managing.Api.Workers/Workers/PricesBaseWorker.cs +++ b/src/Managing.Api.Workers/Workers/PricesBaseWorker.cs @@ -1,4 +1,5 @@ -using Managing.Application.Workers.Abstractions; +using Managing.Application.Workers; +using Managing.Application.Workers.Abstractions; using static Managing.Common.Enums; namespace Managing.Api.Workers.Workers; diff --git a/src/Managing.Api.Workers/Workers/SpotlightWorker.cs b/src/Managing.Api.Workers/Workers/SpotlightWorker.cs index 91e550e..0a23db8 100644 --- a/src/Managing.Api.Workers/Workers/SpotlightWorker.cs +++ b/src/Managing.Api.Workers/Workers/SpotlightWorker.cs @@ -1,4 +1,5 @@ -using Managing.Application.Workers.Abstractions; +using Managing.Application.Workers; +using Managing.Application.Workers.Abstractions; using Managing.Common; namespace Managing.Api.Workers.Workers; diff --git a/src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs b/src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs index e103728..602f584 100644 --- a/src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs +++ b/src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs @@ -1,4 +1,5 @@ -using Managing.Application.Workers.Abstractions; +using Managing.Application.Workers; +using Managing.Application.Workers.Abstractions; using static Managing.Common.Enums; namespace Managing.Api.Workers.Workers; diff --git a/src/Managing.Api.Workers/Workers/TraderWatcher.cs b/src/Managing.Api.Workers/Workers/TraderWatcher.cs index 90895a5..b9f2132 100644 --- a/src/Managing.Api.Workers/Workers/TraderWatcher.cs +++ b/src/Managing.Api.Workers/Workers/TraderWatcher.cs @@ -1,4 +1,5 @@ using Managing.Application.Abstractions.Services; +using Managing.Application.Workers; using Managing.Application.Workers.Abstractions; using static Managing.Common.Enums; diff --git a/src/Managing.Api.Workers/appsettings.Oda-docker.json b/src/Managing.Api.Workers/appsettings.Oda-docker.json index e81d760..efe660f 100644 --- a/src/Managing.Api.Workers/appsettings.Oda-docker.json +++ b/src/Managing.Api.Workers/appsettings.Oda-docker.json @@ -5,8 +5,8 @@ }, "InfluxDb": { "Url": "http://influxdb:8086/", - "Organization": "", - "Token": "" + "Organization": "managing-org", + "Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==" }, "Serilog": { "MinimumLevel": { diff --git a/src/Managing.Api.Workers/appsettings.Oda.json b/src/Managing.Api.Workers/appsettings.Oda.json index d9754cb..643d440 100644 --- a/src/Managing.Api.Workers/appsettings.Oda.json +++ b/src/Managing.Api.Workers/appsettings.Oda.json @@ -1,7 +1,7 @@ { "ManagingDatabase": { "ConnectionString": "mongodb://localhost:27017", - "DatabaseName": "ManagingDb", + "DatabaseName": "ManagingDb" }, "InfluxDb": { "Url": "http://localhost:8086/", diff --git a/src/Managing.Api/Managing.Api.csproj b/src/Managing.Api/Managing.Api.csproj index be05f0e..843053a 100644 --- a/src/Managing.Api/Managing.Api.csproj +++ b/src/Managing.Api/Managing.Api.csproj @@ -36,6 +36,9 @@ Always + + Always + Always diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs index 7d8eba9..0ad26cd 100644 --- a/src/Managing.Api/Program.cs +++ b/src/Managing.Api/Program.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Managing.Api.Authorization; using Managing.Api.Exceptions; using Managing.Api.Filters; +using Managing.Api.Workers; using Managing.Application.Hubs; using Managing.Bootstrap; using Managing.Common; @@ -17,12 +18,15 @@ using Serilog.Sinks.Elasticsearch; // Builder var builder = WebApplication.CreateBuilder(args); -builder.Configuration.AddJsonFile("appsettings.Lowpro.json", optional: true, reloadOnChange: true) +builder.Configuration.SetBasePath(System.AppContext.BaseDirectory); +builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json") .AddJsonFile($"config.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); builder.Configuration.AddEnvironmentVariables(); builder.Configuration.AddUserSecrets(); + builder.Host.UseSerilog((hostBuilder, loggerConfiguration) => { var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-"); @@ -123,6 +127,7 @@ builder.Services.AddSwaggerGen(options => }); builder.WebHost.SetupDiscordBot(); +builder.Services.AddHostedService(); // App var app = builder.Build(); diff --git a/src/Managing.Api/Workers/BotManagerWorker.cs b/src/Managing.Api/Workers/BotManagerWorker.cs new file mode 100644 index 0000000..b58aa6f --- /dev/null +++ b/src/Managing.Api/Workers/BotManagerWorker.cs @@ -0,0 +1,23 @@ +using Managing.Application.ManageBot; +using Managing.Application.Workers; +using Managing.Application.Workers.Abstractions; +using MediatR; +using static Managing.Common.Enums; + +namespace Managing.Api.Workers; + +public class BotManagerWorker( + ILogger logger, + IMediator mediadior, + IWorkerService workerService) + : BaseWorker(WorkerType.BotManager, + logger, + TimeSpan.FromMinutes(1), + workerService) +{ + protected override async Task Run(CancellationToken cancellationToken) + { + var loadBackupBotCommand = new LoadBackupBotCommand(); + await mediadior.Send(loadBackupBotCommand, cancellationToken); + } +} diff --git a/src/Managing.Api/appsettings.Oda-docker.json b/src/Managing.Api/appsettings.Oda-docker.json index e59c1c4..9a8a6a3 100644 --- a/src/Managing.Api/appsettings.Oda-docker.json +++ b/src/Managing.Api/appsettings.Oda-docker.json @@ -8,7 +8,7 @@ "InfluxDb": { "Url": "http://influxdb:8086/", "Organization": "managing-org", - "Token": "" + "Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==" }, "Serilog": { "MinimumLevel": { diff --git a/src/Managing.Api/appsettings.Oda.json b/src/Managing.Api/appsettings.Oda.json index e9b2a8d..bc7818a 100644 --- a/src/Managing.Api/appsettings.Oda.json +++ b/src/Managing.Api/appsettings.Oda.json @@ -5,8 +5,8 @@ }, "InfluxDb": { "Url": "http://localhost:8086/", - "Organization": "", - "Token": "" + "Organization": "managing-org", + "Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==" }, "Serilog": { "MinimumLevel": { diff --git a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs new file mode 100644 index 0000000..7af45de --- /dev/null +++ b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs @@ -0,0 +1,7 @@ +public interface IBotRepository +{ + Task InsertBotAsync(BotBackup bot); + IEnumerable GetBots(); + Task UpdateBackupBot(BotBackup bot); + void DeleteBotBackup(string botName); +} \ No newline at end of file diff --git a/src/Managing.Application.Tests/BotsTests.cs b/src/Managing.Application.Tests/BotsTests.cs index 3f7400d..e95c049 100644 --- a/src/Managing.Application.Tests/BotsTests.cs +++ b/src/Managing.Application.Tests/BotsTests.cs @@ -28,13 +28,14 @@ namespace Managing.Application.Tests var discordService = new Mock().Object; var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger(); var backtestLogger = TradingBaseTests.CreateBacktesterLogger(); + var botService = new Mock().Object; _botFactory = new BotFactory( _exchangeService, tradingBotLogger, - _moneyManagementService.Object, discordService, _accountService.Object, - _tradingService.Object); + _tradingService.Object, + botService); _backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger); _elapsedTimes = new List(); } diff --git a/src/Managing.Application.Tests/WorkflowTests.cs b/src/Managing.Application.Tests/WorkflowTests.cs index 8961bc2..893a221 100644 --- a/src/Managing.Application.Tests/WorkflowTests.cs +++ b/src/Managing.Application.Tests/WorkflowTests.cs @@ -1,5 +1,4 @@ -using Managing.Application.Workflows.Flows.Feeds; -using Managing.Domain.Workflows; +using Managing.Domain.Workflows; using Xunit; using static Managing.Common.Enums; diff --git a/src/Managing.Api.Workers/Workers/BaseWorker.cs b/src/Managing.Application.Workers/BaseWorker.cs similarity index 95% rename from src/Managing.Api.Workers/Workers/BaseWorker.cs rename to src/Managing.Application.Workers/BaseWorker.cs index cc11430..04856ff 100644 --- a/src/Managing.Api.Workers/Workers/BaseWorker.cs +++ b/src/Managing.Application.Workers/BaseWorker.cs @@ -1,8 +1,10 @@  using Managing.Application.Workers.Abstractions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using static Managing.Common.Enums; -namespace Managing.Api.Workers; +namespace Managing.Application.Workers; public abstract class BaseWorker : BackgroundService where T : class { diff --git a/src/Managing.Application.Workers/Managing.Application.Workers.csproj b/src/Managing.Application.Workers/Managing.Application.Workers.csproj index 3a4c3ec..dfb4696 100644 --- a/src/Managing.Application.Workers/Managing.Application.Workers.csproj +++ b/src/Managing.Application.Workers/Managing.Application.Workers.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs new file mode 100644 index 0000000..55eee9d --- /dev/null +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -0,0 +1,34 @@ +using Managing.Common; +using Managing.Domain.Bots; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Workflows; + +namespace Managing.Application.Abstractions; + +public interface IBotService +{ + void SaveBotBackup(BotBackup botBackup); + void SaveBotBackup(string name, Enums.BotType botType, string data); + void AddSimpleBotToCache(IBot bot); + void AddTradingBotToCache(ITradingBot bot); + List GetActiveBots(); + IEnumerable GetSavedBots(); + void StartBotFromBackup(BotBackup backupBot); + + ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, + string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + + ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker, + string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + + ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, + string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + + ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker, + string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + + IBot CreateSimpleBot(string botName, Workflow workflow); + Task StopBot(string requestName); + Task DeleteBot(string requestName); + Task RestartBot(string requestName); +} \ No newline at end of file diff --git a/src/Managing.Application/Bots/Base/BotFactory.cs b/src/Managing.Application/Bots/Base/BotFactory.cs index 539f3f5..ea49f14 100644 --- a/src/Managing.Application/Bots/Base/BotFactory.cs +++ b/src/Managing.Application/Bots/Base/BotFactory.cs @@ -10,32 +10,32 @@ namespace Managing.Application.Bots.Base { public class BotFactory : IBotFactory { - private readonly IMoneyManagementService _moneyManagementService; private readonly IExchangeService _exchangeService; private readonly IMessengerService _messengerService; private readonly IAccountService _accountService; private readonly ILogger _tradingBotLogger; private readonly ITradingService _tradingService; + private readonly IBotService _botService; public BotFactory( IExchangeService exchangeService, ILogger tradingBotLogger, - IMoneyManagementService moneyManagementService, IMessengerService messengerService, IAccountService accountService, - ITradingService tradingService) + ITradingService tradingService, + IBotService botService) { _tradingBotLogger = tradingBotLogger; _exchangeService = exchangeService; - _moneyManagementService = moneyManagementService; _messengerService = messengerService; _accountService = accountService; _tradingService = tradingService; + _botService = botService; } IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow) { - return new SimpleBot(botName, _tradingBotLogger, workflow); + return new SimpleBot(botName, _tradingBotLogger, workflow, _botService); } ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) @@ -52,6 +52,7 @@ namespace Managing.Application.Bots.Base interval, _accountService, _messengerService, + _botService, isForWatchingOnly: isForWatchingOnly); } @@ -69,6 +70,7 @@ namespace Managing.Application.Bots.Base interval, _accountService, _messengerService, + _botService, true, isForWatchingOnly); } @@ -87,6 +89,7 @@ namespace Managing.Application.Bots.Base interval, _accountService, _messengerService, + _botService, isForWatchingOnly: isForWatchingOnly); } @@ -104,6 +107,7 @@ namespace Managing.Application.Bots.Base interval, _accountService, _messengerService, + _botService, true, isForWatchingOnly); } diff --git a/src/Managing.Application/Bots/FlippingBot.cs b/src/Managing.Application/Bots/FlippingBot.cs index 058a910..9bf2270 100644 --- a/src/Managing.Application/Bots/FlippingBot.cs +++ b/src/Managing.Application/Bots/FlippingBot.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Managing.Application.Abstractions; +using Microsoft.Extensions.Logging; using static Managing.Common.Enums; using Managing.Application.Abstractions.Services; using Managing.Domain.MoneyManagements; @@ -18,25 +19,26 @@ namespace Managing.Application.Bots Timeframe timeframe, IAccountService accountService, IMessengerService messengerService, + IBotService botService, bool isForBacktest = false, bool isForWatchingOnly = false) : base(accountName, - moneyManagement, - name, - ticker, - scenario, - exchangeService, - logger, - tradingService, - timeframe, - accountService, - messengerService, - isForBacktest, - isForWatchingOnly, - flipPosition: true) + moneyManagement, + name, + ticker, + scenario, + exchangeService, + logger, + tradingService, + timeframe, + accountService, + messengerService, + botService, + isForBacktest, + isForWatchingOnly, + flipPosition: true) { BotType = BotType.FlippingBot; - Start(); } public sealed override void Start() @@ -46,4 +48,4 @@ namespace Managing.Application.Bots Logger.LogInformation($"Starting {Name} bot - Status : {Status}"); } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Bots/ScalpingBot.cs b/src/Managing.Application/Bots/ScalpingBot.cs index f770c15..f69c336 100644 --- a/src/Managing.Application/Bots/ScalpingBot.cs +++ b/src/Managing.Application/Bots/ScalpingBot.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Managing.Application.Abstractions; +using Microsoft.Extensions.Logging; using static Managing.Common.Enums; using Managing.Application.Abstractions.Services; using Managing.Domain.MoneyManagements; @@ -18,6 +19,7 @@ namespace Managing.Application.Bots Timeframe timeframe, IAccountService accountService, IMessengerService messengerService, + IBotService botService, bool isForBacktest = false, bool isForWatchingOnly = false) : base(accountName, @@ -31,11 +33,11 @@ namespace Managing.Application.Bots timeframe, accountService, messengerService, + botService, isForBacktest, isForWatchingOnly) { BotType = BotType.ScalpingBot; - Start(); } public sealed override void Start() diff --git a/src/Managing.Application/Bots/SimpleBot.cs b/src/Managing.Application/Bots/SimpleBot.cs index ee8feb8..c22f3d4 100644 --- a/src/Managing.Application/Bots/SimpleBot.cs +++ b/src/Managing.Application/Bots/SimpleBot.cs @@ -1,20 +1,25 @@ -using Managing.Domain.Bots; +using Managing.Application.Abstractions; +using Managing.Domain.Bots; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using static Managing.Common.Enums; namespace Managing.Application.Bots { public class SimpleBot : Bot { public readonly ILogger Logger; - private readonly Workflow _workflow; + private readonly IBotService _botService; + private Workflow _workflow; - public SimpleBot(string name, ILogger logger, Workflow workflow) : base(name) + public SimpleBot(string name, ILogger logger, Workflow workflow, IBotService botService) : + base(name) { Logger = logger; + _botService = botService; _workflow = workflow; Interval = 100; - Start(); } public override void Start() @@ -32,8 +37,20 @@ namespace Managing.Application.Bots Logger.LogInformation(Identifier); Logger.LogInformation(DateTime.Now.ToString()); await _workflow.Execute(); + SaveBackup(); Logger.LogInformation("__________________________________________________"); }); } + + public override void SaveBackup() + { + var data = JsonConvert.SerializeObject(_workflow); + _botService.SaveBotBackup(Name, BotType.SimpleBot, data); + } + + public override void LoadBackup(BotBackup backup) + { + _workflow = JsonConvert.DeserializeObject(backup.Data); + } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 1dde4ab..21c1f80 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -11,6 +11,7 @@ using Managing.Domain.Shared.Helpers; using Managing.Domain.Strategies; using Managing.Domain.Trades; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using static Managing.Common.Enums; namespace Managing.Application.Bots; @@ -22,6 +23,7 @@ public class TradingBot : Bot, ITradingBot public readonly IMessengerService MessengerService; public readonly IAccountService AccountService; private readonly ITradingService TradingService; + private readonly IBotService BotService; public Account Account { get; set; } public HashSet Strategies { get; set; } @@ -54,6 +56,7 @@ public class TradingBot : Bot, ITradingBot Timeframe timeframe, IAccountService accountService, IMessengerService messengerService, + IBotService botService, bool isForBacktest = false, bool isForWatchingOnly = false, bool flipPosition = false) @@ -63,6 +66,7 @@ public class TradingBot : Bot, ITradingBot AccountService = accountService; MessengerService = messengerService; TradingService = tradingService; + BotService = botService; IsForWatchingOnly = isForWatchingOnly; FlipPosition = flipPosition; @@ -97,7 +101,17 @@ public class TradingBot : Bot, ITradingBot LoadScenario(); await PreloadCandles(); await CancelAllOrders(); - await MessengerService.SendMessage($"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies."); + + try + { + await MessengerService.SendMessage( + $"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies."); + } + catch (Exception ex) + { + Logger.LogError(ex, ex.Message); + } + await InitWorker(Run); } @@ -143,7 +157,8 @@ public class TradingBot : Bot, ITradingBot public async Task Run() { Logger.LogInformation($"____________________{Name}____________________"); - Logger.LogInformation($"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}"); + Logger.LogInformation( + $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}"); var previousCandleCount = Candles.Count; @@ -158,6 +173,9 @@ public class TradingBot : Bot, ITradingBot if (!IsForWatchingOnly) await ManagePositions(); + if (!IsForBacktest) + SaveBackup(); + await UpdateWalletBalances(); Logger.LogInformation($"Candles : {Candles.Count}"); Logger.LogInformation($"Signals : {Signals.Count}"); @@ -168,6 +186,9 @@ public class TradingBot : Bot, ITradingBot private async Task PreloadCandles() { + if (Candles.Any()) + return; + var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe); foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime())) @@ -203,7 +224,7 @@ public class TradingBot : Bot, ITradingBot signal.Status = SignalStatus.Expired; var signalText = $"{Scenario} trigger a signal. Signal told you " + - $"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}"; + $"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}"; Logger.LogInformation(signalText); @@ -265,7 +286,9 @@ public class TradingBot : Bot, ITradingBot { Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}"); - var position = IsForBacktest ? positionForSignal : TradingService.GetPositionByIdentifier(positionForSignal.Identifier); + var position = IsForBacktest + ? positionForSignal + : TradingService.GetPositionByIdentifier(positionForSignal.Identifier); if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped)) { @@ -277,32 +300,41 @@ public class TradingBot : Bot, ITradingBot // For backtesting or force close if not executed on exchange : // check if position is still open // Check status, if still open update the status of the position - var lastCandle = IsForBacktest ? Candles.Last() : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow); + var lastCandle = IsForBacktest + ? Candles.Last() + : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow); if (positionForSignal.OriginDirection == TradeDirection.Long) { if (positionForSignal.StopLoss.Price >= lastCandle.Low) { - await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}"); - await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true); + await LogInformation( + $"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}"); + await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, + positionForSignal.StopLoss.Price, true); positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); } else if (positionForSignal.TakeProfit1.Price <= lastCandle.High - && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) + && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) { - await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}"); - await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); + await LogInformation( + $"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, + positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); } else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High) { - await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}"); - await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true); + await LogInformation( + $"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, + positionForSignal.TakeProfit2.Price, true); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); } else { - Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened"); + Logger.LogInformation( + $"Position {signal.Identifier} don't need to be update. Position still opened"); } } @@ -310,26 +342,33 @@ public class TradingBot : Bot, ITradingBot { if (positionForSignal.StopLoss.Price <= lastCandle.High) { - await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}"); - await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true); + await LogInformation( + $"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}"); + await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, + positionForSignal.StopLoss.Price, true); positionForSignal.StopLoss.SetStatus(TradeStatus.Filled); } else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low - && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) + && positionForSignal.TakeProfit1.Status != TradeStatus.Filled) { - await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}"); - await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); + await LogInformation( + $"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, + positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null); positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled); } else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low) { - await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}"); - await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true); + await LogInformation( + $"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}"); + await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, + positionForSignal.TakeProfit2.Price, true); positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled); } else { - Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened"); + Logger.LogInformation( + $"Position {signal.Identifier} don't need to be update. Position still opened"); } } } @@ -353,16 +392,17 @@ public class TradingBot : Bot, ITradingBot } - private async Task OpenPosition(Signal signal) { // Check if a position is already open Logger.LogInformation($"Opening position for {signal.Identifier}"); var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled - && p.SignalIdentifier != signal.Identifier); + && p.SignalIdentifier != signal.Identifier); - var lastPrice = IsForBacktest ? Candles.Last().Close : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow); + var lastPrice = IsForBacktest + ? Candles.Last().Close + : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow); // If position open if (openedPosition != null) @@ -373,7 +413,8 @@ public class TradingBot : Bot, ITradingBot if (openedPosition.OriginDirection == signal.Direction) { // An operation is already open for the same direction - await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction"); + await LogInformation( + $"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); } else @@ -386,11 +427,13 @@ public class TradingBot : Bot, ITradingBot await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped); await OpenPosition(signal); - await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$"); + await LogInformation( + $"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$"); } else { - await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped."); + await LogWarning( + $"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped."); SetSignalStatus(signal.Identifier, SignalStatus.Expired); } } @@ -399,12 +442,14 @@ public class TradingBot : Bot, ITradingBot { if (!CanOpenPosition(signal)) { - await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position"); + await LogInformation( + "Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); return; } - await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}"); + await LogInformation( + $"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}"); try { @@ -459,9 +504,9 @@ public class TradingBot : Bot, ITradingBot return true; var lastPosition = Positions.LastOrDefault(p => p.IsFinished() - && p.SignalIdentifier != signal.Identifier - && p.ProfitAndLoss.Realized < 0 - && p.OriginDirection == signal.Direction); + && p.SignalIdentifier != signal.Identifier + && p.ProfitAndLoss.Realized < 0 + && p.OriginDirection == signal.Direction); if (lastPosition == null) return true; @@ -472,15 +517,18 @@ public class TradingBot : Bot, ITradingBot return positionSignal.Date < tenCandleAgo.Date; } - private async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, bool tradeClosingPosition = false) + private async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, + bool tradeClosingPosition = false) { - if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled && tradeToClose.TradeType == TradeType.StopMarket) + if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled && + tradeToClose.TradeType == TradeType.StopMarket) { // If trade is the 2nd Take profit tradeToClose.Quantity = position.TakeProfit2.Quantity; } - await LogInformation($"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " + + await LogInformation( + $"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " + $"- Closing Position : {tradeClosingPosition}"); // Get status of position before closing it. The position might be already close by the exchange @@ -494,8 +542,9 @@ public class TradingBot : Bot, ITradingBot var command = new ClosePositionCommand(position, lastPrice); try { - var closedPosition = await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)) - .Handle(command); + var closedPosition = + await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)) + .Handle(command); if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped)) { @@ -521,7 +570,6 @@ public class TradingBot : Bot, ITradingBot await SetPositionStatus(signal.Identifier, PositionStatus.Finished); } } - } } @@ -534,7 +582,8 @@ public class TradingBot : Bot, ITradingBot position.SignalIdentifier = previousPosition.SignalIdentifier; Positions[positionIndex] = position; SetSignalStatus(position.SignalIdentifier, SignalStatus.Expired); - Logger.LogInformation($"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss.Realized}"); + Logger.LogInformation( + $"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss.Realized}"); } else { @@ -551,7 +600,7 @@ public class TradingBot : Bot, ITradingBot private async Task CancelAllOrders() { - if (!IsForBacktest) + if (!IsForBacktest && !IsForWatchingOnly) { try { @@ -648,4 +697,52 @@ public class TradingBot : Bot, ITradingBot await MessengerService.SendTradeMessage(message, isBadBehavior); } } + + public override void SaveBackup() + { + var data = new TradingBotBackup + { + Name = Name, + BotType = BotType, + Signals = Signals, + Positions = Positions, + Timeframe = Timeframe, + Ticker = Ticker, + Scenario = Scenario, + AccountName = AccountName, + IsForWatchingOnly = IsForWatchingOnly, + WalletBalances = WalletBalances, + MoneyManagement = MoneyManagement + }; + BotService.SaveBotBackup(Name, BotType, JsonConvert.SerializeObject(data)); + } + + public override void LoadBackup(BotBackup backup) + { + var data = JsonConvert.DeserializeObject(backup.Data); + Signals = data.Signals; + Positions = data.Positions; + WalletBalances = data.WalletBalances; + MoneyManagement = data.MoneyManagement; + Timeframe = data.Timeframe; + Ticker = data.Ticker; + Scenario = data.Scenario; + AccountName = data.AccountName; + IsForWatchingOnly = data.IsForWatchingOnly; + } } + +public class TradingBotBackup +{ + public string Name { get; set; } + public BotType BotType { get; set; } + public HashSet Signals { get; set; } + public List Positions { get; set; } + public Timeframe Timeframe { get; set; } + public Ticker Ticker { get; set; } + public string Scenario { get; set; } + public string AccountName { get; set; } + public bool IsForWatchingOnly { get; set; } + public Dictionary WalletBalances { get; set; } + public MoneyManagement MoneyManagement { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs new file mode 100644 index 0000000..5291d60 --- /dev/null +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -0,0 +1,278 @@ +using System.Collections.Concurrent; +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; +using Managing.Application.Bots; +using Managing.Common; +using Managing.Domain.Bots; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Workflows; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Managing.Application.ManageBot +{ + public class BotService : IBotService + { + private readonly IBotRepository _botRepository; + private readonly IExchangeService _exchangeService; + private readonly IMessengerService _messengerService; + private readonly IAccountService _accountService; + private readonly ILogger _tradingBotLogger; + private readonly ITradingService _tradingService; + + private ConcurrentDictionary _botTasks = + new ConcurrentDictionary(); + + public BotService(IBotRepository botRepository, IExchangeService exchangeService, + IMessengerService messengerService, IAccountService accountService, ILogger tradingBotLogger, + ITradingService tradingService) + { + _botRepository = botRepository; + _exchangeService = exchangeService; + _messengerService = messengerService; + _accountService = accountService; + _tradingBotLogger = tradingBotLogger; + _tradingService = tradingService; + } + + public async void SaveBotBackup(BotBackup botBackup) + { + await _botRepository.InsertBotAsync(botBackup); + } + + public BotBackup GetBotBackup(string name) + { + return _botRepository.GetBots().FirstOrDefault(b => b.Name == name); + } + + public void SaveBotBackup(string name, Enums.BotType botType, string data) + { + var backup = GetBotBackup(name); + + if (backup != null) + { + backup.Data = data; + _botRepository.UpdateBackupBot(backup); + return; + } + + var botBackup = new BotBackup + { + Name = name, + BotType = botType, + Data = data + }; + + _botRepository.InsertBotAsync(botBackup); + } + + 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 BotTaskWrapper(Task task, Type botType, object botInstance) // Update constructor + { + Task = task; + BotType = botType; + BotInstance = botInstance; // Set the bot instance + } + } + + public void AddSimpleBotToCache(IBot bot) + { + var botTask = + new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); // Pass bot as the instance + _botTasks.AddOrUpdate(bot.GetName(), botTask, (key, existingVal) => botTask); + } + + public void AddTradingBotToCache(ITradingBot bot) + { + var botTask = new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); + _botTasks.AddOrUpdate(bot.GetName(), botTask, (key, existingVal) => botTask); + } + + public List GetActiveBots() + { + return _botTasks.Values + .Where(wrapper => typeof(ITradingBot).IsAssignableFrom(wrapper.BotType)) + .Select(wrapper => wrapper.BotInstance as ITradingBot) + .Where(bot => bot != null) + .ToList(); + } + + public IEnumerable GetSavedBots() + { + return _botRepository.GetBots(); + } + + public void StartBotFromBackup(BotBackup backupBot) + { + object bot = null; + Task botTask = null; + + switch (backupBot.BotType) + { + case Enums.BotType.SimpleBot: + bot = CreateSimpleBot(backupBot.Name, + null); // Assuming null is an acceptable parameter for workflow + botTask = Task.Run(() => ((IBot)bot).Start()); + break; + case Enums.BotType.ScalpingBot: + var scalpingBotData = JsonConvert.DeserializeObject(backupBot.Data); + bot = CreateScalpingBot( + scalpingBotData.AccountName, + scalpingBotData.MoneyManagement, + backupBot.Name, + scalpingBotData.Ticker, + scalpingBotData.Scenario, + scalpingBotData.Timeframe, + scalpingBotData.IsForWatchingOnly); + botTask = Task.Run(() => ((ITradingBot)bot).Start()); + break; + case Enums.BotType.FlippingBot: + var flippingBotData = JsonConvert.DeserializeObject(backupBot.Data); + bot = CreateFlippingBot( + flippingBotData.AccountName, + flippingBotData.MoneyManagement, + backupBot.Name, + flippingBotData.Ticker, + flippingBotData.Scenario, + flippingBotData.Timeframe, + flippingBotData.IsForWatchingOnly); + botTask = Task.Run(() => ((ITradingBot)bot).Start()); + break; + } + + if (bot != null && botTask != null) + { + var botWrapper = new BotTaskWrapper(botTask, bot.GetType(), bot); + _botTasks.AddOrUpdate(backupBot.Name, botWrapper, (key, existingVal) => botWrapper); + } + } + + public IBot CreateSimpleBot(string botName, Workflow workflow) + { + return new SimpleBot(botName, _tradingBotLogger, workflow, this); + } + + public async Task StopBot(string botName) + { + if (_botTasks.TryGetValue(botName, out var botWrapper)) + { + if (botWrapper.BotInstance is IBot bot) + { + await Task.Run(() => + bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods + return bot.GetStatus(); + } + } + + return Enums.BotStatus.Down.ToString(); + } + + public Task DeleteBot(string botName) + { + if (_botTasks.TryRemove(botName, out _)) + { + _botRepository.DeleteBotBackup(botName); + return Task.FromResult(true); + } + + return Task.FromResult(false); + } + + public Task RestartBot(string botName) + { + if (_botTasks.TryGetValue(botName, out var botWrapper)) + { + if (botWrapper.BotInstance is IBot bot) + { + bot.Restart(); + return Task.FromResult(bot.GetStatus()); + } + } + + return Task.FromResult(Enums.BotStatus.Down.ToString()); + } + + public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + { + return new ScalpingBot( + accountName, + moneyManagement, + name, + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + this, + isForWatchingOnly: isForWatchingOnly); + } + + public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + { + return new ScalpingBot( + accountName, + moneyManagement, + "BacktestBot", + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + this, + true, + isForWatchingOnly); + } + + public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + { + return new FlippingBot( + accountName, + moneyManagement, + name, + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + this, + isForWatchingOnly: isForWatchingOnly); + } + + public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + { + return new FlippingBot( + accountName, + moneyManagement, + "BacktestBot", + scenario, + _exchangeService, + ticker, + _tradingService, + _tradingBotLogger, + interval, + _accountService, + _messengerService, + this, + true, + isForWatchingOnly); + } + } +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs b/src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs index 36c4757..7ae2734 100644 --- a/src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/DeleteBotCommandHandler.cs @@ -8,25 +8,16 @@ namespace Managing.Application.ManageBot; public class DeleteBotCommandHandler : IRequestHandler { private readonly ILogger _log; - private readonly ITaskCache _taskCache; + private readonly IBotService _botService; - public DeleteBotCommandHandler(ITaskCache taskCache, ILogger log) + public DeleteBotCommandHandler(ILogger log, IBotService botService) { - _taskCache = taskCache; _log = log; + _botService = botService; } public Task Handle(DeleteBotCommand request, CancellationToken cancellationToken) { - try - { - _taskCache.Invalidate(request.Name); - return Task.FromResult(true); - } - catch (Exception e) - { - _log.LogError(e.Message); - return Task.FromResult(false); - } + return _botService.DeleteBot(request.Name); } -} +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs b/src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs index 4ee8e46..930049e 100644 --- a/src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs +++ b/src/Managing.Application/ManageBot/GetActiveBotsCommandHandler.cs @@ -1,30 +1,15 @@ using Managing.Application.Abstractions; using Managing.Application.ManageBot.Commands; -using Managing.Core; using MediatR; namespace Managing.Application.ManageBot { - public class GetActiveBotsCommandHandler : IRequestHandler> + public class GetActiveBotsCommandHandler(IBotService botService) + : IRequestHandler> { - private readonly ITaskCache taskCache; - - public GetActiveBotsCommandHandler(ITaskCache taskCache) - { - this.taskCache = taskCache; - } - public Task> Handle(GetActiveBotsCommand request, CancellationToken cancellationToken) { - var cachedTask = taskCache.GetCache>(); - var result = new List(); - - foreach (var item in cachedTask) - { - result.Add(item.Value.Result); - } - - return Task.FromResult(result); + return Task.FromResult(botService.GetActiveBots()); } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs new file mode 100644 index 0000000..9b0e37c --- /dev/null +++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs @@ -0,0 +1,87 @@ +using MediatR; +using static Managing.Common.Enums; +using Managing.Application.Abstractions; +using Managing.Core; +using Microsoft.Extensions.Logging; + +namespace Managing.Application.ManageBot; + +public class LoadBackupBotCommandHandler : IRequestHandler +{ + private readonly IBotService _botService; + private readonly ILogger _logger; + + public LoadBackupBotCommandHandler( + ILogger logger, IBotService botService) + { + _logger = logger; + _botService = botService; + } + + public Task Handle(LoadBackupBotCommand request, CancellationToken cancellationToken) + { + BotStatus botStatus = BotStatus.Down; + var backupBots = _botService.GetSavedBots(); + var activeBots = _botService.GetActiveBots(); + var result = new Dictionary(); + + _logger.LogInformation($"Loading {backupBots.Count()} backup bots"); + + foreach (var backupBot in backupBots) + { + // Check if bot is existing in cache + try + { + switch (backupBot.BotType) + { + case BotType.SimpleBot: + var simpleBot = activeBots.FirstOrDefault(b => b.GetName() == backupBot.Name); + if (simpleBot == null) + { + _logger.LogInformation($"Starting backup bot {backupBot.Name}"); + _botService.StartBotFromBackup(backupBot); + result.Add(simpleBot.GetName(), BotStatus.Backup); + } + else + { + result.Add(simpleBot.GetName(), MiscExtensions.ParseEnum(simpleBot.GetStatus())); + } + + break; + case BotType.ScalpingBot: + case BotType.FlippingBot: + var scalpingBot = activeBots.FirstOrDefault(b => b.GetName() == backupBot.Name); + if (scalpingBot == null) + { + _logger.LogInformation($"Starting backup bot {backupBot.Name}"); + _botService.StartBotFromBackup(backupBot); + var bots = _botService.GetActiveBots(); + scalpingBot = bots.FirstOrDefault(b => b.GetName() == backupBot.Name); + result.Add(scalpingBot.GetName(), BotStatus.Backup); + } + else + { + result.Add(scalpingBot.GetName(), + MiscExtensions.ParseEnum(scalpingBot.GetStatus())); + } + + break; + default: + result.Add(backupBot.Name, BotStatus.Down); + break; + } + } + catch (Exception ex) + { + _logger.LogError($"Error loading bot {backupBot.Name}", ex.Message); + result.Add(backupBot.Name, BotStatus.Down); + } + } + + return Task.FromResult(botStatus.ToString()); + } +} + +public class LoadBackupBotCommand : IRequest +{ +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/RestartBotCommandHandler.cs b/src/Managing.Application/ManageBot/RestartBotCommandHandler.cs index b8a1079..dd9a4a2 100644 --- a/src/Managing.Application/ManageBot/RestartBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/RestartBotCommandHandler.cs @@ -1,39 +1,21 @@ using Managing.Application.Abstractions; using Managing.Application.ManageBot.Commands; -using Managing.Domain.Bots; using MediatR; -using static Managing.Common.Enums; namespace Managing.Application.ManageBot { public class RestartBotCommandHandler : IRequestHandler { - private readonly ITaskCache _taskCache; + private readonly IBotService _botService; - public RestartBotCommandHandler(ITaskCache taskCache) + public RestartBotCommandHandler(IBotService botService) { - _taskCache = taskCache; + _botService = botService; } public Task Handle(RestartBotCommand request, CancellationToken cancellationToken) { - switch (request.BotType) - { - case BotType.SimpleBot: - var simpleBot = _taskCache.Get(request.Name); - simpleBot.Restart(); - return Task.FromResult(simpleBot.GetStatus()); - case BotType.ScalpingBot: - var scalpingBot = _taskCache.Get(request.Name); - scalpingBot.Restart(); - return Task.FromResult(scalpingBot.GetStatus()); - case BotType.FlippingBot: - var flippingBot = _taskCache.Get(request.Name); - flippingBot.Restart(); - return Task.FromResult(flippingBot.GetStatus()); - default: - return Task.FromResult(BotStatus.Down.ToString()); - } + return _botService.RestartBot(request.Name); } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs index 7828678..b98b7c7 100644 --- a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs @@ -1,5 +1,4 @@ -using Managing.Domain.Bots; -using MediatR; +using MediatR; using static Managing.Common.Enums; using Managing.Application.Abstractions; using Managing.Application.ManageBot.Commands; @@ -9,13 +8,14 @@ namespace Managing.Application.ManageBot public class StartBotCommandHandler : IRequestHandler { private readonly IBotFactory _botFactory; - private readonly ITaskCache _taskCache; + private readonly IBotService _botService; private readonly IMoneyManagementService _moneyManagementService; - public StartBotCommandHandler(IBotFactory botFactory, ITaskCache taskCache, IMoneyManagementService moneyManagementService) + public StartBotCommandHandler(IBotFactory botFactory, IBotService botService, + IMoneyManagementService moneyManagementService) { _botFactory = botFactory; - _taskCache = taskCache; + _botService = botService; _moneyManagementService = moneyManagementService; } @@ -26,17 +26,27 @@ namespace Managing.Application.ManageBot switch (request.BotType) { case BotType.SimpleBot: - Func> simpleBot = () => Task.FromResult(_botFactory.CreateSimpleBot(request.Name, null)); - return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, simpleBot).Result.GetStatus()); + var bot = _botFactory.CreateSimpleBot(request.Name, null); + bot.Start(); + _botService.AddSimpleBotToCache(bot); + return Task.FromResult(bot.GetStatus()); case BotType.ScalpingBot: - Func> scalpingBot = () => Task.FromResult(_botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly)); - return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, scalpingBot).Result.GetStatus()); + var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, + request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly); + sBot.Start(); + _botService.AddTradingBotToCache(sBot); + return Task.FromResult(sBot.GetStatus()); case BotType.FlippingBot: - Func> flippingBot = () => Task.FromResult(_botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly)); - return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, flippingBot).Result.GetStatus()); - }; + var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, + request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly); + fBot.Start(); + _botService.AddTradingBotToCache(fBot); + return Task.FromResult(fBot.GetStatus()); + } + + ; return Task.FromResult(botStatus.ToString()); } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/StopBotCommandHandler.cs b/src/Managing.Application/ManageBot/StopBotCommandHandler.cs index 5a0a701..e92bbb4 100644 --- a/src/Managing.Application/ManageBot/StopBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StopBotCommandHandler.cs @@ -1,39 +1,21 @@ using Managing.Application.Abstractions; using Managing.Application.ManageBot.Commands; -using Managing.Domain.Bots; using MediatR; -using static Managing.Common.Enums; namespace Managing.Application.ManageBot { public class StopBotCommandHandler : IRequestHandler { - private readonly ITaskCache _taskCache; + private readonly IBotService _botService; - public StopBotCommandHandler(ITaskCache taskCache) + public StopBotCommandHandler(IBotService botService) { - _taskCache = taskCache; + _botService = botService; } public Task Handle(StopBotCommand request, CancellationToken cancellationToken) { - switch (request.BotType) - { - case BotType.SimpleBot: - var simpleBot = _taskCache.Get(request.Name); - simpleBot.Stop(); - return Task.FromResult(simpleBot.GetStatus()); - case BotType.ScalpingBot: - var scalpingBot = _taskCache.Get(request.Name); - scalpingBot.Stop(); - return Task.FromResult(scalpingBot.GetStatus()); - case BotType.FlippingBot: - var flippingBot = _taskCache.Get(request.Name); - flippingBot.Stop(); - return Task.FromResult(flippingBot.GetStatus()); - default: - return Task.FromResult(BotStatus.Down.ToString()); - } + return _botService.StopBot(request.Name); } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs index 47871b5..12f7263 100644 --- a/src/Managing.Application/Trading/TradingService.cs +++ b/src/Managing.Application/Trading/TradingService.cs @@ -1,5 +1,4 @@ -using DnsClient.Internal; -using Managing.Application.Abstractions; +using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; @@ -9,7 +8,6 @@ using Managing.Domain.Strategies; using Managing.Domain.Trades; using Managing.Domain.Shared.Helpers; using Microsoft.Extensions.Logging; -using MongoDB.Driver; using static Managing.Common.Enums; namespace Managing.Application.Trading; @@ -206,7 +204,7 @@ public class TradingService : ITradingService return _cacheService.GetOrSave($"Fee-{account.Exchange}", () => { - return _tradingRepository.GetFee(TradingExchanges.Evm).Cost; + return _tradingRepository.GetFee(TradingExchanges.Evm)?.Cost ?? 0m; }, TimeSpan.FromHours(2)); } diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs index 84f2e4d..0b37d1a 100644 --- a/src/Managing.Application/Users/UserService.cs +++ b/src/Managing.Application/Users/UserService.cs @@ -11,7 +11,7 @@ public class UserService : IUserService private readonly IUserRepository _userRepository; private readonly IAccountService _accountService; - private string[] authorizedAddresses = new string[] { "0x6781920674dA695aa5120d95D80c4B1788046806" }; + private string[] authorizedAddresses = ["0x6781920674dA695aa5120d95D80c4B1788046806", "0xA2B43AFF0992a47838DF2e6099A8439981f0B717"]; public UserService( IEvmManager evmManager, @@ -29,7 +29,7 @@ public class UserService : IUserService if (!authorizedAddresses.Contains(recoveredAddress)) throw new Exception("Address not authorized"); - + if (recoveredAddress == null || !recoveredAddress.Equals(address)) throw new Exception("Address not corresponding"); diff --git a/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs b/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs index 67eda2b..c309fd1 100644 --- a/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs +++ b/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs @@ -1,11 +1,8 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; -using Managing.Application.Accounts; -using Managing.Application.Shared; using Managing.Application.Trading.Commands; using Managing.Application.Trading; using Managing.Domain.Accounts; -using Managing.Domain.MoneyManagements; using Managing.Domain.Strategies; using Managing.Domain.Trades; using Managing.Domain.Workflows; diff --git a/src/Managing.Bootstrap/ApiBootstrap.cs b/src/Managing.Bootstrap/ApiBootstrap.cs index f90c98a..509d180 100644 --- a/src/Managing.Bootstrap/ApiBootstrap.cs +++ b/src/Managing.Bootstrap/ApiBootstrap.cs @@ -41,6 +41,7 @@ using Binance.Net.Interfaces.Clients; using Managing.Infrastructure.Evm.Services; using Managing.Application.Workflows; using Managing.Application.Bots.Base; +using Managing.Application.ManageBot; namespace Managing.Bootstrap; @@ -105,11 +106,13 @@ public static class ApiBootstrap services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); // Cache services.AddDistributedMemoryCache(); services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); // Processors services.AddTransient(); @@ -123,6 +126,8 @@ public static class ApiBootstrap services.AddTransient(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); // Stream services.AddSingleton(); diff --git a/src/Managing.Bootstrap/WorkersBootstrap.cs b/src/Managing.Bootstrap/WorkersBootstrap.cs index b6676e1..ba0df40 100644 --- a/src/Managing.Bootstrap/WorkersBootstrap.cs +++ b/src/Managing.Bootstrap/WorkersBootstrap.cs @@ -33,6 +33,7 @@ using Managing.Application.Trading.Commands; using Managing.Domain.Trades; using Managing.Infrastructure.Evm.Services; using Managing.Application.Bots.Base; +using Managing.Application.ManageBot; namespace Managing.Bootstrap; @@ -58,6 +59,7 @@ public static class WorkersBootstrap services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTransient, OpenPositionCommandHandler>(); services.AddTransient, ClosePositionCommandHandler>(); @@ -93,6 +95,7 @@ public static class WorkersBootstrap services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Cache services.AddDistributedMemoryCache(); diff --git a/src/Managing.Common/Enums.cs b/src/Managing.Common/Enums.cs index 6bf4d83..b342b8a 100644 --- a/src/Managing.Common/Enums.cs +++ b/src/Managing.Common/Enums.cs @@ -73,7 +73,8 @@ public static class Enums { Down, Starting, - Up + Up, + Backup } public enum SignalStatus @@ -316,7 +317,8 @@ public static class Enums PositionFetcher, TraderWatcher, LeaderboardWorker, - Noobiesboard + Noobiesboard, + BotManager } public enum WorkflowUsage diff --git a/src/Managing.Docker/docker-compose.sandbox.yml b/src/Managing.Docker/docker-compose.local.yml similarity index 52% rename from src/Managing.Docker/docker-compose.sandbox.yml rename to src/Managing.Docker/docker-compose.local.yml index d4c7d4e..5b4f8b7 100644 --- a/src/Managing.Docker/docker-compose.sandbox.yml +++ b/src/Managing.Docker/docker-compose.local.yml @@ -4,9 +4,9 @@ version: '3.4' services: managing.api: environment: - - ASPNETCORE_ENVIRONMENT=oda-docker + - ASPNETCORE_ENVIRONMENT=Oda-docker - ASPNETCORE_URLS=https://+:443;http://+:80 - - ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11 + - ASPNETCORE_Kestrel__Certificates__Default__Password=!Managing94 - ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx ports: - "80:80" @@ -14,20 +14,23 @@ services: volumes: - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro + - /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro + - /Users/oda/Microsoft/UserSecrets:/root/.microsoft/usersecrets/$USER_SECRETS_ID depends_on: - managingdb managing.api.workers: environment: - - ASPNETCORE_ENVIRONMENT=oda-docker + - ASPNETCORE_ENVIRONMENT=Oda-docker - ASPNETCORE_URLS=https://+:443;http://+:80 - - ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11 + - ASPNETCORE_Kestrel__Certificates__Default__Password=!Managing94 - ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx ports: - "81:80" - "444:443" volumes: - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro + - /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro depends_on: - managingdb @@ -38,24 +41,24 @@ services: ports: - "27017:27017" - elasticsearch: - ports: - - 9200:9200 - volumes: - - elasticsearch-data:/usr/share/elasticsearch/data - environment: - - discovery.type=single-node - - xpack.monitoring.templates.enabled=true - - ES_JAVA_OPTS=-Xms1g -Xmx1g - - xpack.security.enabled=false + # elasticsearch: + # ports: + # - 9200:9200 + # volumes: + # - elasticsearch-data:/usr/share/elasticsearch/data + # environment: + # - discovery.type=single-node + # - xpack.monitoring.templates.enabled=true + # - ES_JAVA_OPTS=-Xms1g -Xmx1g + # - xpack.security.enabled=false - kibana: - ports: - - 5601:5601 - depends_on: - - elasticsearch - environment: - - ELASTICSEARCH_URL=http://elasticsearch:9200 + # kibana: + # ports: + # - 5601:5601 + # depends_on: + # - elasticsearch + # environment: + # - ELASTICSEARCH_URL=http://elasticsearch:9200 influxdb: image: influxdb:latest diff --git a/src/Managing.Docker/docker-compose.yml b/src/Managing.Docker/docker-compose.yml index f86658c..e8e3f83 100644 --- a/src/Managing.Docker/docker-compose.yml +++ b/src/Managing.Docker/docker-compose.yml @@ -22,13 +22,13 @@ services: networks: - managing-network - elasticsearch: - image: elasticsearch:8.4.1 - networks: - - managing-network + # elasticsearch: + # image: elasticsearch:8.4.1 + # networks: + # - managing-network - kibana: - image: kibana:8.4.1 + # kibana: + # image: kibana:8.4.1 influxdb: image: influxdb:latest diff --git a/src/Managing.Domain/Bots/Bot.cs b/src/Managing.Domain/Bots/Bot.cs index ca4af2a..9911d32 100644 --- a/src/Managing.Domain/Bots/Bot.cs +++ b/src/Managing.Domain/Bots/Bot.cs @@ -71,5 +71,8 @@ namespace Managing.Domain.Bots { return Name; } + + public abstract void SaveBackup(); + public abstract void LoadBackup(BotBackup backup); } } \ No newline at end of file diff --git a/src/Managing.Domain/Bots/BotBackup.cs b/src/Managing.Domain/Bots/BotBackup.cs new file mode 100644 index 0000000..4028572 --- /dev/null +++ b/src/Managing.Domain/Bots/BotBackup.cs @@ -0,0 +1,8 @@ +using static Managing.Common.Enums; + +public class BotBackup +{ + public string Name { get; set; } + public BotType BotType { get; set; } + public string Data { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Domain/Bots/IBot.cs b/src/Managing.Domain/Bots/IBot.cs index 7572a3d..95a240e 100644 --- a/src/Managing.Domain/Bots/IBot.cs +++ b/src/Managing.Domain/Bots/IBot.cs @@ -8,5 +8,7 @@ void Restart(); string GetStatus(); string GetName(); + void SaveBackup(); + void LoadBackup(BotBackup backup); } } diff --git a/src/Managing.Infrastructure.Database/BotRepository.cs b/src/Managing.Infrastructure.Database/BotRepository.cs new file mode 100644 index 0000000..573e351 --- /dev/null +++ b/src/Managing.Infrastructure.Database/BotRepository.cs @@ -0,0 +1,39 @@ +using Managing.Infrastructure.Databases.MongoDb; +using Managing.Infrastructure.Databases.MongoDb.Abstractions; +using Managing.Infrastructure.Databases.MongoDb.Collections; + +namespace Managing.Infrastructure.Databases; + +public class BotRepository : IBotRepository +{ + private readonly IMongoRepository _botRepository; + + public BotRepository(IMongoRepository botRepository) + { + _botRepository = botRepository; + } + + public async Task InsertBotAsync(BotBackup bot) + { + await _botRepository.InsertOneAsync(MongoMappers.Map(bot)); + } + + public IEnumerable GetBots() + { + var bots = _botRepository.FindAll(); + return bots.Select(b => MongoMappers.Map(b)); + } + + public async Task UpdateBackupBot(BotBackup bot) + { + var b = await _botRepository.FindOneAsync(b => b.Name == bot.Name); + var dto = MongoMappers.Map(bot); + dto.Id = b.Id; + _botRepository.Update(dto); + } + + public void DeleteBotBackup(string botName) + { + _botRepository.DeleteOne(b => b.Name == botName); + } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs new file mode 100644 index 0000000..ab7303a --- /dev/null +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs @@ -0,0 +1,13 @@ +using Managing.Infrastructure.Databases.MongoDb.Attributes; +using Managing.Infrastructure.Databases.MongoDb.Configurations; +using static Managing.Common.Enums; + +namespace Managing.Infrastructure.Databases.MongoDb.Collections; + +[BsonCollection("Bots")] +public class BotDto : Document +{ + public string Name { get; set; } + public string Data { get; set; } + public BotType BotType { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs index 8412872..e1c0592 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs @@ -650,5 +650,29 @@ public static class MongoMappers }; } + internal static BotDto Map(BotBackup bot) + { + if (bot == null) return null; + + return new BotDto + { + Name = bot.Name, + BotType = bot.BotType, + Data = bot.Data, + }; + } + + internal static BotBackup Map(BotDto b) + { + if (b == null) return null; + + return new BotBackup + { + Name = b.Name, + BotType = b.BotType, + Data = b.Data + }; + } + #endregion } diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs index 71885ad..1516b8e 100644 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/BinanceProcessor.cs @@ -1,7 +1,6 @@ using Binance.Net.Clients; using Binance.Net.Enums; using Binance.Net.Interfaces.Clients; -using CryptoExchange.Net; using CryptoExchange.Net.Authentication; using Managing.Common; using Managing.Core; diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs index de1b687..01fcd5c 100644 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/FtxProcessor.cs @@ -1,5 +1,4 @@ using CryptoExchange.Net.Authentication; -using FTX.Net.Clients; using FTX.Net.Interfaces.Clients; using FTX.Net.Objects; using Managing.Common; diff --git a/src/Managing.Infrastructure.Storage/TaskCache.cs b/src/Managing.Infrastructure.Storage/TaskCache.cs index b89e52e..0461580 100644 --- a/src/Managing.Infrastructure.Storage/TaskCache.cs +++ b/src/Managing.Infrastructure.Storage/TaskCache.cs @@ -1,6 +1,5 @@ using Managing.Application.Abstractions; using Managing.Core; -//using Microsoft.Extensions.Caching.Memory; using System.Runtime.Caching; namespace Managing.Infrastructure.Storage @@ -8,11 +7,11 @@ namespace Managing.Infrastructure.Storage public class TaskCache : ITaskCache { private MemoryCache _cache { get; } = MemoryCache.Default; + private CacheItemPolicy _defaultPolicy { get; } = new CacheItemPolicy(); public async Task AddOrGetExisting(string key, Func> valueFactory) { - var asyncLazyValue = new AsyncLazy(valueFactory); var existingValue = (AsyncLazy)_cache.AddOrGetExisting(key, asyncLazyValue, _defaultPolicy); @@ -33,6 +32,7 @@ namespace Managing.Infrastructure.Storage // Get the most recent value with a recursive call. return await AddOrGetExisting(key, valueFactory); } + return result; } catch (Exception) @@ -64,13 +64,14 @@ namespace Managing.Infrastructure.Storage public T Get(string key) { var existingValue = (AsyncLazy)_cache.Get(key); - return existingValue.Value.Result; + if (existingValue != null) return existingValue.Value.Result; + return default(T); } public virtual List GetCache() { List list = new List(); - + foreach (var item in _cache) { list.Add((T)item.Value); @@ -79,4 +80,4 @@ namespace Managing.Infrastructure.Storage return list; } } -} +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Tests/SubgraphTests.cs b/src/Managing.Infrastructure.Tests/SubgraphTests.cs index ff2040d..f2af853 100644 --- a/src/Managing.Infrastructure.Tests/SubgraphTests.cs +++ b/src/Managing.Infrastructure.Tests/SubgraphTests.cs @@ -1,7 +1,4 @@ -using Xunit; -using static Managing.Common.Enums; - -namespace Managing.Infrastructure.Tests; +namespace Managing.Infrastructure.Tests; public class SubgraphTests { diff --git a/src/Managing.WebApp/.env b/src/Managing.WebApp/.env index 0b33460..f8d9d0e 100644 --- a/src/Managing.WebApp/.env +++ b/src/Managing.WebApp/.env @@ -1,4 +1,4 @@ -VITE_API_URL_LOCAL=https://localhost:5001 +VITE_API_URL_LOCAL=http://localhost:5000 VITE_API_URL_SERVER=https://dev-managing-api.apps.managing.live VITE_WORKER_URL_LOCAL=https://localhost:5002 VITE_WORKER_URL_SERVER=https://dev-managing-worker.apps.managing.live diff --git a/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx index b80ff7a..92eca05 100644 --- a/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx +++ b/src/Managing.WebApp/src/components/mollecules/LogIn/LogIn.tsx @@ -8,21 +8,24 @@ import { UserClient } from '../../../generated/ManagingApi' import type { ILoginFormInput } from '../../../global/type' import useCookie from '../../../hooks/useCookie' import { SecondaryNavbar } from '../NavBar/NavBar' +import Toast from '../Toast/Toast' const LogIn = () => { const { apiUrl } = useApiUrlStore() const { register, handleSubmit } = useForm() const { disconnect } = useDisconnect() const { address } = useAccount() - const { isLoading, signMessageAsync } = useSignMessage({}) + const { signMessageAsync } = useSignMessage({}) const { setCookie } = useCookie() const onSubmit: SubmitHandler = async (form) => { const message = 'wagmi' const signature = await signMessageAsync({ message }) + const t = new Toast('Creating token') if (signature && address) { const userClient = new UserClient({}, apiUrl) + await userClient .user_CreateToken({ address: address.toString(), @@ -34,11 +37,11 @@ const LogIn = () => { setCookie('token', data, 1) location.reload() }) - .catch((err) => { - // eslint-disable-next-line no-console - console.error(err) + .catch((err: any) => { + t.update('error', 'Error :' + err.message) }) - } else { + }else{ + t.update('error', 'Error : No signature') } } @@ -77,7 +80,6 @@ const LogIn = () => { - +