Merge pull request #1 from CryptoOda/update-global

Add backup management
This commit is contained in:
Oda
2024-07-12 22:24:22 +07:00
committed by GitHub
60 changed files with 873 additions and 247 deletions

View File

@@ -151,6 +151,8 @@ ________________________________________________________________________________
- [ ] Create method to update the money management use by the bot
- [ ] Implement from/to tickers array pattern
- [ ] Extract all managing trade method into a TradingBox class => Create composable trading bot type easily
- [ ] Bot backup worker: Every x, get saved bots and check if still running. If not running call api to reboot bot.
- [ ] Create worker to fetch the biggest spread between long\short funding rate and send alert when most profitable delta neutral position is found
# Front-end
## Improve Account page

View File

@@ -2,4 +2,4 @@ cd ..
cd .\src\
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.local.yml up -d

View File

@@ -32,13 +32,13 @@
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Lowpro.json">
<Content Update="appsettings.Oda.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-docker.json">
<Content Update="appsettings.Prod.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

View File

@@ -14,8 +14,9 @@ using Serilog.Sinks.Elasticsearch;
// Builder
var builder = WebApplication.CreateBuilder(args);
builder.Configuration
.AddEnvironmentVariables();
builder.Configuration.SetBasePath(System.AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
{

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;

View File

@@ -1,4 +1,5 @@
using Managing.Application.Workers.Abstractions;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;

View File

@@ -1,4 +1,5 @@
using Managing.Application.Workers.Abstractions;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;

View File

@@ -1,5 +1,6 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Trades;
using static Managing.Common.Enums;

View File

@@ -1,4 +1,5 @@
using Managing.Application.Workers.Abstractions;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;

View File

@@ -1,4 +1,5 @@
using Managing.Application.Workers.Abstractions;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using Managing.Common;
namespace Managing.Api.Workers.Workers;

View File

@@ -1,4 +1,5 @@
using Managing.Application.Workers.Abstractions;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;

View File

@@ -5,8 +5,8 @@
},
"InfluxDb": {
"Url": "http://influxdb:8086/",
"Organization": "",
"Token": ""
"Organization": "managing-org",
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
},
"Serilog": {
"MinimumLevel": {

View File

@@ -1,7 +1,7 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://localhost:8086/",

View File

@@ -36,6 +36,9 @@
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Prod.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-sandbox.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

View File

@@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
using Managing.Api.Authorization;
using Managing.Api.Exceptions;
using Managing.Api.Filters;
using Managing.Api.Workers;
using Managing.Application.Hubs;
using Managing.Bootstrap;
using Managing.Common;
@@ -17,12 +18,15 @@ using Serilog.Sinks.Elasticsearch;
// Builder
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("appsettings.Lowpro.json", optional: true, reloadOnChange: true)
builder.Configuration.SetBasePath(System.AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json")
.AddJsonFile($"config.{builder.Environment.EnvironmentName}.json",
optional: true, reloadOnChange: true);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddUserSecrets<Program>();
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
{
var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-");
@@ -123,6 +127,7 @@ builder.Services.AddSwaggerGen(options =>
});
builder.WebHost.SetupDiscordBot();
builder.Services.AddHostedService<BotManagerWorker>();
// App
var app = builder.Build();

View 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);
}
}

View File

@@ -8,7 +8,7 @@
"InfluxDb": {
"Url": "http://influxdb:8086/",
"Organization": "managing-org",
"Token": ""
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
},
"Serilog": {
"MinimumLevel": {

View File

@@ -5,8 +5,8 @@
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"Organization": "",
"Token": ""
"Organization": "managing-org",
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
},
"Serilog": {
"MinimumLevel": {

View File

@@ -0,0 +1,7 @@
public interface IBotRepository
{
Task InsertBotAsync(BotBackup bot);
IEnumerable<BotBackup> GetBots();
Task UpdateBackupBot(BotBackup bot);
void DeleteBotBackup(string botName);
}

View File

@@ -28,13 +28,14 @@ namespace Managing.Application.Tests
var discordService = new Mock<IMessengerService>().Object;
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
var botService = new Mock<IBotService>().Object;
_botFactory = new BotFactory(
_exchangeService,
tradingBotLogger,
_moneyManagementService.Object,
discordService,
_accountService.Object,
_tradingService.Object);
_tradingService.Object,
botService);
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger);
_elapsedTimes = new List<double>();
}

View File

@@ -1,5 +1,4 @@
using Managing.Application.Workflows.Flows.Feeds;
using Managing.Domain.Workflows;
using Managing.Domain.Workflows;
using Xunit;
using static Managing.Common.Enums;

View File

@@ -1,8 +1,10 @@

using Managing.Application.Workers.Abstractions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Api.Workers;
namespace Managing.Application.Workers;
public abstract class BaseWorker<T> : BackgroundService where T : class
{

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View 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);
}

