diff --git a/assets/Todo-v2.md b/assets/Todo-v2.md
index 8f4c220..f752975 100644
--- a/assets/Todo-v2.md
+++ b/assets/Todo-v2.md
@@ -151,6 +151,7 @@ ________________________________________________________________________________
- [ ] 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.
# Front-end
## Improve Account page
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..e01d702 100644
--- a/src/Managing.Api/Program.cs
+++ b/src/Managing.Api/Program.cs
@@ -17,12 +17,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(".", "-");
diff --git a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs
new file mode 100644
index 0000000..0473039
--- /dev/null
+++ b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs
@@ -0,0 +1,8 @@
+using Managing.Domain.Bots;
+
+public interface IBotRepository
+{
+ Task InsertBotAsync(BotBackup bot);
+ IEnumerable GetBots();
+ Task UpdateBackupBot(BotBackup bot);
+}
diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs
new file mode 100644
index 0000000..95feed0
--- /dev/null
+++ b/src/Managing.Application/Abstractions/IBotService.cs
@@ -0,0 +1,4 @@
+ public interface IBotService
+ {
+ void SaveBotBackup(BotBackup botBackup);
+ }
\ No newline at end of file
diff --git a/src/Managing.Application/Bots/SimpleBot.cs b/src/Managing.Application/Bots/SimpleBot.cs
index ee8feb8..8ed092a 100644
--- a/src/Managing.Application/Bots/SimpleBot.cs
+++ b/src/Managing.Application/Bots/SimpleBot.cs
@@ -1,13 +1,14 @@
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
namespace Managing.Application.Bots
{
public class SimpleBot : Bot
{
public readonly ILogger Logger;
- private readonly Workflow _workflow;
+ private Workflow _workflow;
public SimpleBot(string name, ILogger logger, Workflow workflow) : base(name)
{
@@ -35,5 +36,15 @@ namespace Managing.Application.Bots
Logger.LogInformation("__________________________________________________");
});
}
+
+ public override string GetBackup()
+ {
+ return JsonConvert.SerializeObject(_workflow);
+ }
+
+ public override void LoadBackup(BotBackup backup)
+ {
+ _workflow = JsonConvert.DeserializeObject(backup.Data);
+ }
}
}
diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs
index 1dde4ab..82fb14b 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;
@@ -64,6 +65,7 @@ public class TradingBot : Bot, ITradingBot
MessengerService = messengerService;
TradingService = tradingService;
+
IsForWatchingOnly = isForWatchingOnly;
FlipPosition = flipPosition;
AccountName = accountName;
@@ -648,4 +650,48 @@ public class TradingBot : Bot, ITradingBot
await MessengerService.SendTradeMessage(message, isBadBehavior);
}
}
+
+ public override string GetBackup()
+ {
+ return JsonConvert.SerializeObject(new TradingBotBackup
+ {
+ Name = Name,
+ BotType = BotType,
+ Strategies = Strategies,
+ Signals = Signals,
+ Positions = Positions,
+ Timeframe = Timeframe,
+ Ticker = Ticker,
+ Scenario = Scenario,
+ AccountName = AccountName,
+ IsForWatchingOnly = IsForWatchingOnly,
+ WalletBalances = WalletBalances,
+ MoneyManagement = MoneyManagement
+ });
+ }
+
+ public override void LoadBackup(BotBackup backup)
+ {
+ var data = JsonConvert.DeserializeObject(backup.Data);
+ Strategies = data.Strategies;
+ Signals = data.Signals;
+ Positions = data.Positions;
+ WalletBalances = data.WalletBalances;
+ }
}
+
+public class TradingBotBackup
+{
+ public string Name { get; set; }
+ public BotType BotType { get; set; }
+ public HashSet Strategies { 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; internal set; }
+}
\ No newline at end of file
diff --git a/src/Managing.Application/ManageBot/BackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/BackupBotCommandHandler.cs
new file mode 100644
index 0000000..4b5d127
--- /dev/null
+++ b/src/Managing.Application/ManageBot/BackupBotCommandHandler.cs
@@ -0,0 +1,68 @@
+using Managing.Domain.Bots;
+using MediatR;
+using static Managing.Common.Enums;
+using Managing.Application.Abstractions;
+using Managing.Application.ManageBot.Commands;
+using Managing.Domain.MoneyManagements;
+
+namespace Managing.Application.ManageBot
+{
+ public class BackupBotCommandHandler : IRequestHandler
+ {
+ private readonly IBotFactory _botFactory;
+ private readonly ITaskCache _taskCache;
+ private readonly IMoneyManagementService _moneyManagementService;
+ private readonly IBotService _botService;
+
+ public BackupBotCommandHandler(IBotFactory botFactory, ITaskCache taskCache, IBotService botService)
+ {
+ _botFactory = botFactory;
+ _taskCache = taskCache;
+ _botService = botService;
+ }
+
+ public Task Handle(BackupBotCommand request, CancellationToken cancellationToken)
+ {
+ var botBackup = new BotBackup
+ {
+ Name = request.Name,
+ BotType = request.BotType,
+ Data = ""
+ };
+
+ switch (request.BotType)
+ {
+ case BotType.SimpleBot:
+ var simpleBot = _taskCache.Get(request.Name);
+ botBackup.Data = simpleBot.GetBackup();
+ break;
+ case BotType.ScalpingBot:
+ var scalpingBot = _taskCache.Get(request.Name);
+ botBackup.Data = scalpingBot.GetBackup();
+ break;
+ case BotType.FlippingBot:
+ var flippingBot = _taskCache.Get(request.Name);
+ botBackup.Data = flippingBot.GetBackup();
+ break;
+ default:
+ return Task.FromResult(false);
+ }
+
+ _botService.SaveBotBackup(botBackup);
+
+ return Task.FromResult(true);
+ }
+ }
+
+ public class BackupBotCommand : IRequest
+ {
+ public string Name { get; }
+ public BotType BotType { get; }
+
+ public BackupBotCommand(BotType botType, string name)
+ {
+ BotType = botType;
+ Name = name;
+ }
+ }
+}
diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs
new file mode 100644
index 0000000..7319cc8
--- /dev/null
+++ b/src/Managing.Application/ManageBot/BotService.cs
@@ -0,0 +1,37 @@
+using Managing.Application.Abstractions;
+using Newtonsoft.Json;
+using System.IO;
+
+namespace Managing.Application.ManageBot
+{
+ public class BotService : IBotService
+ {
+ private readonly IBotFactory _botFactory;
+ private readonly IBotRepository _botRepository;
+
+ public BotService(IBotFactory botFactory, IBotRepository botRepository)
+ {
+ _botFactory = botFactory;
+ _botRepository = botRepository;
+ }
+
+ // public void CreateBot()
+ // {
+ // // Use the factory to create a new bot
+ // return _botFactory.CreateBot();
+ // }
+
+ // public void LoadBotBackup(BotBackup botBackup)
+ // {
+ // // Deserialize the JSON into a Bot object
+ // var bot = JsonConvert.DeserializeObject(json);
+
+ // return bot;
+ // }
+
+ public async void SaveBotBackup(BotBackup botBackup)
+ {
+ await _botRepository.InsertBotAsync(botBackup);
+ }
+ }
+}
\ 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..b6c734c
--- /dev/null
+++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs
@@ -0,0 +1,127 @@
+using Managing.Domain.Bots;
+using MediatR;
+using static Managing.Common.Enums;
+using Managing.Application.Abstractions;
+using Managing.Core;
+using Managing.Domain.MoneyManagements;
+using Newtonsoft.Json;
+using Managing.Application.Bots;
+
+namespace Managing.Application.ManageBot
+{
+ public class LoadBackupBotCommandHandler : IRequestHandler
+ {
+ private readonly IBotFactory _botFactory;
+ private readonly ITaskCache _taskCache;
+ private readonly IMoneyManagementService _moneyManagementService;
+ private readonly IBotRepository _botRepository;
+ private readonly IMediator _mediator;
+
+ public LoadBackupBotCommandHandler(
+ IBotFactory botFactory,
+ ITaskCache taskCache,
+ IMoneyManagementService moneyManagementService,
+ IBotRepository botRepository,
+ IMediator mediator)
+ {
+ _botFactory = botFactory;
+ _taskCache = taskCache;
+ _moneyManagementService = moneyManagementService;
+ _botRepository = botRepository;
+ _mediator = mediator;
+ }
+
+ public Task Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
+ {
+ BotStatus botStatus = BotStatus.Down;
+ var backupBots = _botRepository.GetBots();
+ var result = new Dictionary();
+
+ foreach (var backupBot in backupBots)
+ {
+ // Check if bot is existing in cache
+ switch (backupBot.BotType)
+ {
+ case BotType.SimpleBot:
+ var simpleBot = _taskCache.Get(backupBot.Name);
+ if (simpleBot == null)
+ {
+ StartBot(request, backupBot);
+ simpleBot.LoadBackup(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 = _taskCache.Get(backupBot.Name);
+ if (scalpingBot == null)
+ {
+ StartBot(request, backupBot);
+ scalpingBot.LoadBackup(backupBot);
+ result.Add(scalpingBot.GetName(), BotStatus.Backup);
+ }
+ else
+ {
+ result.Add(scalpingBot.GetName(), MiscExtensions.ParseEnum(scalpingBot.GetStatus()));
+ }
+ break;
+ default:
+ result.Add(backupBot.Name, BotStatus.Down);
+ break;
+ }
+ }
+
+
+ return Task.FromResult(botStatus.ToString());
+ }
+
+ private void StartBot(LoadBackupBotCommand request, BotBackup backupBot)
+ {
+ switch (backupBot.BotType)
+ {
+ case BotType.SimpleBot:
+ Func> simpleBot = () => Task.FromResult(_botFactory.CreateSimpleBot(request.Name, null));
+ var bot1 = _taskCache.AddOrGetExisting(request.Name, simpleBot).Result;
+ bot1.LoadBackup(backupBot);
+ break;
+ case BotType.ScalpingBot:
+ var data = JsonConvert.DeserializeObject(backupBot.Data);
+ Func> scalpingBot = () => Task.FromResult(_botFactory.CreateScalpingBot(
+ data.AccountName,
+ data.MoneyManagement,
+ data.Name,
+ data.Ticker,
+ data.Scenario,
+ data.Timeframe,
+ data.IsForWatchingOnly));
+ var bot2 = _taskCache.AddOrGetExisting(request.Name, scalpingBot).Result;
+ bot2.LoadBackup(backupBot);
+ break;
+ case BotType.FlippingBot:
+ var dataFlippingBot = JsonConvert.DeserializeObject(backupBot.Data);
+ Func> flippingBot = () => Task.FromResult(_botFactory.CreateFlippingBot(
+ dataFlippingBot.AccountName,
+ dataFlippingBot.MoneyManagement,
+ dataFlippingBot.Name,
+ dataFlippingBot.Ticker,
+ dataFlippingBot.Scenario,
+ dataFlippingBot.Timeframe,
+ dataFlippingBot.IsForWatchingOnly));
+ var bot3 = _taskCache.AddOrGetExisting(request.Name, flippingBot).Result;
+ bot3.LoadBackup(backupBot);
+ break;
+ };
+ }
+ }
+
+ public class LoadBackupBotCommand : IRequest
+ {
+ public string Name { get; internal set; }
+ public string AccountName { get; internal set; }
+ }
+}
diff --git a/src/Managing.Bootstrap/ApiBootstrap.cs b/src/Managing.Bootstrap/ApiBootstrap.cs
index f90c98a..0dfbc24 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,6 +106,7 @@ public static class ApiBootstrap
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
// Cache
services.AddDistributedMemoryCache();
@@ -123,6 +125,7 @@ public static class ApiBootstrap
services.AddTransient();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
// Stream
services.AddSingleton();
diff --git a/src/Managing.Common/Enums.cs b/src/Managing.Common/Enums.cs
index 6bf4d83..84c7988 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
diff --git a/src/Managing.Domain/Bots/Bot.cs b/src/Managing.Domain/Bots/Bot.cs
index ca4af2a..7e3959d 100644
--- a/src/Managing.Domain/Bots/Bot.cs
+++ b/src/Managing.Domain/Bots/Bot.cs
@@ -1,4 +1,5 @@
-using static Managing.Common.Enums;
+using System.Text.Json;
+using static Managing.Common.Enums;
namespace Managing.Domain.Bots
{
@@ -14,6 +15,7 @@ namespace Managing.Domain.Bots
public int Interval { get; set; }
public BotStatus Status { get; set; }
private CancellationTokenSource CancellationToken { get; set; }
+ public string Data { get; set; }
public Bot(string name)
{
@@ -71,5 +73,8 @@ namespace Managing.Domain.Bots
{
return Name;
}
+
+ public abstract string GetBackup();
+ 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..5ea153c
--- /dev/null
+++ b/src/Managing.Domain/Bots/BotBackup.cs
@@ -0,0 +1,9 @@
+using Managing.Domain.MoneyManagements;
+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..9c2c30f 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();
+ string GetBackup();
+ 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..4147431
--- /dev/null
+++ b/src/Managing.Infrastructure.Database/BotRepository.cs
@@ -0,0 +1,41 @@
+using Managing.Domain.Bots;
+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 = _botRepository.FindOne(b => b.Name == bot.Name);
+ var dto = MongoMappers.Map(bot);
+ dto.Id = b.Id;
+ _botRepository.Update(dto);
+ }
+
+ public void DeleteBotByName(string name)
+ {
+ var bot = _botRepository.FindOne(b => b.Name == name);
+ _botRepository.DeleteById(bot.Id.ToString());
+ }
+}
\ 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..9a2c864 100644
--- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs
+++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs
@@ -1,5 +1,7 @@
-using Managing.Domain.Accounts;
+using System.Reflection.Metadata.Ecma335;
+using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
+using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
@@ -650,5 +652,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
}