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
|
- [ ] Create method to update the money management use by the bot
|
||||||
- [ ] Implement from/to tickers array pattern
|
- [ ] Implement from/to tickers array pattern
|
||||||
- [ ] Extract all managing trade method into a TradingBox class => Create composable trading bot type easily
|
- [ ] 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
|
# Front-end
|
||||||
## Improve Account page
|
## Improve Account page
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ cd ..
|
|||||||
cd .\src\
|
cd .\src\
|
||||||
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
|
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 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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Update="appsettings.Lowpro.json">
|
<Content Update="appsettings.Oda.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Update="appsettings.json">
|
<Content Update="appsettings.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Update="appsettings.Oda-docker.json">
|
<Content Update="appsettings.Prod.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ using Serilog.Sinks.Elasticsearch;
|
|||||||
|
|
||||||
// Builder
|
// Builder
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Configuration
|
builder.Configuration.SetBasePath(System.AppContext.BaseDirectory);
|
||||||
.AddEnvironmentVariables();
|
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||||
|
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
|
||||||
|
|
||||||
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Application.Workers;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
using static Managing.Common.Enums;
|
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;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Workers;
|
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;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Workers;
|
namespace Managing.Api.Workers.Workers;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
|
using Managing.Application.Workers;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Application.Workers;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using static Managing.Common.Enums;
|
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;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Workers;
|
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;
|
using Managing.Common;
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Workers;
|
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;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Workers;
|
namespace Managing.Api.Workers.Workers;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Application.Workers;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
},
|
},
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://influxdb:8086/",
|
"Url": "http://influxdb:8086/",
|
||||||
"Organization": "",
|
"Organization": "managing-org",
|
||||||
"Token": ""
|
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
||||||
},
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"ManagingDatabase": {
|
"ManagingDatabase": {
|
||||||
"ConnectionString": "mongodb://localhost:27017",
|
"ConnectionString": "mongodb://localhost:27017",
|
||||||
"DatabaseName": "ManagingDb",
|
"DatabaseName": "ManagingDb"
|
||||||
},
|
},
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://localhost:8086/",
|
"Url": "http://localhost:8086/",
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<Content Update="appsettings.json">
|
<Content Update="appsettings.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Update="appsettings.Prod.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Update="appsettings.Oda-sandbox.json">
|
<Content Update="appsettings.Oda-sandbox.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
|
|||||||
using Managing.Api.Authorization;
|
using Managing.Api.Authorization;
|
||||||
using Managing.Api.Exceptions;
|
using Managing.Api.Exceptions;
|
||||||
using Managing.Api.Filters;
|
using Managing.Api.Filters;
|
||||||
|
using Managing.Api.Workers;
|
||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
using Managing.Bootstrap;
|
using Managing.Bootstrap;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
@@ -17,12 +18,15 @@ using Serilog.Sinks.Elasticsearch;
|
|||||||
|
|
||||||
// Builder
|
// Builder
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
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",
|
.AddJsonFile($"config.{builder.Environment.EnvironmentName}.json",
|
||||||
optional: true, reloadOnChange: true);
|
optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
builder.Configuration.AddEnvironmentVariables();
|
builder.Configuration.AddEnvironmentVariables();
|
||||||
builder.Configuration.AddUserSecrets<Program>();
|
builder.Configuration.AddUserSecrets<Program>();
|
||||||
|
|
||||||
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
||||||
{
|
{
|
||||||
var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-");
|
var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-");
|
||||||
@@ -123,6 +127,7 @@ builder.Services.AddSwaggerGen(options =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.WebHost.SetupDiscordBot();
|
builder.WebHost.SetupDiscordBot();
|
||||||
|
builder.Services.AddHostedService<BotManagerWorker>();
|
||||||
|
|
||||||
// App
|
// App
|
||||||
var app = builder.Build();
|
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": {
|
"InfluxDb": {
|
||||||
"Url": "http://influxdb:8086/",
|
"Url": "http://influxdb:8086/",
|
||||||
"Organization": "managing-org",
|
"Organization": "managing-org",
|
||||||
"Token": ""
|
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
||||||
},
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"MinimumLevel": {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
},
|
},
|
||||||
"InfluxDb": {
|
"InfluxDb": {
|
||||||
"Url": "http://localhost:8086/",
|
"Url": "http://localhost:8086/",
|
||||||
"Organization": "",
|
"Organization": "managing-org",
|
||||||
"Token": ""
|
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
||||||
},
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"MinimumLevel": {
|
"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 discordService = new Mock<IMessengerService>().Object;
|
||||||
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
|
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
|
||||||
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
|
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
|
||||||
|
var botService = new Mock<IBotService>().Object;
|
||||||
_botFactory = new BotFactory(
|
_botFactory = new BotFactory(
|
||||||
_exchangeService,
|
_exchangeService,
|
||||||
tradingBotLogger,
|
tradingBotLogger,
|
||||||
_moneyManagementService.Object,
|
|
||||||
discordService,
|
discordService,
|
||||||
_accountService.Object,
|
_accountService.Object,
|
||||||
_tradingService.Object);
|
_tradingService.Object,
|
||||||
|
botService);
|
||||||
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger);
|
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger);
|
||||||
_elapsedTimes = new List<double>();
|
_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 Xunit;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Api.Workers;
|
namespace Managing.Application.Workers;
|
||||||
|
|
||||||
public abstract class BaseWorker<T> : BackgroundService where T : class
|
public abstract class BaseWorker<T> : BackgroundService where T : class
|
||||||
{
|
{
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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
|
public class BotFactory : IBotFactory
|
||||||
{
|
{
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
|
||||||
private readonly IExchangeService _exchangeService;
|
private readonly IExchangeService _exchangeService;
|
||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly ILogger<TradingBot> _tradingBotLogger;
|
private readonly ILogger<TradingBot> _tradingBotLogger;
|
||||||
private readonly ITradingService _tradingService;
|
private readonly ITradingService _tradingService;
|
||||||
|
private readonly IBotService _botService;
|
||||||
|
|
||||||
public BotFactory(
|
public BotFactory(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
ILogger<TradingBot> tradingBotLogger,
|
ILogger<TradingBot> tradingBotLogger,
|
||||||
IMoneyManagementService moneyManagementService,
|
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ITradingService tradingService)
|
ITradingService tradingService,
|
||||||
|
IBotService botService)
|
||||||
{
|
{
|
||||||
_tradingBotLogger = tradingBotLogger;
|
_tradingBotLogger = tradingBotLogger;
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
_moneyManagementService = moneyManagementService;
|
|
||||||
_messengerService = messengerService;
|
_messengerService = messengerService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
|
_botService = botService;
|
||||||
}
|
}
|
||||||
|
|
||||||
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
|
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)
|
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,
|
interval,
|
||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
|
_botService,
|
||||||
isForWatchingOnly: isForWatchingOnly);
|
isForWatchingOnly: isForWatchingOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ namespace Managing.Application.Bots.Base
|
|||||||
interval,
|
interval,
|
||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
|
_botService,
|
||||||
true,
|
true,
|
||||||
isForWatchingOnly);
|
isForWatchingOnly);
|
||||||
}
|
}
|
||||||
@@ -87,6 +89,7 @@ namespace Managing.Application.Bots.Base
|
|||||||
interval,
|
interval,
|
||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
|
_botService,
|
||||||
isForWatchingOnly: isForWatchingOnly);
|
isForWatchingOnly: isForWatchingOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +107,7 @@ namespace Managing.Application.Bots.Base
|
|||||||
interval,
|
interval,
|
||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
|
_botService,
|
||||||
true,
|
true,
|
||||||
isForWatchingOnly);
|
isForWatchingOnly);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Managing.Application.Abstractions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
@@ -18,25 +19,26 @@ namespace Managing.Application.Bots
|
|||||||
Timeframe timeframe,
|
Timeframe timeframe,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
|
IBotService botService,
|
||||||
bool isForBacktest = false,
|
bool isForBacktest = false,
|
||||||
bool isForWatchingOnly = false)
|
bool isForWatchingOnly = false)
|
||||||
: base(accountName,
|
: base(accountName,
|
||||||
moneyManagement,
|
moneyManagement,
|
||||||
name,
|
name,
|
||||||
ticker,
|
ticker,
|
||||||
scenario,
|
scenario,
|
||||||
exchangeService,
|
exchangeService,
|
||||||
logger,
|
logger,
|
||||||
tradingService,
|
tradingService,
|
||||||
timeframe,
|
timeframe,
|
||||||
accountService,
|
accountService,
|
||||||
messengerService,
|
messengerService,
|
||||||
isForBacktest,
|
botService,
|
||||||
isForWatchingOnly,
|
isForBacktest,
|
||||||
flipPosition: true)
|
isForWatchingOnly,
|
||||||
|
flipPosition: true)
|
||||||
{
|
{
|
||||||
BotType = BotType.FlippingBot;
|
BotType = BotType.FlippingBot;
|
||||||
Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override void Start()
|
public sealed override void Start()
|
||||||
@@ -46,4 +48,4 @@ namespace Managing.Application.Bots
|
|||||||
Logger.LogInformation($"Starting {Name} bot - Status : {Status}");
|
Logger.LogInformation($"Starting {Name} bot - Status : {Status}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Managing.Application.Abstractions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
@@ -18,6 +19,7 @@ namespace Managing.Application.Bots
|
|||||||
Timeframe timeframe,
|
Timeframe timeframe,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
|
IBotService botService,
|
||||||
bool isForBacktest = false,
|
bool isForBacktest = false,
|
||||||
bool isForWatchingOnly = false)
|
bool isForWatchingOnly = false)
|
||||||
: base(accountName,
|
: base(accountName,
|
||||||
@@ -31,11 +33,11 @@ namespace Managing.Application.Bots
|
|||||||
timeframe,
|
timeframe,
|
||||||
accountService,
|
accountService,
|
||||||
messengerService,
|
messengerService,
|
||||||
|
botService,
|
||||||
isForBacktest,
|
isForBacktest,
|
||||||
isForWatchingOnly)
|
isForWatchingOnly)
|
||||||
{
|
{
|
||||||
BotType = BotType.ScalpingBot;
|
BotType = BotType.ScalpingBot;
|
||||||
Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override void 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 Managing.Domain.Workflows;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Bots
|
namespace Managing.Application.Bots
|
||||||
{
|
{
|
||||||
public class SimpleBot : Bot
|
public class SimpleBot : Bot
|
||||||
{
|
{
|
||||||
public readonly ILogger<TradingBot> Logger;
|
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;
|
Logger = logger;
|
||||||
|
_botService = botService;
|
||||||
_workflow = workflow;
|
_workflow = workflow;
|
||||||
Interval = 100;
|
Interval = 100;
|
||||||
Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Start()
|
public override void Start()
|
||||||
@@ -32,8 +37,20 @@ namespace Managing.Application.Bots
|
|||||||
Logger.LogInformation(Identifier);
|
Logger.LogInformation(Identifier);
|
||||||
Logger.LogInformation(DateTime.Now.ToString());
|
Logger.LogInformation(DateTime.Now.ToString());
|
||||||
await _workflow.Execute();
|
await _workflow.Execute();
|
||||||
|
SaveBackup();
|
||||||
Logger.LogInformation("__________________________________________________");
|
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.Strategies;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Bots;
|
namespace Managing.Application.Bots;
|
||||||
@@ -22,6 +23,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
public readonly IMessengerService MessengerService;
|
public readonly IMessengerService MessengerService;
|
||||||
public readonly IAccountService AccountService;
|
public readonly IAccountService AccountService;
|
||||||
private readonly ITradingService TradingService;
|
private readonly ITradingService TradingService;
|
||||||
|
private readonly IBotService BotService;
|
||||||
|
|
||||||
public Account Account { get; set; }
|
public Account Account { get; set; }
|
||||||
public HashSet<IStrategy> Strategies { get; set; }
|
public HashSet<IStrategy> Strategies { get; set; }
|
||||||
@@ -54,6 +56,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
Timeframe timeframe,
|
Timeframe timeframe,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
|
IBotService botService,
|
||||||
bool isForBacktest = false,
|
bool isForBacktest = false,
|
||||||
bool isForWatchingOnly = false,
|
bool isForWatchingOnly = false,
|
||||||
bool flipPosition = false)
|
bool flipPosition = false)
|
||||||
@@ -63,6 +66,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
AccountService = accountService;
|
AccountService = accountService;
|
||||||
MessengerService = messengerService;
|
MessengerService = messengerService;
|
||||||
TradingService = tradingService;
|
TradingService = tradingService;
|
||||||
|
BotService = botService;
|
||||||
|
|
||||||
IsForWatchingOnly = isForWatchingOnly;
|
IsForWatchingOnly = isForWatchingOnly;
|
||||||
FlipPosition = flipPosition;
|
FlipPosition = flipPosition;
|
||||||
@@ -97,7 +101,17 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
LoadScenario();
|
LoadScenario();
|
||||||
await PreloadCandles();
|
await PreloadCandles();
|
||||||
await CancelAllOrders();
|
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);
|
await InitWorker(Run);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +157,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
public async Task Run()
|
public async Task Run()
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"____________________{Name}____________________");
|
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;
|
var previousCandleCount = Candles.Count;
|
||||||
|
|
||||||
@@ -158,6 +173,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
if (!IsForWatchingOnly)
|
if (!IsForWatchingOnly)
|
||||||
await ManagePositions();
|
await ManagePositions();
|
||||||
|
|
||||||
|
if (!IsForBacktest)
|
||||||
|
SaveBackup();
|
||||||
|
|
||||||
await UpdateWalletBalances();
|
await UpdateWalletBalances();
|
||||||
Logger.LogInformation($"Candles : {Candles.Count}");
|
Logger.LogInformation($"Candles : {Candles.Count}");
|
||||||
Logger.LogInformation($"Signals : {Signals.Count}");
|
Logger.LogInformation($"Signals : {Signals.Count}");
|
||||||
@@ -168,6 +186,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
private async Task PreloadCandles()
|
private async Task PreloadCandles()
|
||||||
{
|
{
|
||||||
|
if (Candles.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
|
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
|
||||||
|
|
||||||
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
||||||
@@ -203,7 +224,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
signal.Status = SignalStatus.Expired;
|
signal.Status = SignalStatus.Expired;
|
||||||
|
|
||||||
var signalText = $"{Scenario} trigger a signal. Signal told you " +
|
var signalText = $"{Scenario} trigger a signal. Signal told you " +
|
||||||
$"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
|
$"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
|
||||||
|
|
||||||
Logger.LogInformation(signalText);
|
Logger.LogInformation(signalText);
|
||||||
|
|
||||||
@@ -265,7 +286,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
{
|
{
|
||||||
Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");
|
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))
|
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 :
|
// For backtesting or force close if not executed on exchange :
|
||||||
// check if position is still open
|
// check if position is still open
|
||||||
// Check status, if still open update the status of the position
|
// 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.OriginDirection == TradeDirection.Long)
|
||||||
{
|
{
|
||||||
if (positionForSignal.StopLoss.Price >= lastCandle.Low)
|
if (positionForSignal.StopLoss.Price >= lastCandle.Low)
|
||||||
{
|
{
|
||||||
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}");
|
await LogInformation(
|
||||||
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
|
$"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}");
|
||||||
|
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
|
||||||
|
positionForSignal.StopLoss.Price, true);
|
||||||
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
|
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
else if (positionForSignal.TakeProfit1.Price <= lastCandle.High
|
else if (positionForSignal.TakeProfit1.Price <= lastCandle.High
|
||||||
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
|
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
|
||||||
{
|
{
|
||||||
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}");
|
await LogInformation(
|
||||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
|
$"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);
|
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High)
|
else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High)
|
||||||
{
|
{
|
||||||
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}");
|
await LogInformation(
|
||||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
|
$"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}");
|
||||||
|
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
|
||||||
|
positionForSignal.TakeProfit2.Price, true);
|
||||||
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
|
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (positionForSignal.StopLoss.Price <= lastCandle.High)
|
||||||
{
|
{
|
||||||
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}");
|
await LogInformation(
|
||||||
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
|
$"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}");
|
||||||
|
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
|
||||||
|
positionForSignal.StopLoss.Price, true);
|
||||||
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
|
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low
|
else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low
|
||||||
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
|
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
|
||||||
{
|
{
|
||||||
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}");
|
await LogInformation(
|
||||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
|
$"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);
|
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low)
|
else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low)
|
||||||
{
|
{
|
||||||
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}");
|
await LogInformation(
|
||||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
|
$"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}");
|
||||||
|
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
|
||||||
|
positionForSignal.TakeProfit2.Price, true);
|
||||||
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
|
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened");
|
Logger.LogInformation(
|
||||||
|
$"Position {signal.Identifier} don't need to be update. Position still opened");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,16 +392,17 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private async Task OpenPosition(Signal signal)
|
private async Task OpenPosition(Signal signal)
|
||||||
{
|
{
|
||||||
// Check if a position is already open
|
// Check if a position is already open
|
||||||
Logger.LogInformation($"Opening position for {signal.Identifier}");
|
Logger.LogInformation($"Opening position for {signal.Identifier}");
|
||||||
|
|
||||||
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
||||||
&& p.SignalIdentifier != signal.Identifier);
|
&& p.SignalIdentifier != signal.Identifier);
|
||||||
|
|
||||||
var lastPrice = IsForBacktest ? Candles.Last().Close : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
|
var lastPrice = IsForBacktest
|
||||||
|
? Candles.Last().Close
|
||||||
|
: ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
|
||||||
|
|
||||||
// If position open
|
// If position open
|
||||||
if (openedPosition != null)
|
if (openedPosition != null)
|
||||||
@@ -373,7 +413,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
if (openedPosition.OriginDirection == signal.Direction)
|
if (openedPosition.OriginDirection == signal.Direction)
|
||||||
{
|
{
|
||||||
// An operation is already open for the same 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);
|
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -386,11 +427,13 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||||
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
|
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
|
||||||
await OpenPosition(signal);
|
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
|
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);
|
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,12 +442,14 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
{
|
{
|
||||||
if (!CanOpenPosition(signal))
|
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);
|
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
return;
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -459,9 +504,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
var lastPosition = Positions.LastOrDefault(p => p.IsFinished()
|
var lastPosition = Positions.LastOrDefault(p => p.IsFinished()
|
||||||
&& p.SignalIdentifier != signal.Identifier
|
&& p.SignalIdentifier != signal.Identifier
|
||||||
&& p.ProfitAndLoss.Realized < 0
|
&& p.ProfitAndLoss.Realized < 0
|
||||||
&& p.OriginDirection == signal.Direction);
|
&& p.OriginDirection == signal.Direction);
|
||||||
|
|
||||||
if (lastPosition == null)
|
if (lastPosition == null)
|
||||||
return true;
|
return true;
|
||||||
@@ -472,15 +517,18 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
return positionSignal.Date < tenCandleAgo.Date;
|
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
|
// If trade is the 2nd Take profit
|
||||||
tradeToClose.Quantity = position.TakeProfit2.Quantity;
|
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}");
|
$"- Closing Position : {tradeClosingPosition}");
|
||||||
|
|
||||||
// Get status of position before closing it. The position might be already close by the exchange
|
// Get status of position before closing it. The position might be already close by the exchange
|
||||||
@@ -494,8 +542,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
var command = new ClosePositionCommand(position, lastPrice);
|
var command = new ClosePositionCommand(position, lastPrice);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var closedPosition = await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
|
var closedPosition =
|
||||||
.Handle(command);
|
await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
|
||||||
|
.Handle(command);
|
||||||
|
|
||||||
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
||||||
{
|
{
|
||||||
@@ -521,7 +570,6 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,7 +582,8 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
position.SignalIdentifier = previousPosition.SignalIdentifier;
|
position.SignalIdentifier = previousPosition.SignalIdentifier;
|
||||||
Positions[positionIndex] = position;
|
Positions[positionIndex] = position;
|
||||||
SetSignalStatus(position.SignalIdentifier, SignalStatus.Expired);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -551,7 +600,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
private async Task CancelAllOrders()
|
private async Task CancelAllOrders()
|
||||||
{
|
{
|
||||||
if (!IsForBacktest)
|
if (!IsForBacktest && !IsForWatchingOnly)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -648,4 +697,52 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
await MessengerService.SendTradeMessage(message, isBadBehavior);
|
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>
|
public class DeleteBotCommandHandler : IRequestHandler<DeleteBotCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly ILogger<DeleteBotCommandHandler> _log;
|
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;
|
_log = log;
|
||||||
|
_botService = botService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> Handle(DeleteBotCommand request, CancellationToken cancellationToken)
|
public Task<bool> Handle(DeleteBotCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
return _botService.DeleteBot(request.Name);
|
||||||
{
|
|
||||||
_taskCache.Invalidate(request.Name);
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_log.LogError(e.Message);
|
|
||||||
return Task.FromResult(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,15 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
using Managing.Core;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace Managing.Application.ManageBot
|
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)
|
public Task<List<ITradingBot>> Handle(GetActiveBotsCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var cachedTask = taskCache.GetCache<AsyncLazy<ITradingBot>>();
|
return Task.FromResult(botService.GetActiveBots());
|
||||||
var result = new List<ITradingBot>();
|
|
||||||
|
|
||||||
foreach (var item in cachedTask)
|
|
||||||
{
|
|
||||||
result.Add(item.Value.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.Abstractions;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
using Managing.Domain.Bots;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using static Managing.Common.Enums;
|
|
||||||
|
|
||||||
namespace Managing.Application.ManageBot
|
namespace Managing.Application.ManageBot
|
||||||
{
|
{
|
||||||
public class RestartBotCommandHandler : IRequestHandler<RestartBotCommand, string>
|
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)
|
public Task<string> Handle(RestartBotCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
switch (request.BotType)
|
return _botService.RestartBot(request.Name);
|
||||||
{
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Domain.Bots;
|
using MediatR;
|
||||||
using MediatR;
|
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
@@ -9,13 +8,14 @@ namespace Managing.Application.ManageBot
|
|||||||
public class StartBotCommandHandler : IRequestHandler<StartBotCommand, string>
|
public class StartBotCommandHandler : IRequestHandler<StartBotCommand, string>
|
||||||
{
|
{
|
||||||
private readonly IBotFactory _botFactory;
|
private readonly IBotFactory _botFactory;
|
||||||
private readonly ITaskCache _taskCache;
|
private readonly IBotService _botService;
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
|
|
||||||
public StartBotCommandHandler(IBotFactory botFactory, ITaskCache taskCache, IMoneyManagementService moneyManagementService)
|
public StartBotCommandHandler(IBotFactory botFactory, IBotService botService,
|
||||||
|
IMoneyManagementService moneyManagementService)
|
||||||
{
|
{
|
||||||
_botFactory = botFactory;
|
_botFactory = botFactory;
|
||||||
_taskCache = taskCache;
|
_botService = botService;
|
||||||
_moneyManagementService = moneyManagementService;
|
_moneyManagementService = moneyManagementService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,17 +26,27 @@ namespace Managing.Application.ManageBot
|
|||||||
switch (request.BotType)
|
switch (request.BotType)
|
||||||
{
|
{
|
||||||
case BotType.SimpleBot:
|
case BotType.SimpleBot:
|
||||||
Func<Task<IBot>> simpleBot = () => Task.FromResult(_botFactory.CreateSimpleBot(request.Name, null));
|
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
||||||
return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, simpleBot).Result.GetStatus());
|
bot.Start();
|
||||||
|
_botService.AddSimpleBotToCache(bot);
|
||||||
|
return Task.FromResult(bot.GetStatus());
|
||||||
case BotType.ScalpingBot:
|
case BotType.ScalpingBot:
|
||||||
Func<Task<ITradingBot>> scalpingBot = () => Task.FromResult(_botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly));
|
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
|
||||||
return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, scalpingBot).Result.GetStatus());
|
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
|
||||||
|
sBot.Start();
|
||||||
|
_botService.AddTradingBotToCache(sBot);
|
||||||
|
return Task.FromResult(sBot.GetStatus());
|
||||||
case BotType.FlippingBot:
|
case BotType.FlippingBot:
|
||||||
Func<Task<ITradingBot>> flippingBot = () => Task.FromResult(_botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly));
|
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
|
||||||
return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, flippingBot).Result.GetStatus());
|
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
|
||||||
};
|
fBot.Start();
|
||||||
|
_botService.AddTradingBotToCache(fBot);
|
||||||
|
return Task.FromResult(fBot.GetStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
return Task.FromResult(botStatus.ToString());
|
return Task.FromResult(botStatus.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,21 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
using Managing.Domain.Bots;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using static Managing.Common.Enums;
|
|
||||||
|
|
||||||
namespace Managing.Application.ManageBot
|
namespace Managing.Application.ManageBot
|
||||||
{
|
{
|
||||||
public class StopBotCommandHandler : IRequestHandler<StopBotCommand, string>
|
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)
|
public Task<string> Handle(StopBotCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
switch (request.BotType)
|
return _botService.StopBot(request.Name);
|
||||||
{
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using DnsClient.Internal;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions;
|
|
||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
@@ -9,7 +8,6 @@ using Managing.Domain.Strategies;
|
|||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MongoDB.Driver;
|
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Trading;
|
namespace Managing.Application.Trading;
|
||||||
@@ -206,7 +204,7 @@ public class TradingService : ITradingService
|
|||||||
|
|
||||||
return _cacheService.GetOrSave($"Fee-{account.Exchange}", () =>
|
return _cacheService.GetOrSave($"Fee-{account.Exchange}", () =>
|
||||||
{
|
{
|
||||||
return _tradingRepository.GetFee(TradingExchanges.Evm).Cost;
|
return _tradingRepository.GetFee(TradingExchanges.Evm)?.Cost ?? 0m;
|
||||||
}, TimeSpan.FromHours(2));
|
}, TimeSpan.FromHours(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class UserService : IUserService
|
|||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
|
|
||||||
private string[] authorizedAddresses = new string[] { "0x6781920674dA695aa5120d95D80c4B1788046806" };
|
private string[] authorizedAddresses = ["0x6781920674dA695aa5120d95D80c4B1788046806", "0xA2B43AFF0992a47838DF2e6099A8439981f0B717"];
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
IEvmManager evmManager,
|
IEvmManager evmManager,
|
||||||
@@ -29,7 +29,7 @@ public class UserService : IUserService
|
|||||||
|
|
||||||
if (!authorizedAddresses.Contains(recoveredAddress))
|
if (!authorizedAddresses.Contains(recoveredAddress))
|
||||||
throw new Exception("Address not authorized");
|
throw new Exception("Address not authorized");
|
||||||
|
|
||||||
if (recoveredAddress == null || !recoveredAddress.Equals(address))
|
if (recoveredAddress == null || !recoveredAddress.Equals(address))
|
||||||
throw new Exception("Address not corresponding");
|
throw new Exception("Address not corresponding");
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Accounts;
|
|
||||||
using Managing.Application.Shared;
|
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
using Managing.Application.Trading;
|
using Managing.Application.Trading;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.MoneyManagements;
|
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ using Binance.Net.Interfaces.Clients;
|
|||||||
using Managing.Infrastructure.Evm.Services;
|
using Managing.Infrastructure.Evm.Services;
|
||||||
using Managing.Application.Workflows;
|
using Managing.Application.Workflows;
|
||||||
using Managing.Application.Bots.Base;
|
using Managing.Application.Bots.Base;
|
||||||
|
using Managing.Application.ManageBot;
|
||||||
|
|
||||||
namespace Managing.Bootstrap;
|
namespace Managing.Bootstrap;
|
||||||
|
|
||||||
@@ -105,11 +106,13 @@ public static class ApiBootstrap
|
|||||||
services.AddTransient<IUserRepository, UserRepository>();
|
services.AddTransient<IUserRepository, UserRepository>();
|
||||||
services.AddTransient<IStatisticRepository, StatisticRepository>();
|
services.AddTransient<IStatisticRepository, StatisticRepository>();
|
||||||
services.AddTransient<IWorkflowRepository, WorkflowRepository>();
|
services.AddTransient<IWorkflowRepository, WorkflowRepository>();
|
||||||
|
services.AddTransient<IBotRepository, BotRepository>();
|
||||||
|
services.AddTransient<IWorkerRepository, WorkerRepository>();
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
services.AddTransient<ICacheService, CacheService>();
|
services.AddTransient<ICacheService, CacheService>();
|
||||||
services.AddTransient<ITaskCache, TaskCache>();
|
services.AddSingleton<ITaskCache, TaskCache>();
|
||||||
|
|
||||||
// Processors
|
// Processors
|
||||||
services.AddTransient<IExchangeProcessor, EvmProcessor>();
|
services.AddTransient<IExchangeProcessor, EvmProcessor>();
|
||||||
@@ -123,6 +126,8 @@ public static class ApiBootstrap
|
|||||||
services.AddTransient<IExchangeStream, ExchangeStream>();
|
services.AddTransient<IExchangeStream, ExchangeStream>();
|
||||||
services.AddSingleton<IMessengerService, MessengerService>();
|
services.AddSingleton<IMessengerService, MessengerService>();
|
||||||
services.AddSingleton<IDiscordService, DiscordService>();
|
services.AddSingleton<IDiscordService, DiscordService>();
|
||||||
|
services.AddSingleton<IBotService, BotService>();
|
||||||
|
services.AddSingleton<IWorkerService, WorkerService>();
|
||||||
|
|
||||||
// Stream
|
// Stream
|
||||||
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();
|
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ using Managing.Application.Trading.Commands;
|
|||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Evm.Services;
|
using Managing.Infrastructure.Evm.Services;
|
||||||
using Managing.Application.Bots.Base;
|
using Managing.Application.Bots.Base;
|
||||||
|
using Managing.Application.ManageBot;
|
||||||
|
|
||||||
namespace Managing.Bootstrap;
|
namespace Managing.Bootstrap;
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ public static class WorkersBootstrap
|
|||||||
services.AddSingleton<ITradingService, TradingService>();
|
services.AddSingleton<ITradingService, TradingService>();
|
||||||
services.AddSingleton<ISettingsService, SettingsService>();
|
services.AddSingleton<ISettingsService, SettingsService>();
|
||||||
services.AddSingleton<IBacktester, Backtester>();
|
services.AddSingleton<IBacktester, Backtester>();
|
||||||
|
services.AddSingleton<IBotService, BotService>();
|
||||||
|
|
||||||
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
||||||
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
||||||
@@ -93,6 +95,7 @@ public static class WorkersBootstrap
|
|||||||
services.AddTransient<ISettingsRepository, SettingsRepository>();
|
services.AddTransient<ISettingsRepository, SettingsRepository>();
|
||||||
services.AddTransient<ITradingRepository, TradingRepository>();
|
services.AddTransient<ITradingRepository, TradingRepository>();
|
||||||
services.AddTransient<IBacktestRepository, BacktestRepository>();
|
services.AddTransient<IBacktestRepository, BacktestRepository>();
|
||||||
|
services.AddTransient<IBotRepository, BotRepository>();
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ public static class Enums
|
|||||||
{
|
{
|
||||||
Down,
|
Down,
|
||||||
Starting,
|
Starting,
|
||||||
Up
|
Up,
|
||||||
|
Backup
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SignalStatus
|
public enum SignalStatus
|
||||||
@@ -316,7 +317,8 @@ public static class Enums
|
|||||||
PositionFetcher,
|
PositionFetcher,
|
||||||
TraderWatcher,
|
TraderWatcher,
|
||||||
LeaderboardWorker,
|
LeaderboardWorker,
|
||||||
Noobiesboard
|
Noobiesboard,
|
||||||
|
BotManager
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WorkflowUsage
|
public enum WorkflowUsage
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ version: '3.4'
|
|||||||
services:
|
services:
|
||||||
managing.api:
|
managing.api:
|
||||||
environment:
|
environment:
|
||||||
- ASPNETCORE_ENVIRONMENT=oda-docker
|
- ASPNETCORE_ENVIRONMENT=Oda-docker
|
||||||
- ASPNETCORE_URLS=https://+:443;http://+:80
|
- 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
|
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
@@ -14,20 +14,23 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
|
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
|
||||||
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https: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:
|
depends_on:
|
||||||
- managingdb
|
- managingdb
|
||||||
|
|
||||||
managing.api.workers:
|
managing.api.workers:
|
||||||
environment:
|
environment:
|
||||||
- ASPNETCORE_ENVIRONMENT=oda-docker
|
- ASPNETCORE_ENVIRONMENT=Oda-docker
|
||||||
- ASPNETCORE_URLS=https://+:443;http://+:80
|
- 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
|
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx
|
||||||
ports:
|
ports:
|
||||||
- "81:80"
|
- "81:80"
|
||||||
- "444:443"
|
- "444:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
|
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
|
||||||
|
- /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- managingdb
|
- managingdb
|
||||||
|
|
||||||
@@ -38,24 +41,24 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "27017:27017"
|
- "27017:27017"
|
||||||
|
|
||||||
elasticsearch:
|
# elasticsearch:
|
||||||
ports:
|
# ports:
|
||||||
- 9200:9200
|
# - 9200:9200
|
||||||
volumes:
|
# volumes:
|
||||||
- elasticsearch-data:/usr/share/elasticsearch/data
|
# - elasticsearch-data:/usr/share/elasticsearch/data
|
||||||
environment:
|
# environment:
|
||||||
- discovery.type=single-node
|
# - discovery.type=single-node
|
||||||
- xpack.monitoring.templates.enabled=true
|
# - xpack.monitoring.templates.enabled=true
|
||||||
- ES_JAVA_OPTS=-Xms1g -Xmx1g
|
# - ES_JAVA_OPTS=-Xms1g -Xmx1g
|
||||||
- xpack.security.enabled=false
|
# - xpack.security.enabled=false
|
||||||
|
|
||||||
kibana:
|
# kibana:
|
||||||
ports:
|
# ports:
|
||||||
- 5601:5601
|
# - 5601:5601
|
||||||
depends_on:
|
# depends_on:
|
||||||
- elasticsearch
|
# - elasticsearch
|
||||||
environment:
|
# environment:
|
||||||
- ELASTICSEARCH_URL=http://elasticsearch:9200
|
# - ELASTICSEARCH_URL=http://elasticsearch:9200
|
||||||
|
|
||||||
influxdb:
|
influxdb:
|
||||||
image: influxdb:latest
|
image: influxdb:latest
|
||||||
@@ -22,13 +22,13 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- managing-network
|
- managing-network
|
||||||
|
|
||||||
elasticsearch:
|
# elasticsearch:
|
||||||
image: elasticsearch:8.4.1
|
# image: elasticsearch:8.4.1
|
||||||
networks:
|
# networks:
|
||||||
- managing-network
|
# - managing-network
|
||||||
|
|
||||||
kibana:
|
# kibana:
|
||||||
image: kibana:8.4.1
|
# image: kibana:8.4.1
|
||||||
|
|
||||||
influxdb:
|
influxdb:
|
||||||
image: influxdb:latest
|
image: influxdb:latest
|
||||||
|
|||||||
@@ -71,5 +71,8 @@ namespace Managing.Domain.Bots
|
|||||||
{
|
{
|
||||||
return Name;
|
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();
|
void Restart();
|
||||||
string GetStatus();
|
string GetStatus();
|
||||||
string GetName();
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Binance.Net.Clients;
|
using Binance.Net.Clients;
|
||||||
using Binance.Net.Enums;
|
using Binance.Net.Enums;
|
||||||
using Binance.Net.Interfaces.Clients;
|
using Binance.Net.Interfaces.Clients;
|
||||||
using CryptoExchange.Net;
|
|
||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using FTX.Net.Clients;
|
|
||||||
using FTX.Net.Interfaces.Clients;
|
using FTX.Net.Interfaces.Clients;
|
||||||
using FTX.Net.Objects;
|
using FTX.Net.Objects;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
//using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using System.Runtime.Caching;
|
using System.Runtime.Caching;
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Storage
|
namespace Managing.Infrastructure.Storage
|
||||||
@@ -8,11 +7,11 @@ namespace Managing.Infrastructure.Storage
|
|||||||
public class TaskCache : ITaskCache
|
public class TaskCache : ITaskCache
|
||||||
{
|
{
|
||||||
private MemoryCache _cache { get; } = MemoryCache.Default;
|
private MemoryCache _cache { get; } = MemoryCache.Default;
|
||||||
|
|
||||||
private CacheItemPolicy _defaultPolicy { get; } = new CacheItemPolicy();
|
private CacheItemPolicy _defaultPolicy { get; } = new CacheItemPolicy();
|
||||||
|
|
||||||
public async Task<T> AddOrGetExisting<T>(string key, Func<Task<T>> valueFactory)
|
public async Task<T> AddOrGetExisting<T>(string key, Func<Task<T>> valueFactory)
|
||||||
{
|
{
|
||||||
|
|
||||||
var asyncLazyValue = new AsyncLazy<T>(valueFactory);
|
var asyncLazyValue = new AsyncLazy<T>(valueFactory);
|
||||||
var existingValue = (AsyncLazy<T>)_cache.AddOrGetExisting(key, asyncLazyValue, _defaultPolicy);
|
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.
|
// Get the most recent value with a recursive call.
|
||||||
return await AddOrGetExisting(key, valueFactory);
|
return await AddOrGetExisting(key, valueFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -64,13 +64,14 @@ namespace Managing.Infrastructure.Storage
|
|||||||
public T Get<T>(string key)
|
public T Get<T>(string key)
|
||||||
{
|
{
|
||||||
var existingValue = (AsyncLazy<T>)_cache.Get(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>()
|
public virtual List<T> GetCache<T>()
|
||||||
{
|
{
|
||||||
List<T> list = new List<T>();
|
List<T> list = new List<T>();
|
||||||
|
|
||||||
foreach (var item in _cache)
|
foreach (var item in _cache)
|
||||||
{
|
{
|
||||||
list.Add((T)item.Value);
|
list.Add((T)item.Value);
|
||||||
@@ -79,4 +80,4 @@ namespace Managing.Infrastructure.Storage
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
using Xunit;
|
namespace Managing.Infrastructure.Tests;
|
||||||
using static Managing.Common.Enums;
|
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Tests;
|
|
||||||
|
|
||||||
public class SubgraphTests
|
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_API_URL_SERVER=https://dev-managing-api.apps.managing.live
|
||||||
VITE_WORKER_URL_LOCAL=https://localhost:5002
|
VITE_WORKER_URL_LOCAL=https://localhost:5002
|
||||||
VITE_WORKER_URL_SERVER=https://dev-managing-worker.apps.managing.live
|
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 type { ILoginFormInput } from '../../../global/type'
|
||||||
import useCookie from '../../../hooks/useCookie'
|
import useCookie from '../../../hooks/useCookie'
|
||||||
import { SecondaryNavbar } from '../NavBar/NavBar'
|
import { SecondaryNavbar } from '../NavBar/NavBar'
|
||||||
|
import Toast from '../Toast/Toast'
|
||||||
|
|
||||||
const LogIn = () => {
|
const LogIn = () => {
|
||||||
const { apiUrl } = useApiUrlStore()
|
const { apiUrl } = useApiUrlStore()
|
||||||
const { register, handleSubmit } = useForm<ILoginFormInput>()
|
const { register, handleSubmit } = useForm<ILoginFormInput>()
|
||||||
const { disconnect } = useDisconnect()
|
const { disconnect } = useDisconnect()
|
||||||
const { address } = useAccount()
|
const { address } = useAccount()
|
||||||
const { isLoading, signMessageAsync } = useSignMessage({})
|
const { signMessageAsync } = useSignMessage({})
|
||||||
const { setCookie } = useCookie()
|
const { setCookie } = useCookie()
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<ILoginFormInput> = async (form) => {
|
const onSubmit: SubmitHandler<ILoginFormInput> = async (form) => {
|
||||||
const message = 'wagmi'
|
const message = 'wagmi'
|
||||||
const signature = await signMessageAsync({ message })
|
const signature = await signMessageAsync({ message })
|
||||||
|
const t = new Toast('Creating token')
|
||||||
|
|
||||||
if (signature && address) {
|
if (signature && address) {
|
||||||
const userClient = new UserClient({}, apiUrl)
|
const userClient = new UserClient({}, apiUrl)
|
||||||
|
|
||||||
await userClient
|
await userClient
|
||||||
.user_CreateToken({
|
.user_CreateToken({
|
||||||
address: address.toString(),
|
address: address.toString(),
|
||||||
@@ -34,11 +37,11 @@ const LogIn = () => {
|
|||||||
setCookie('token', data, 1)
|
setCookie('token', data, 1)
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err: any) => {
|
||||||
// eslint-disable-next-line no-console
|
t.update('error', 'Error :' + err.message)
|
||||||
console.error(err)
|
|
||||||
})
|
})
|
||||||
} else {
|
}else{
|
||||||
|
t.update('error', 'Error : No signature')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +80,6 @@ const LogIn = () => {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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"
|
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
|
Sign and login
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ const baseOptions: UpdateOptions = {
|
|||||||
class Toast {
|
class Toast {
|
||||||
private id: Id
|
private id: Id
|
||||||
|
|
||||||
constructor(content: string) {
|
constructor(content: string, isLoading = true) {
|
||||||
this.id = toast.loading(content)
|
if (!isLoading) {
|
||||||
|
this.id = toast(content)
|
||||||
|
}else{
|
||||||
|
this.id = toast.loading(content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update(type: TypeOptions, content: string, opts?: any) {
|
update(type: TypeOptions, content: string, opts?: any) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ColorSwatchIcon, TrashIcon, XIcon } from '@heroicons/react/solid'
|
import { ColorSwatchIcon, TrashIcon, XIcon } from '@heroicons/react/solid'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import 'react-toastify/dist/ReactToastify.css'
|
import 'react-toastify/dist/ReactToastify.css'
|
||||||
import useApiUrlStore from '../../app/store/apiStore'
|
import useApiUrlStore from '../../app/store/apiStore'
|
||||||
@@ -17,14 +17,17 @@ const BacktestScanner: React.FC = () => {
|
|||||||
const { apiUrl } = useApiUrlStore()
|
const { apiUrl } = useApiUrlStore()
|
||||||
const client = new BacktestClient({}, apiUrl)
|
const client = new BacktestClient({}, apiUrl)
|
||||||
|
|
||||||
const { isLoading, refetch } = useQuery({
|
const { isLoading, refetch, data: backtests } = useQuery({
|
||||||
onSuccess: (data) => {
|
|
||||||
setBacktest(data)
|
|
||||||
},
|
|
||||||
queryFn: () => client.backtest_Backtests(),
|
queryFn: () => client.backtest_Backtests(),
|
||||||
queryKey: ['backtests'],
|
queryKey: ['backtests'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (backtests) {
|
||||||
|
setBacktest(backtests)
|
||||||
|
}
|
||||||
|
}, [backtests])
|
||||||
|
|
||||||
function deleteAllBacktests() {
|
function deleteAllBacktests() {
|
||||||
const t = new Toast('Deleting all backtests')
|
const t = new Toast('Deleting all backtests')
|
||||||
client
|
client
|
||||||
@@ -114,7 +117,7 @@ const BacktestScanner: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BacktestTable list={backtestingResult} isFetching={isLoading} />
|
<BacktestTable list={backtestingResult} isFetching={isLoading} setBacktests={setBacktest} />
|
||||||
|
|
||||||
<BacktestModal
|
<BacktestModal
|
||||||
showModal={showModal}
|
showModal={showModal}
|
||||||
|
|||||||
Reference in New Issue
Block a user