View File

@@ -10,32 +10,32 @@ namespace Managing.Application.Bots.Base
{
public class BotFactory : IBotFactory
{
private readonly IMoneyManagementService _moneyManagementService;
private readonly IExchangeService _exchangeService;
private readonly IMessengerService _messengerService;
private readonly IAccountService _accountService;
private readonly ILogger<TradingBot> _tradingBotLogger;
private readonly ITradingService _tradingService;
private readonly IBotService _botService;
public BotFactory(
IExchangeService exchangeService,
ILogger<TradingBot> tradingBotLogger,
IMoneyManagementService moneyManagementService,
IMessengerService messengerService,
IAccountService accountService,
ITradingService tradingService)
ITradingService tradingService,
IBotService botService)
{
_tradingBotLogger = tradingBotLogger;
_exchangeService = exchangeService;
_moneyManagementService = moneyManagementService;
_messengerService = messengerService;
_accountService = accountService;
_tradingService = tradingService;
_botService = botService;
}
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
{
return new SimpleBot(botName, _tradingBotLogger, workflow);
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService);
}
ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
@@ -52,6 +52,7 @@ namespace Managing.Application.Bots.Base
interval,
_accountService,
_messengerService,
_botService,
isForWatchingOnly: isForWatchingOnly);
}
@@ -69,6 +70,7 @@ namespace Managing.Application.Bots.Base
interval,
_accountService,
_messengerService,
_botService,
true,
isForWatchingOnly);
}
@@ -87,6 +89,7 @@ namespace Managing.Application.Bots.Base
interval,
_accountService,
_messengerService,
_botService,
isForWatchingOnly: isForWatchingOnly);
}
@@ -104,6 +107,7 @@ namespace Managing.Application.Bots.Base
interval,
_accountService,
_messengerService,
_botService,
true,
isForWatchingOnly);
}

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using Managing.Application.Abstractions;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
@@ -18,6 +19,7 @@ namespace Managing.Application.Bots
Timeframe timeframe,
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
@@ -31,12 +33,12 @@ namespace Managing.Application.Bots
timeframe,
accountService,
messengerService,
botService,
isForBacktest,
isForWatchingOnly,
flipPosition: true)
{
BotType = BotType.FlippingBot;
Start();
}
public sealed override void Start()

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using Managing.Application.Abstractions;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
@@ -18,6 +19,7 @@ namespace Managing.Application.Bots
Timeframe timeframe,
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
@@ -31,11 +33,11 @@ namespace Managing.Application.Bots
timeframe,
accountService,
messengerService,
botService,
isForBacktest,
isForWatchingOnly)
{
BotType = BotType.ScalpingBot;
Start();
}
public sealed override void Start()

View File

@@ -1,20 +1,25 @@
using Managing.Domain.Bots;
using Managing.Application.Abstractions;
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.Bots
{
public class SimpleBot : Bot
{
public readonly ILogger<TradingBot> Logger;
private readonly Workflow _workflow;
private readonly IBotService _botService;
private Workflow _workflow;
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow) : base(name)
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow, IBotService botService) :
base(name)
{
Logger = logger;
_botService = botService;
_workflow = workflow;
Interval = 100;
Start();
}
public override void Start()
@@ -32,8 +37,20 @@ namespace Managing.Application.Bots
Logger.LogInformation(Identifier);
Logger.LogInformation(DateTime.Now.ToString());
await _workflow.Execute();
SaveBackup();
Logger.LogInformation("__________________________________________________");
});
}
public override void SaveBackup()
{
var data = JsonConvert.SerializeObject(_workflow);
_botService.SaveBotBackup(Name, BotType.SimpleBot, data);
}
public override void LoadBackup(BotBackup backup)
{
_workflow = JsonConvert.DeserializeObject<Workflow>(backup.Data);
}
}
}

View File

