Merge pull request #1 from CryptoOda/update-global
Add backup management
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.local.yml up -d
|
||||
@@ -32,13 +32,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.Lowpro.json">
|
||||
<Content Update="appsettings.Oda.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="appsettings.Oda-docker.json">
|
||||
<Content Update="appsettings.Prod.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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) =>
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Workers;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Workers;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
},
|
||||
"InfluxDb": {
|
||||
"Url": "http://influxdb:8086/",
|
||||
"Organization": "",
|
||||
"Token": ""
|
||||
"Organization": "managing-org",
|
||||
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ManagingDatabase": {
|
||||
"ConnectionString": "mongodb://localhost:27017",
|
||||
"DatabaseName": "ManagingDb",
|
||||
"DatabaseName": "ManagingDb"
|
||||
},
|
||||
"InfluxDb": {
|
||||
"Url": "http://localhost:8086/",
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="appsettings.Prod.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="appsettings.Oda-sandbox.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
@@ -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<Program>();
|
||||
|
||||
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<BotManagerWorker>();
|
||||
|
||||
// App
|
||||
var app = builder.Build();
|
||||
|
||||
23
src/Managing.Api/Workers/BotManagerWorker.cs
Normal file
23
src/Managing.Api/Workers/BotManagerWorker.cs
Normal file
@@ -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<BotManagerWorker> logger,
|
||||
IMediator mediadior,
|
||||
IWorkerService workerService)
|
||||
: BaseWorker<BotManagerWorker>(WorkerType.BotManager,
|
||||
logger,
|
||||
TimeSpan.FromMinutes(1),
|
||||
workerService)
|
||||
{
|
||||
protected override async Task Run(CancellationToken cancellationToken)
|
||||
{
|
||||
var loadBackupBotCommand = new LoadBackupBotCommand();
|
||||
await mediadior.Send(loadBackupBotCommand, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"InfluxDb": {
|
||||
"Url": "http://influxdb:8086/",
|
||||
"Organization": "managing-org",
|
||||
"Token": ""
|
||||
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
},
|
||||
"InfluxDb": {
|
||||
"Url": "http://localhost:8086/",
|
||||
"Organization": "",
|
||||
"Token": ""
|
||||
"Organization": "managing-org",
|
||||
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
public interface IBotRepository
|
||||
{
|
||||
Task InsertBotAsync(BotBackup bot);
|
||||
IEnumerable<BotBackup> GetBots();
|
||||
Task UpdateBackupBot(BotBackup bot);
|
||||
void DeleteBotBackup(string botName);
|
||||
}
|
||||
@@ -28,13 +28,14 @@ namespace Managing.Application.Tests
|
||||
var discordService = new Mock<IMessengerService>().Object;
|
||||
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
|
||||
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
|
||||
var botService = new Mock<IBotService>().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<double>();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<T> : BackgroundService where T : class
|
||||
{
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
34
src/Managing.Application/Abstractions/IBotService.cs
Normal file
34
src/Managing.Application/Abstractions/IBotService.cs
Normal file
@@ -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<ITradingBot> GetActiveBots();
|
||||
IEnumerable<BotBackup> 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<string> StopBot(string requestName);
|
||||
Task<bool> DeleteBot(string requestName);
|
||||
Task<string> RestartBot(string requestName);
|
||||
}
|
||||
@@ -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<TradingBot> _tradingBotLogger;
|
||||
private readonly ITradingService _tradingService;
|
||||
private readonly IBotService _botService;
|
||||
|
||||
public BotFactory(
|
||||
IExchangeService exchangeService,
|
||||
ILogger<TradingBot> 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);
|
||||
}
|
||||
|
||||
@@ -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,12 +33,12 @@ namespace Managing.Application.Bots
|
||||
timeframe,
|
||||
accountService,
|
||||
messengerService,
|
||||
botService,
|
||||
isForBacktest,
|
||||
isForWatchingOnly,
|
||||
flipPosition: true)
|
||||
{
|
||||
BotType = BotType.FlippingBot;
|
||||
Start();
|
||||
}
|
||||
|
||||
public sealed override void Start()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<TradingBot> Logger;
|
||||
private readonly Workflow _workflow;
|
||||
private readonly IBotService _botService;
|
||||
private Workflow _workflow;
|
||||
|
||||
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow) : base(name)
|
||||
public SimpleBot(string name, ILogger<TradingBot> 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<Workflow>(backup.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IStrategy> 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()))
|
||||
@@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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,7 +392,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task OpenPosition(Signal signal)
|
||||
{
|
||||
// Check if a position is already open
|
||||
@@ -362,7 +400,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
||||
&& 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
|
||||
{
|
||||
@@ -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,7 +542,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
var command = new ClosePositionCommand(position, lastPrice);
|
||||
try
|
||||
{
|
||||
var closedPosition = await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
|
||||
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<TradingBotBackup>(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<Signal> Signals { get; set; }
|
||||
public List<Position> 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<DateTime, decimal> WalletBalances { get; set; }
|
||||
public MoneyManagement MoneyManagement { get; set; }
|
||||
}
|
||||
278
src/Managing.Application/ManageBot/BotService.cs
Normal file
278
src/Managing.Application/ManageBot/BotService.cs
Normal file
@@ -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<TradingBot> _tradingBotLogger;
|
||||
private readonly ITradingService _tradingService;
|
||||
|
||||
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
|
||||
new ConcurrentDictionary<string, BotTaskWrapper>();
|
||||
|
||||
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
|
||||
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> 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<ITradingBot> GetActiveBots()
|
||||
{
|
||||
return _botTasks.Values
|
||||
.Where(wrapper => typeof(ITradingBot).IsAssignableFrom(wrapper.BotType))
|
||||
.Select(wrapper => wrapper.BotInstance as ITradingBot)
|
||||
.Where(bot => bot != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<BotBackup> 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<TradingBotBackup>(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<TradingBotBackup>(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<string> 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<bool> DeleteBot(string botName)
|
||||
{
|
||||
if (_botTasks.TryRemove(botName, out _))
|
||||
{
|
||||
_botRepository.DeleteBotBackup(botName);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public Task<string> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,25 +8,16 @@ namespace Managing.Application.ManageBot;
|
||||
public class DeleteBotCommandHandler : IRequestHandler<DeleteBotCommand, bool>
|
||||
{
|
||||
private readonly ILogger<DeleteBotCommandHandler> _log;
|
||||
private readonly ITaskCache _taskCache;
|
||||
private readonly IBotService _botService;
|
||||
|
||||
public DeleteBotCommandHandler(ITaskCache taskCache, ILogger<DeleteBotCommandHandler> log)
|
||||
public DeleteBotCommandHandler(ILogger<DeleteBotCommandHandler> log, IBotService botService)
|
||||
{
|
||||
_taskCache = taskCache;
|
||||
_log = log;
|
||||
_botService = botService;
|
||||
}
|
||||
|
||||
public Task<bool> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<GetActiveBotsCommand, List<ITradingBot>>
|
||||
public class GetActiveBotsCommandHandler(IBotService botService)
|
||||
: IRequestHandler<GetActiveBotsCommand, List<ITradingBot>>
|
||||
{
|
||||
private readonly ITaskCache taskCache;
|
||||
|
||||
public GetActiveBotsCommandHandler(ITaskCache taskCache)
|
||||
{
|
||||
this.taskCache = taskCache;
|
||||
}
|
||||
|
||||
public Task<List<ITradingBot>> Handle(GetActiveBotsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var cachedTask = taskCache.GetCache<AsyncLazy<ITradingBot>>();
|
||||
var result = new List<ITradingBot>();
|
||||
|
||||
foreach (var item in cachedTask)
|
||||
{
|
||||
result.Add(item.Value.Result);
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
return Task.FromResult(botService.GetActiveBots());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<LoadBackupBotCommand, string>
|
||||
{
|
||||
private readonly IBotService _botService;
|
||||
private readonly ILogger<LoadBackupBotCommandHandler> _logger;
|
||||
|
||||
public LoadBackupBotCommandHandler(
|
||||
ILogger<LoadBackupBotCommandHandler> logger, IBotService botService)
|
||||
{
|
||||
_logger = logger;
|
||||
_botService = botService;
|
||||
}
|
||||
|
||||
public Task<string> Handle(LoadBackupBotCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
BotStatus botStatus = BotStatus.Down;
|
||||
var backupBots = _botService.GetSavedBots();
|
||||
var activeBots = _botService.GetActiveBots();
|
||||
var result = new Dictionary<string, BotStatus>();
|
||||
|
||||
_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<BotStatus>(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<BotStatus>(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<string>
|
||||
{
|
||||
}
|
||||
@@ -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<RestartBotCommand, string>
|
||||
{
|
||||
private readonly ITaskCache _taskCache;
|
||||
private readonly IBotService _botService;
|
||||
|
||||
public RestartBotCommandHandler(ITaskCache taskCache)
|
||||
public RestartBotCommandHandler(IBotService botService)
|
||||
{
|
||||
_taskCache = taskCache;
|
||||
_botService = botService;
|
||||
}
|
||||
|
||||
public Task<string> Handle(RestartBotCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
switch (request.BotType)
|
||||
{
|
||||
case BotType.SimpleBot:
|
||||
var simpleBot = _taskCache.Get<IBot>(request.Name);
|
||||
simpleBot.Restart();
|
||||
return Task.FromResult(simpleBot.GetStatus());
|
||||
case BotType.ScalpingBot:
|
||||
var scalpingBot = _taskCache.Get<ITradingBot>(request.Name);
|
||||
scalpingBot.Restart();
|
||||
return Task.FromResult(scalpingBot.GetStatus());
|
||||
case BotType.FlippingBot:
|
||||
var flippingBot = _taskCache.Get<ITradingBot>(request.Name);
|
||||
flippingBot.Restart();
|
||||
return Task.FromResult(flippingBot.GetStatus());
|
||||
default:
|
||||
return Task.FromResult(BotStatus.Down.ToString());
|
||||
}
|
||||
return _botService.RestartBot(request.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<StartBotCommand, string>
|
||||
{
|
||||
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,15 +26,25 @@ namespace Managing.Application.ManageBot
|
||||
switch (request.BotType)
|
||||
{
|
||||
case BotType.SimpleBot:
|
||||
Func<Task<IBot>> 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<Task<ITradingBot>> 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<Task<ITradingBot>> 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());
|
||||
}
|
||||
|
||||
@@ -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<StopBotCommand, string>
|
||||
{
|
||||
private readonly ITaskCache _taskCache;
|
||||
private readonly IBotService _botService;
|
||||
|
||||
public StopBotCommandHandler(ITaskCache taskCache)
|
||||
public StopBotCommandHandler(IBotService botService)
|
||||
{
|
||||
_taskCache = taskCache;
|
||||
_botService = botService;
|
||||
}
|
||||
|
||||
public Task<string> Handle(StopBotCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
switch (request.BotType)
|
||||
{
|
||||
case BotType.SimpleBot:
|
||||
var simpleBot = _taskCache.Get<IBot>(request.Name);
|
||||
simpleBot.Stop();
|
||||
return Task.FromResult(simpleBot.GetStatus());
|
||||
case BotType.ScalpingBot:
|
||||
var scalpingBot = _taskCache.Get<ITradingBot>(request.Name);
|
||||
scalpingBot.Stop();
|
||||
return Task.FromResult(scalpingBot.GetStatus());
|
||||
case BotType.FlippingBot:
|
||||
var flippingBot = _taskCache.Get<ITradingBot>(request.Name);
|
||||
flippingBot.Stop();
|
||||
return Task.FromResult(flippingBot.GetStatus());
|
||||
default:
|
||||
return Task.FromResult(BotStatus.Down.ToString());
|
||||
}
|
||||
return _botService.StopBot(request.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<IUserRepository, UserRepository>();
|
||||
services.AddTransient<IStatisticRepository, StatisticRepository>();
|
||||
services.AddTransient<IWorkflowRepository, WorkflowRepository>();
|
||||
services.AddTransient<IBotRepository, BotRepository>();
|
||||
services.AddTransient<IWorkerRepository, WorkerRepository>();
|
||||
|
||||
// Cache
|
||||
services.AddDistributedMemoryCache();
|
||||
services.AddTransient<ICacheService, CacheService>();
|
||||
services.AddTransient<ITaskCache, TaskCache>();
|
||||
services.AddSingleton<ITaskCache, TaskCache>();
|
||||
|
||||
// Processors
|
||||
services.AddTransient<IExchangeProcessor, EvmProcessor>();
|
||||
@@ -123,6 +126,8 @@ public static class ApiBootstrap
|
||||
services.AddTransient<IExchangeStream, ExchangeStream>();
|
||||
services.AddSingleton<IMessengerService, MessengerService>();
|
||||
services.AddSingleton<IDiscordService, DiscordService>();
|
||||
services.AddSingleton<IBotService, BotService>();
|
||||
services.AddSingleton<IWorkerService, WorkerService>();
|
||||
|
||||
// Stream
|
||||
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();
|
||||
|
||||
@@ -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<ITradingService, TradingService>();
|
||||
services.AddSingleton<ISettingsService, SettingsService>();
|
||||
services.AddSingleton<IBacktester, Backtester>();
|
||||
services.AddSingleton<IBotService, BotService>();
|
||||
|
||||
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
||||
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
||||
@@ -93,6 +95,7 @@ public static class WorkersBootstrap
|
||||
services.AddTransient<ISettingsRepository, SettingsRepository>();
|
||||
services.AddTransient<ITradingRepository, TradingRepository>();
|
||||
services.AddTransient<IBacktestRepository, BacktestRepository>();
|
||||
services.AddTransient<IBotRepository, BotRepository>();
|
||||
|
||||
// Cache
|
||||
services.AddDistributedMemoryCache();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -71,5 +71,8 @@ namespace Managing.Domain.Bots
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public abstract void SaveBackup();
|
||||
public abstract void LoadBackup(BotBackup backup);
|
||||
}
|
||||
}
|
||||
8
src/Managing.Domain/Bots/BotBackup.cs
Normal file
8
src/Managing.Domain/Bots/BotBackup.cs
Normal file
@@ -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; }
|
||||
}
|
||||
@@ -8,5 +8,7 @@
|
||||
void Restart();
|
||||
string GetStatus();
|
||||
string GetName();
|
||||
void SaveBackup();
|
||||
void LoadBackup(BotBackup backup);
|
||||
}
|
||||
}
|
||||
|
||||
39
src/Managing.Infrastructure.Database/BotRepository.cs
Normal file
39
src/Managing.Infrastructure.Database/BotRepository.cs
Normal file
@@ -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<BotDto> _botRepository;
|
||||
|
||||
public BotRepository(IMongoRepository<BotDto> botRepository)
|
||||
{
|
||||
_botRepository = botRepository;
|
||||
}
|
||||
|
||||
public async Task InsertBotAsync(BotBackup bot)
|
||||
{
|
||||
await _botRepository.InsertOneAsync(MongoMappers.Map(bot));
|
||||
}
|
||||
|
||||
public IEnumerable<BotBackup> 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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using CryptoExchange.Net.Authentication;
|
||||
using FTX.Net.Clients;
|
||||
using FTX.Net.Interfaces.Clients;
|
||||
using FTX.Net.Objects;
|
||||
using Managing.Common;
|
||||
|
||||
@@ -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<T> AddOrGetExisting<T>(string key, Func<Task<T>> valueFactory)
|
||||
{
|
||||
|
||||
var asyncLazyValue = new AsyncLazy<T>(valueFactory);
|
||||
var existingValue = (AsyncLazy<T>)_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,7 +64,8 @@ namespace Managing.Infrastructure.Storage
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
var existingValue = (AsyncLazy<T>)_cache.Get(key);
|
||||
return existingValue.Value.Result;
|
||||
if (existingValue != null) return existingValue.Value.Result;
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public virtual List<T> GetCache<T>()
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Xunit;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Tests;
|
||||
namespace Managing.Infrastructure.Tests;
|
||||
|
||||
public class SubgraphTests
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ILoginFormInput>()
|
||||
const { disconnect } = useDisconnect()
|
||||
const { address } = useAccount()
|
||||
const { isLoading, signMessageAsync } = useSignMessage({})
|
||||
const { signMessageAsync } = useSignMessage({})
|
||||
const { setCookie } = useCookie()
|
||||
|
||||
const onSubmit: SubmitHandler<ILoginFormInput> = 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{
|
||||
t.update('error', 'Error : No signature')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +80,6 @@ const LogIn = () => {
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="btn bg-primary w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
|
||||
>
|
||||
Sign and login
|
||||
|
||||
@@ -15,9 +15,13 @@ const baseOptions: UpdateOptions = {
|
||||
class Toast {
|
||||
private id: Id
|
||||
|
||||
constructor(content: string) {
|
||||
constructor(content: string, isLoading = true) {
|
||||
if (!isLoading) {
|
||||
this.id = toast(content)
|
||||
}else{
|
||||
this.id = toast.loading(content)
|
||||
}
|
||||
}
|
||||
|
||||
update(type: TypeOptions, content: string, opts?: any) {
|
||||
const options = { ...baseOptions, ...opts }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ColorSwatchIcon, TrashIcon, XIcon } from '@heroicons/react/solid'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
@@ -17,14 +17,17 @@ const BacktestScanner: React.FC = () => {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const client = new BacktestClient({}, apiUrl)
|
||||
|
||||
const { isLoading, refetch } = useQuery({
|
||||
onSuccess: (data) => {
|
||||
setBacktest(data)
|
||||
},
|
||||
const { isLoading, refetch, data: backtests } = useQuery({
|
||||
queryFn: () => client.backtest_Backtests(),
|
||||
queryKey: ['backtests'],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (backtests) {
|
||||
setBacktest(backtests)
|
||||
}
|
||||
}, [backtests])
|
||||
|
||||
function deleteAllBacktests() {
|
||||
const t = new Toast('Deleting all backtests')
|
||||
client
|
||||
@@ -114,7 +117,7 @@ const BacktestScanner: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<BacktestTable list={backtestingResult} isFetching={isLoading} />
|
||||
<BacktestTable list={backtestingResult} isFetching={isLoading} setBacktests={setBacktest} />
|
||||
|
||||
<BacktestModal
|
||||
showModal={showModal}
|
||||
|
||||
Reference in New Issue
Block a user