@@ -11,6 +11,7 @@ using Managing.Domain.Shared.Helpers;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.Bots;
@@ -22,6 +23,7 @@ public class TradingBot : Bot, ITradingBot
public readonly IMessengerService MessengerService;
public readonly IAccountService AccountService;
private readonly ITradingService TradingService;
private readonly IBotService BotService;
public Account Account { get; set; }
public HashSet<IStrategy> Strategies { get; set; }
@@ -54,6 +56,7 @@ public class TradingBot : Bot, ITradingBot
Timeframe timeframe,
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
bool isForBacktest = false,
bool isForWatchingOnly = false,
bool flipPosition = false)
@@ -63,6 +66,7 @@ public class TradingBot : Bot, ITradingBot
AccountService = accountService;
MessengerService = messengerService;
TradingService = tradingService;
BotService = botService;
IsForWatchingOnly = isForWatchingOnly;
FlipPosition = flipPosition;
@@ -97,7 +101,17 @@ public class TradingBot : Bot, ITradingBot
LoadScenario();
await PreloadCandles();
await CancelAllOrders();
await MessengerService.SendMessage($"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies.");
try
{
await MessengerService.SendMessage(
$"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies.");
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Message);
}
await InitWorker(Run);
}
@@ -143,7 +157,8 @@ public class TradingBot : Bot, ITradingBot
public async Task Run()
{
Logger.LogInformation($"____________________{Name}____________________");
Logger.LogInformation($"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
Logger.LogInformation(
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
var previousCandleCount = Candles.Count;
@@ -158,6 +173,9 @@ public class TradingBot : Bot, ITradingBot
if (!IsForWatchingOnly)
await ManagePositions();
if (!IsForBacktest)
SaveBackup();
await UpdateWalletBalances();
Logger.LogInformation($"Candles : {Candles.Count}");
Logger.LogInformation($"Signals : {Signals.Count}");
@@ -168,6 +186,9 @@ public class TradingBot : Bot, ITradingBot
private async Task PreloadCandles()
{
if (Candles.Any())
return;
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
@@ -265,7 +286,9 @@ public class TradingBot : Bot, ITradingBot
{
Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");
var position = IsForBacktest ? positionForSignal : TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
var position = IsForBacktest
? positionForSignal
: TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped))
{
@@ -277,32 +300,41 @@ public class TradingBot : Bot, ITradingBot
// For backtesting or force close if not executed on exchange :
// check if position is still open
// Check status, if still open update the status of the position
var lastCandle = IsForBacktest ? Candles.Last() : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
var lastCandle = IsForBacktest
? Candles.Last()
: ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
if (positionForSignal.OriginDirection == TradeDirection.Long)
{
if (positionForSignal.StopLoss.Price >= lastCandle.Low)
{
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
await LogInformation(
$"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
positionForSignal.StopLoss.Price, true);
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit1.Price <= lastCandle.High
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
{
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
await LogInformation(
$"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1,
positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High)
{
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
await LogInformation(
$"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
positionForSignal.TakeProfit2.Price, true);
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
}
else
{
Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened");
Logger.LogInformation(
$"Position {signal.Identifier} don't need to be update. Position still opened");
}
}
@@ -310,26 +342,33 @@ public class TradingBot : Bot, ITradingBot
{
if (positionForSignal.StopLoss.Price <= lastCandle.High)
{
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
await LogInformation(
$"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
positionForSignal.StopLoss.Price, true);
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
{
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
await LogInformation(
$"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1,
positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low)
{
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
await LogInformation(
$"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
positionForSignal.TakeProfit2.Price, true);
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
}
else
{
Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened");
Logger.LogInformation(
$"Position {signal.Identifier} don't need to be update. Position still opened");
}
}
}
@@ -353,7 +392,6 @@ public class TradingBot : Bot, ITradingBot
}
private async Task OpenPosition(Signal signal)
{
// Check if a position is already open
@@ -362,7 +400,9 @@ public class TradingBot : Bot, ITradingBot
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
&& p.SignalIdentifier != signal.Identifier);
var lastPrice = IsForBacktest ? Candles.Last().Close : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
var lastPrice = IsForBacktest
? Candles.Last().Close
: ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
// If position open
if (openedPosition != null)
@@ -373,7 +413,8 @@ public class TradingBot : Bot, ITradingBot
if (openedPosition.OriginDirection == signal.Direction)
{
// An operation is already open for the same direction
await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction");
await LogInformation(
$"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction");
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
}
else
@@ -386,11 +427,13 @@ public class TradingBot : Bot, ITradingBot
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
await OpenPosition(signal);
await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
await LogInformation(
$"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
}
else
{
await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
await LogWarning(
$"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
}
}
@@ -399,12 +442,14 @@ public class TradingBot : Bot, ITradingBot
{
if (!CanOpenPosition(signal))
{
await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
await LogInformation(
"Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
return;
}
await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}");
await LogInformation(
$"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}");
try
{
@@ -472,15 +517,18 @@ public class TradingBot : Bot, ITradingBot
return positionSignal.Date < tenCandleAgo.Date;
}
private async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, bool tradeClosingPosition = false)
private async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
bool tradeClosingPosition = false)
{
if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled && tradeToClose.TradeType == TradeType.StopMarket)
if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled &&
tradeToClose.TradeType == TradeType.StopMarket)
{
// If trade is the 2nd Take profit
tradeToClose.Quantity = position.TakeProfit2.Quantity;
}
await LogInformation($"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
await LogInformation(
$"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
$"- Closing Position : {tradeClosingPosition}");
// Get status of position before closing it. The position might be already close by the exchange
@@ -494,7 +542,8 @@ public class TradingBot : Bot, ITradingBot
var command = new ClosePositionCommand(position, lastPrice);
try
{
var closedPosition = await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
var closedPosition =
await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
.Handle(command);
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
@@ -521,7 +570,6 @@ public class TradingBot : Bot, ITradingBot
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
}
}
}
}
@@ -534,7 +582,8 @@ public class TradingBot : Bot, ITradingBot
position.SignalIdentifier = previousPosition.SignalIdentifier;
Positions[positionIndex] = position;
SetSignalStatus(position.SignalIdentifier, SignalStatus.Expired);
Logger.LogInformation($"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss.Realized}");
Logger.LogInformation(
$"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss.Realized}");
}
else
{
@@ -551,7 +600,7 @@ public class TradingBot : Bot, ITradingBot
private async Task CancelAllOrders()
{
if (!IsForBacktest)
if (!IsForBacktest && !IsForWatchingOnly)
{
try
{
@@ -648,4 +697,52 @@ public class TradingBot : Bot, ITradingBot
await MessengerService.SendTradeMessage(message, isBadBehavior);
}
}
public override void SaveBackup()
{
var data = new TradingBotBackup
{
Name = Name,
BotType = BotType,
Signals = Signals,
Positions = Positions,
Timeframe = Timeframe,
Ticker = Ticker,
Scenario = Scenario,
AccountName = AccountName,
IsForWatchingOnly = IsForWatchingOnly,
WalletBalances = WalletBalances,
MoneyManagement = MoneyManagement
};
BotService.SaveBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
}
public override void LoadBackup(BotBackup backup)
{
var data = JsonConvert.DeserializeObject<TradingBotBackup>(backup.Data);
Signals = data.Signals;
Positions = data.Positions;
WalletBalances = data.WalletBalances;
MoneyManagement = data.MoneyManagement;
Timeframe = data.Timeframe;
Ticker = data.Ticker;
Scenario = data.Scenario;
AccountName = data.AccountName;
IsForWatchingOnly = data.IsForWatchingOnly;
}
}
public class TradingBotBackup
{
public string Name { get; set; }
public BotType BotType { get; set; }
public HashSet<Signal> Signals { get; set; }
public List<Position> Positions { get; set; }
public Timeframe Timeframe { get; set; }
public Ticker Ticker { get; set; }
public string Scenario { get; set; }
public string AccountName { get; set; }
public bool IsForWatchingOnly { get; set; }
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
public MoneyManagement MoneyManagement { get; set; }
}

View 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);
}
}
}

View File

@@ -8,25 +8,16 @@ namespace Managing.Application.ManageBot;
public class DeleteBotCommandHandler : IRequestHandler<DeleteBotCommand, bool>
{
private readonly ILogger<DeleteBotCommandHandler> _log;
private readonly ITaskCache _taskCache;
private readonly IBotService _botService;
public DeleteBotCommandHandler(ITaskCache taskCache, ILogger<DeleteBotCommandHandler> log)
public DeleteBotCommandHandler(ILogger<DeleteBotCommandHandler> log, IBotService botService)
{
_taskCache = taskCache;
_log = log;
_botService = botService;
}
public Task<bool> Handle(DeleteBotCommand request, CancellationToken cancellationToken)
{
try
{
_taskCache.Invalidate(request.Name);
return Task.FromResult(true);
}
catch (Exception e)
{
_log.LogError(e.Message);
return Task.FromResult(false);
}
return _botService.DeleteBot(request.Name);
}
}

View File

@@ -1,30 +1,15 @@
using Managing.Application.Abstractions;
using Managing.Application.ManageBot.Commands;
using Managing.Core;
using MediatR;
namespace Managing.Application.ManageBot
{
public class GetActiveBotsCommandHandler : IRequestHandler<GetActiveBotsCommand, List<ITradingBot>>
public class GetActiveBotsCommandHandler(IBotService botService)
: IRequestHandler<GetActiveBotsCommand, List<ITradingBot>>
{
private readonly ITaskCache taskCache;
public GetActiveBotsCommandHandler(ITaskCache taskCache)
{
this.taskCache = taskCache;
}
public Task<List<ITradingBot>> Handle(GetActiveBotsCommand request, CancellationToken cancellationToken)
{
var cachedTask = taskCache.GetCache<AsyncLazy<ITradingBot>>();
var result = new List<ITradingBot>();
foreach (var item in cachedTask)
{
result.Add(item.Value.Result);
}
return Task.FromResult(result);
return Task.FromResult(botService.GetActiveBots());
}
}
}

View File

@@ -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>
{
}

View File

@@ -1,39 +1,21 @@
using Managing.Application.Abstractions;
using Managing.Application.ManageBot.Commands;
using Managing.Domain.Bots;
using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
{
public class RestartBotCommandHandler : IRequestHandler<RestartBotCommand, string>
{
private readonly ITaskCache _taskCache;
private readonly IBotService _botService;
public RestartBotCommandHandler(ITaskCache taskCache)
public RestartBotCommandHandler(IBotService botService)
{
_taskCache = taskCache;
_botService = botService;
}
public Task<string> Handle(RestartBotCommand request, CancellationToken cancellationToken)
{
switch (request.BotType)
{
case BotType.SimpleBot:
var simpleBot = _taskCache.Get<IBot>(request.Name);
simpleBot.Restart();
return Task.FromResult(simpleBot.GetStatus());
case BotType.ScalpingBot:
var scalpingBot = _taskCache.Get<ITradingBot>(request.Name);
scalpingBot.Restart();
return Task.FromResult(scalpingBot.GetStatus());
case BotType.FlippingBot:
var flippingBot = _taskCache.Get<ITradingBot>(request.Name);
flippingBot.Restart();
return Task.FromResult(flippingBot.GetStatus());
default:
return Task.FromResult(BotStatus.Down.ToString());
}
return _botService.RestartBot(request.Name);
}
}
}

View File

@@ -1,5 +1,4 @@
using Managing.Domain.Bots;
using MediatR;
using MediatR;
using static Managing.Common.Enums;
using Managing.Application.Abstractions;
using Managing.Application.ManageBot.Commands;
@@ -9,13 +8,14 @@ namespace Managing.Application.ManageBot
public class StartBotCommandHandler : IRequestHandler<StartBotCommand, string>
{
private readonly IBotFactory _botFactory;
private readonly ITaskCache _taskCache;
private readonly IBotService _botService;
private readonly IMoneyManagementService _moneyManagementService;
public StartBotCommandHandler(IBotFactory botFactory, ITaskCache taskCache, IMoneyManagementService moneyManagementService)
public StartBotCommandHandler(IBotFactory botFactory, IBotService botService,
IMoneyManagementService moneyManagementService)
{
_botFactory = botFactory;
_taskCache = taskCache;
_botService = botService;
_moneyManagementService = moneyManagementService;
}
@@ -26,15 +26,25 @@ namespace Managing.Application.ManageBot
switch (request.BotType)
{
case BotType.SimpleBot:
Func<Task<IBot>> simpleBot = () => Task.FromResult(_botFactory.CreateSimpleBot(request.Name, null));
return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, simpleBot).Result.GetStatus());
var bot = _botFactory.CreateSimpleBot(request.Name, null);
bot.Start();
_botService.AddSimpleBotToCache(bot);
return Task.FromResult(bot.GetStatus());
case BotType.ScalpingBot:
Func<Task<ITradingBot>> scalpingBot = () => Task.FromResult(_botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly));
return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, scalpingBot).Result.GetStatus());
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
sBot.Start();
_botService.AddTradingBotToCache(sBot);
return Task.FromResult(sBot.GetStatus());
case BotType.FlippingBot:
Func<Task<ITradingBot>> flippingBot = () => Task.FromResult(_botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly));
return Task.FromResult(_taskCache.AddOrGetExisting(request.Name, flippingBot).Result.GetStatus());
};
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
fBot.Start();
_botService.AddTradingBotToCache(fBot);
return Task.FromResult(fBot.GetStatus());
}
;
return Task.FromResult(botStatus.ToString());
}

View File

@@ -1,39 +1,21 @@
using Managing.Application.Abstractions;
using Managing.Application.ManageBot.Commands;
using Managing.Domain.Bots;
using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
{
public class StopBotCommandHandler : IRequestHandler<StopBotCommand, string>
{
private readonly ITaskCache _taskCache;
private readonly IBotService _botService;
public StopBotCommandHandler(ITaskCache taskCache)
public StopBotCommandHandler(IBotService botService)
{
_taskCache = taskCache;
_botService = botService;
}
public Task<string> Handle(StopBotCommand request, CancellationToken cancellationToken)
{
switch (request.BotType)
{
case BotType.SimpleBot:
var simpleBot = _taskCache.Get<IBot>(request.Name);
simpleBot.Stop();
return Task.FromResult(simpleBot.GetStatus());
case BotType.ScalpingBot:
var scalpingBot = _taskCache.Get<ITradingBot>(request.Name);
scalpingBot.Stop();
return Task.FromResult(scalpingBot.GetStatus());
case BotType.FlippingBot:
var flippingBot = _taskCache.Get<ITradingBot>(request.Name);
flippingBot.Stop();
return Task.FromResult(flippingBot.GetStatus());
default:
return Task.FromResult(BotStatus.Down.ToString());
}
return _botService.StopBot(request.Name);
}
}
}

View File

@@ -1,5 +1,4 @@
using DnsClient.Internal;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
@@ -9,7 +8,6 @@ using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Managing.Domain.Shared.Helpers;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using static Managing.Common.Enums;
namespace Managing.Application.Trading;
@@ -206,7 +204,7 @@ public class TradingService : ITradingService
return _cacheService.GetOrSave($"Fee-{account.Exchange}", () =>
{
return _tradingRepository.GetFee(TradingExchanges.Evm).Cost;
return _tradingRepository.GetFee(TradingExchanges.Evm)?.Cost ?? 0m;
}, TimeSpan.FromHours(2));
}

View File

@@ -11,7 +11,7 @@ public class UserService : IUserService
private readonly IUserRepository _userRepository;
private readonly IAccountService _accountService;
private string[] authorizedAddresses = new string[] { "0x6781920674dA695aa5120d95D80c4B1788046806" };
private string[] authorizedAddresses = ["0x6781920674dA695aa5120d95D80c4B1788046806", "0xA2B43AFF0992a47838DF2e6099A8439981f0B717"];
public UserService(
IEvmManager evmManager,

View File

@@ -1,11 +1,8 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Accounts;
using Managing.Application.Shared;
using Managing.Application.Trading.Commands;
using Managing.Application.Trading;
using Managing.Domain.Accounts;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Managing.Domain.Workflows;

View File

@@ -41,6 +41,7 @@ using Binance.Net.Interfaces.Clients;
using Managing.Infrastructure.Evm.Services;
using Managing.Application.Workflows;
using Managing.Application.Bots.Base;
using Managing.Application.ManageBot;
namespace Managing.Bootstrap;
@@ -105,11 +106,13 @@ public static class ApiBootstrap
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<IStatisticRepository, StatisticRepository>();
services.AddTransient<IWorkflowRepository, WorkflowRepository>();
services.AddTransient<IBotRepository, BotRepository>();
services.AddTransient<IWorkerRepository, WorkerRepository>();
// Cache
services.AddDistributedMemoryCache();
services.AddTransient<ICacheService, CacheService>();
services.AddTransient<ITaskCache, TaskCache>();
services.AddSingleton<ITaskCache, TaskCache>();
// Processors
services.AddTransient<IExchangeProcessor, EvmProcessor>();
@@ -123,6 +126,8 @@ public static class ApiBootstrap
services.AddTransient<IExchangeStream, ExchangeStream>();
services.AddSingleton<IMessengerService, MessengerService>();
services.AddSingleton<IDiscordService, DiscordService>();
services.AddSingleton<IBotService, BotService>();
services.AddSingleton<IWorkerService, WorkerService>();
// Stream
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();

View File

@@ -33,6 +33,7 @@ using Managing.Application.Trading.Commands;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Services;
using Managing.Application.Bots.Base;
using Managing.Application.ManageBot;
namespace Managing.Bootstrap;
@@ -58,6 +59,7 @@ public static class WorkersBootstrap
services.AddSingleton<ITradingService, TradingService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IBacktester, Backtester>();
services.AddSingleton<IBotService, BotService>();
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
@@ -93,6 +95,7 @@ public static class WorkersBootstrap
services.AddTransient<ISettingsRepository, SettingsRepository>();
services.AddTransient<ITradingRepository, TradingRepository>();
services.AddTransient<IBacktestRepository, BacktestRepository>();
services.AddTransient<IBotRepository, BotRepository>();
// Cache
services.AddDistributedMemoryCache();

View File

@@ -73,7 +73,8 @@ public static class Enums
{
Down,
Starting,
Up
Up,
Backup
}
public enum SignalStatus
@@ -316,7 +317,8 @@ public static class Enums
PositionFetcher,
TraderWatcher,
LeaderboardWorker,
Noobiesboard
Noobiesboard,
BotManager
}
public enum WorkflowUsage

View File

@@ -4,9 +4,9 @@ version: '3.4'
services:
managing.api:
environment:
- ASPNETCORE_ENVIRONMENT=oda-docker
- ASPNETCORE_ENVIRONMENT=Oda-docker
- ASPNETCORE_URLS=https://+:443;http://+:80
- ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11
- ASPNETCORE_Kestrel__Certificates__Default__Password=!Managing94
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx
ports:
- "80:80"
@@ -14,20 +14,23 @@ services:
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
- /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro
- /Users/oda/Microsoft/UserSecrets:/root/.microsoft/usersecrets/$USER_SECRETS_ID
depends_on:
- managingdb
managing.api.workers:
environment:
- ASPNETCORE_ENVIRONMENT=oda-docker
- ASPNETCORE_ENVIRONMENT=Oda-docker
- ASPNETCORE_URLS=https://+:443;http://+:80
- ASPNETCORE_Kestrel__Certificates__Default__Password=!MotdepasseFort11
- ASPNETCORE_Kestrel__Certificates__Default__Password=!Managing94
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx
ports:
- "81:80"
- "444:443"
volumes:
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
- /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro
depends_on:
- managingdb
@@ -38,24 +41,24 @@ services:
ports:
- "27017:27017"
elasticsearch:
ports:
- 9200:9200
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
environment:
- discovery.type=single-node
- xpack.monitoring.templates.enabled=true
- ES_JAVA_OPTS=-Xms1g -Xmx1g
- xpack.security.enabled=false
# elasticsearch:
# ports:
# - 9200:9200
# volumes:
# - elasticsearch-data:/usr/share/elasticsearch/data
# environment:
# - discovery.type=single-node
# - xpack.monitoring.templates.enabled=true
# - ES_JAVA_OPTS=-Xms1g -Xmx1g
# - xpack.security.enabled=false
kibana:
ports:
- 5601:5601
depends_on:
- elasticsearch
environment:
- ELASTICSEARCH_URL=http://elasticsearch:9200
# kibana:
# ports:
# - 5601:5601
# depends_on:
# - elasticsearch
# environment:
# - ELASTICSEARCH_URL=http://elasticsearch:9200
influxdb:
image: influxdb:latest

View File

@@ -22,13 +22,13 @@ services:
networks:
- managing-network
elasticsearch:
image: elasticsearch:8.4.1
networks:
- managing-network
# elasticsearch:
# image: elasticsearch:8.4.1
# networks:
# - managing-network
kibana:
image: kibana:8.4.1
# kibana:
# image: kibana:8.4.1
influxdb:
image: influxdb:latest

View File

@@ -71,5 +71,8 @@ namespace Managing.Domain.Bots
{
return Name;
}
public abstract void SaveBackup();
public abstract void LoadBackup(BotBackup backup);
}
}

View 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; }
}

View File

@@ -8,5 +8,7 @@
void Restart();
string GetStatus();
string GetName();
void SaveBackup();
void LoadBackup(BotBackup backup);
}
}

View 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);
}
}

View File

@@ -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; }
}

View File

@@ -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
}

View File

@@ -1,7 +1,6 @@
using Binance.Net.Clients;
using Binance.Net.Enums;
using Binance.Net.Interfaces.Clients;
using CryptoExchange.Net;
using CryptoExchange.Net.Authentication;
using Managing.Common;
using Managing.Core;

View File

@@ -1,5 +1,4 @@
using CryptoExchange.Net.Authentication;
using FTX.Net.Clients;
using FTX.Net.Interfaces.Clients;
using FTX.Net.Objects;
using Managing.Common;

View File

@@ -1,6 +1,5 @@
using Managing.Application.Abstractions;
using Managing.Core;
//using Microsoft.Extensions.Caching.Memory;
using System.Runtime.Caching;
namespace Managing.Infrastructure.Storage
@@ -8,11 +7,11 @@ namespace Managing.Infrastructure.Storage
public class TaskCache : ITaskCache
{
private MemoryCache _cache { get; } = MemoryCache.Default;
private CacheItemPolicy _defaultPolicy { get; } = new CacheItemPolicy();
public async Task<T> AddOrGetExisting<T>(string key, Func<Task<T>> valueFactory)
{
var asyncLazyValue = new AsyncLazy<T>(valueFactory);
var existingValue = (AsyncLazy<T>)_cache.AddOrGetExisting(key, asyncLazyValue, _defaultPolicy);
@@ -33,6 +32,7 @@ namespace Managing.Infrastructure.Storage
// Get the most recent value with a recursive call.
return await AddOrGetExisting(key, valueFactory);
}
return result;
}
catch (Exception)
@@ -64,7 +64,8 @@ namespace Managing.Infrastructure.Storage
public T Get<T>(string key)
{
var existingValue = (AsyncLazy<T>)_cache.Get(key);
return existingValue.Value.Result;
if (existingValue != null) return existingValue.Value.Result;
return default(T);
}
public virtual List<T> GetCache<T>()

View File

@@ -1,7 +1,4 @@
using Xunit;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Tests;
namespace Managing.Infrastructure.Tests;
public class SubgraphTests
{

View File

@@ -1,4 +1,4 @@
VITE_API_URL_LOCAL=https://localhost:5001
VITE_API_URL_LOCAL=http://localhost:5000
VITE_API_URL_SERVER=https://dev-managing-api.apps.managing.live
VITE_WORKER_URL_LOCAL=https://localhost:5002
VITE_WORKER_URL_SERVER=https://dev-managing-worker.apps.managing.live

View File

@@ -8,21 +8,24 @@ import { UserClient } from '../../../generated/ManagingApi'
import type { ILoginFormInput } from '../../../global/type'
import useCookie from '../../../hooks/useCookie'
import { SecondaryNavbar } from '../NavBar/NavBar'
import Toast from '../Toast/Toast'
const LogIn = () => {
const { apiUrl } = useApiUrlStore()
const { register, handleSubmit } = useForm<ILoginFormInput>()
const { disconnect } = useDisconnect()
const { address } = useAccount()
const { isLoading, signMessageAsync } = useSignMessage({})
const { signMessageAsync } = useSignMessage({})
const { setCookie } = useCookie()
const onSubmit: SubmitHandler<ILoginFormInput> = async (form) => {
const message = 'wagmi'
const signature = await signMessageAsync({ message })
const t = new Toast('Creating token')
if (signature && address) {
const userClient = new UserClient({}, apiUrl)
await userClient
.user_CreateToken({
address: address.toString(),
@@ -34,11 +37,11 @@ const LogIn = () => {
setCookie('token', data, 1)
location.reload()
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error(err)
.catch((err: any) => {
t.update('error', 'Error :' + err.message)
})
}else{
t.update('error', 'Error : No signature')
}
}
@@ -77,7 +80,6 @@ const LogIn = () => {
</div>
<button
type="submit"
disabled={isLoading}
className="btn bg-primary w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
>
Sign and login

View File

@@ -15,9 +15,13 @@ const baseOptions: UpdateOptions = {
class Toast {
private id: Id
constructor(content: string) {
constructor(content: string, isLoading = true) {
if (!isLoading) {
this.id = toast(content)
}else{
this.id = toast.loading(content)
}
}
update(type: TypeOptions, content: string, opts?: any) {
const options = { ...baseOptions, ...opts }

View File

@@ -1,6 +1,6 @@
import { ColorSwatchIcon, TrashIcon, XIcon } from '@heroicons/react/solid'
import { useQuery } from '@tanstack/react-query'
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore'
@@ -17,14 +17,17 @@ const BacktestScanner: React.FC = () => {
const { apiUrl } = useApiUrlStore()
const client = new BacktestClient({}, apiUrl)
const { isLoading, refetch } = useQuery({
onSuccess: (data) => {
setBacktest(data)
},
const { isLoading, refetch, data: backtests } = useQuery({
queryFn: () => client.backtest_Backtests(),
queryKey: ['backtests'],
})
useEffect(() => {
if (backtests) {
setBacktest(backtests)
}
}, [backtests])
function deleteAllBacktests() {
const t = new Toast('Deleting all backtests')
client
@@ -114,7 +117,7 @@ const BacktestScanner: React.FC = () => {
</button>
</div>
<BacktestTable list={backtestingResult} isFetching={isLoading} />
<BacktestTable list={backtestingResult} isFetching={isLoading} setBacktests={setBacktest} />
<BacktestModal
showModal={showModal}