docker files fixes from liaqat

This commit is contained in:
alirehmani
2024-05-03 16:39:25 +05:00
commit 464a8730e8
587 changed files with 44288 additions and 0 deletions

1
src/.dockerignore Normal file
View File

@@ -0,0 +1 @@

4
src/.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.cs]
# IDE0008: Use explicit type
dotnet_diagnostic.IDE0008.severity = none

View File

@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<UserSecretsId>3900ce93-de15-49e5-9a61-7dc2209939ca</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
<DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.20.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageReference Include="xunit" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Lowpro.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-sandbox.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,31 @@
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Workers;
using Microsoft.AspNetCore.Mvc;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Controllers;
[ApiController]
[Route("[controller]")]
[Produces("application/json")]
public class WorkerController : ControllerBase
{
private readonly IWorkerService _workerService;
public WorkerController(IWorkerService workerService)
{
_workerService = workerService;
}
[HttpGet]
public ActionResult<List<Worker>> GetWorkers()
{
return Ok(_workerService.GetWorkers());
}
[HttpPatch]
public async Task<ActionResult> ToggleWorker(WorkerType workerType)
{
return Ok(await _workerService.ToggleWorker(workerType));
}
}

View File

@@ -0,0 +1,36 @@
# Use the official Microsoft ASP.NET Core runtime as the base image.
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# Use the official Microsoft .NET SDK image to build the code.
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["Managing.Api.Workers/Managing.Api.Workers.csproj", "Managing.Api.Workers/"]
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
COPY ["Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj", "Managing.Infrastructure.MongoDb/"]
COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"]
COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"]
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
COPY ["Managing.Domain/Managing.Domain.csproj", "Managing.Domain/"]
COPY ["Managing.Application.Workers/Managing.Application.Workers.csproj", "Managing.Application.Workers/"]
COPY ["Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"]
COPY ["Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj", "Managing.Infrastructure.Exchanges/"]
COPY ["Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj", "Managing.Infrastructure.Database/"]
RUN dotnet restore "Managing.Api.Workers/Managing.Api.Workers.csproj"
COPY . .
WORKDIR "/src/Managing.Api.Workers"
RUN dotnet build "Managing.Api.Workers.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Managing.Api.Workers.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
COPY Managing.Api.Workers/managing_cert.pfx .
COPY Managing.Api.Workers/appsettings.Lowpro.json ./appsettings.json
ENTRYPOINT ["dotnet", "Managing.Api.Workers.dll"]

View File

@@ -0,0 +1,20 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Managing.Api.Workers.Filters
{
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
model.Enum.Clear();
Enum.GetNames(context.Type)
.ToList()
.ForEach(n => model.Enum.Add(new OpenApiString(n)));
}
}
}
}

View File

@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<UserSecretsId>3900ce93-de15-49e5-9a61-7dc2209939ca</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
<DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.20.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageReference Include="xunit" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Lowpro.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-docker.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,150 @@
using System.Text.Json.Serialization;
using Managing.Api.Workers.Filters;
using Managing.Api.Workers.Workers;
using Managing.Api.WorkersExceptions;
using Managing.Application.Hubs;
using Managing.Bootstrap;
using Managing.Common;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb;
using NSwag;
using NSwag.Generation.Processors.Security;
using Serilog;
using Serilog.Sinks.Elasticsearch;
// Builder
var builder = WebApplication.CreateBuilder(args);
builder.Configuration
.AddEnvironmentVariables();
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
{
var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-");
var indexFormat = $"managing-worker-{envName}-" + "{0:yyyy.MM.dd}";
var yourTemplateName = "dotnetlogs";
var es = new ElasticsearchSinkOptions(new Uri(hostBuilder.Configuration["ElasticConfiguration:Uri"]))
{
IndexFormat = indexFormat.ToLower(),
AutoRegisterTemplate = true,
OverwriteTemplate = true,
TemplateName = yourTemplateName,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
TypeName = null,
BatchAction = ElasticOpType.Create,
MinimumLogEventLevel = Serilog.Events.LogEventLevel.Information,
FailureCallback = e => Console.WriteLine($"Unable to submit event {e.RenderMessage()} to ElasticSearch. " +
$"Full message : " + e.Exception.Message),
DetectElasticsearchVersion = true,
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
};
loggerConfiguration
.WriteTo.Console()
.WriteTo.Elasticsearch(es);
});
builder.Services.AddOptions();
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
builder.Services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder
.SetIsOriginAllowed((host) => true)
.AllowAnyOrigin()
.WithOrigins("http://localhost:3000/")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
builder.Services.AddSignalR().AddJsonProtocol();
builder.Services.RegisterWorkersDependencies(builder.Configuration);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(document =>
{
document.AddSecurity("JWT", Enumerable.Empty<string>(), new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.ApiKey,
Name = "Authorization",
In = OpenApiSecurityApiKeyLocation.Header,
Description = "Type into the textbox: Bearer {your JWT token}."
});
document.OperationProcessors.Add(
new AspNetCoreOperationSecurityScopeProcessor("JWT"));
});
builder.Services.AddSwaggerGen(options =>
{
options.SchemaFilter<EnumSchemaFilter>();
options.AddSecurityDefinition("Bearer,", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "Please insert your JWT Token into field : Bearer {your_token}",
Name = "Authorization",
Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Scheme = "Bearer",
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme{
Reference = new Microsoft.OpenApi.Models.OpenApiReference{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[]{}
}
});
});
builder.WebHost.SetupDiscordBot();
builder.Services.AddHostedService<FeeWorker>();
builder.Services.AddHostedService<PositionManagerWorker>();
builder.Services.AddHostedService<PositionFetcher>();
builder.Services.AddHostedService<PricesFiveMinutesWorker>();
builder.Services.AddHostedService<PricesFifteenMinutesWorker>();
builder.Services.AddHostedService<PricesOneHourWorker>();
builder.Services.AddHostedService<PricesFourHoursWorker>();
builder.Services.AddHostedService<PricesOneDayWorker>();
builder.Services.AddHostedService<PositionManagerWorker>();
builder.Services.AddHostedService<SpotlightWorker>();
builder.Services.AddHostedService<TraderWatcher>();
builder.Services.AddHostedService<LeaderboardWorker>();
builder.Services.AddHostedService<NoobiesboardWorker>();
// App
var app = builder.Build();
app.UseSerilogRequestLogging();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseOpenApi();
app.UseSwaggerUi3();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Managing Workers v1");
c.RoutePrefix = string.Empty;
});
app.UseCors("CorsPolicy");
app.UseMiddleware(typeof(GlobalErrorHandlingMiddleware));
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<PositionHub>("/positionhub");
});
app.Run();

View File

@@ -0,0 +1,75 @@

using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers;
public abstract class BaseWorker<T> : BackgroundService where T : class
{
private readonly WorkerType _workerType;
protected readonly ILogger<T> _logger;
protected readonly TimeSpan _delay;
private readonly IWorkerService _workerService;
private int _executionCount;
protected BaseWorker(
WorkerType workerType,
ILogger<T> logger,
TimeSpan timeSpan,
IWorkerService workerService)
{
_workerType = workerType;
_logger = logger;
_delay = timeSpan == TimeSpan.Zero ? TimeSpan.FromMinutes(1) : timeSpan;
_workerService = workerService;
_executionCount = 0;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
try
{
_logger.LogInformation($"[{_workerType}] Starting");
var worker = await _workerService.GetWorker(_workerType);
if (worker == null)
{
await _workerService.InsertWorker(_workerType, _delay);
}
else
{
_logger.LogInformation($"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}");
_executionCount = worker.ExecutionCount;
}
cancellationToken.Register(() => _logger.LogInformation($"[{_workerType}] Stopping"));
while (!cancellationToken.IsCancellationRequested)
{
worker = await _workerService.GetWorker(_workerType);
//if (true)
if (worker.IsActive)
{
await Run(cancellationToken);
_executionCount++;
await _workerService.UpdateWorker(_workerType, _executionCount);
_logger.LogInformation($"[{_workerType}] Run ok. Next run at : {DateTime.UtcNow.Add(_delay)}");
}
else
{
_logger.LogInformation($"[{_workerType}] Worker not active. Next run at : {DateTime.UtcNow.Add(_delay)}");
}
await Task.Delay(_delay);
}
_logger.LogInformation($"[{_workerType}] Stopped");
}
catch (Exception ex)
{
_logger.LogError($"Error : {ex.Message}");
}
}
protected abstract Task Run(CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,29 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class FeeWorker : BaseWorker<FeeWorker>
{
private readonly ITradingService _tradingService;
private static readonly WorkerType _workerType = WorkerType.Fee;
public FeeWorker(
ILogger<FeeWorker> logger,
ITradingService tradingService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromHours(12),
workerService
)
{
_tradingService = tradingService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
_tradingService.UpdateFee(TradingExchanges.Evm);
}
}

View File

@@ -0,0 +1,28 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class LeaderboardWorker : BaseWorker<FeeWorker>
{
private readonly IStatisticService _statisticService;
private static readonly WorkerType _workerType = WorkerType.LeaderboardWorker;
public LeaderboardWorker(
ILogger<FeeWorker> logger,
IStatisticService statisticService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromHours(24),
workerService
)
{
_statisticService = statisticService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await _statisticService.UpdateLeaderboard();
}
}

View File

@@ -0,0 +1,28 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class NoobiesboardWorker : BaseWorker<FeeWorker>
{
private readonly IStatisticService _statisticService;
private static readonly WorkerType _workerType = WorkerType.Noobiesboard;
public NoobiesboardWorker(
ILogger<FeeWorker> logger,
IStatisticService statisticService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromHours(24),
workerService
)
{
_statisticService = statisticService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await _statisticService.UpdateNoobiesboard();
}
}

View File

@@ -0,0 +1,37 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.Workers.Abstractions;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PositionFetcher : BaseWorker<PositionFetcher>
{
private static readonly WorkerType _workerType = WorkerType.PositionFetcher;
private readonly ITradingService _tradingService;
private readonly IHubContext<PositionHub> _hubContext;
private readonly ILogger<PositionFetcher> _logger;
public PositionFetcher(
ILogger<PositionFetcher> logger,
IWorkerService workerService,
ITradingService tradingService,
IHubContext<PositionHub> hubContext) : base(
_workerType,
logger,
TimeSpan.FromSeconds(10),
workerService)
{
_logger = logger;
_tradingService = tradingService;
_hubContext = hubContext;
}
protected override async Task Run(CancellationToken cancellationToken)
{
var positions = _tradingService.GetPositions().Where(p => p.Initiator != PositionInitiator.PaperTrading);
await _hubContext.Clients.All.SendAsync("Positions", positions);
}
}

View File

@@ -0,0 +1,200 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
{
private static readonly WorkerType _workerType = WorkerType.PositionManager;
private readonly ITradingService _tradingService;
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ILogger<PositionManagerWorker> _logger;
public PositionManagerWorker(
ILogger<PositionManagerWorker> logger,
IWorkerService workerService,
ITradingService tradingService,
IExchangeService exchangeService,
IAccountService accountService) : base(
_workerType,
logger,
TimeSpan.FromMinutes(1),
workerService)
{
_logger = logger;
_tradingService = tradingService;
_exchangeService = exchangeService;
_accountService = accountService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await ManageNewPositions();
await ManagePartillyFilledPositions();
await ManageFilledPositions();
}
private async Task ManagePartillyFilledPositions()
{
var positions = GetPositions(PositionStatus.PartiallyFilled);
_logger.LogInformation("Partilly filled positions count : {0} ", positions.Count());
foreach (var position in positions)
{
_logger.LogInformation("Managing Partilly filled position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", position.Identifier, position.Date, position.OriginDirection, position.Ticker);
var account = await _accountService.GetAccount(position.AccountName, false, false);
try
{
if (position.StopLoss.Status == TradeStatus.PendingOpen)
{
var stopLoss = _exchangeService.OpenStopLoss(account, position.Ticker, position.OriginDirection, position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow).Result;
if (stopLoss != null & (stopLoss.Status == TradeStatus.Requested))
{
position.StopLoss = stopLoss;
_logger.LogInformation("|_ SL is requested");
}
else
{
throw new Exception("Stop loss not requested");
}
}
else
{
_logger.LogInformation($"|_ SL is already handle. Current status = {position.StopLoss.Status}");
}
if (position.TakeProfit1.Status == TradeStatus.PendingOpen)
{
var takeProfit1 = _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection, position.TakeProfit1.Price, position.TakeProfit1.Quantity, false, DateTime.UtcNow).Result;
if (takeProfit1 != null & (takeProfit1.Status == TradeStatus.Requested))
{
position.TakeProfit1 = takeProfit1;
_logger.LogInformation("|_ TP is requested");
}
else
{
throw new Exception("Take Profit 1 not requested");
}
}
else
{
_logger.LogInformation($"|_ TP is already handle. Current status = {position.TakeProfit1.Status}");
}
if (position.TakeProfit2 != null &&
position.TakeProfit2.Status == TradeStatus.PendingOpen)
{
var takeProfit2 = _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection, position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow).Result;
if (takeProfit2 != null & (takeProfit2.Status == TradeStatus.Requested))
{
position.TakeProfit2 = takeProfit2;
_logger.LogInformation("|_ TP2 is requested");
}
else
{
throw new Exception("Take Profit 2 not requested");
}
}
else
{
_logger.LogInformation("|_ TP2 is already handle or not required");
}
}
catch (Exception ex)
{
_logger.LogError($"|_ Cannot fully filled position because : {ex.Message}");
}
if (position.StopLoss.Status == TradeStatus.Requested
&& position.TakeProfit1.Status == TradeStatus.Requested
&& (position.TakeProfit2 == null || position.TakeProfit2.Status == TradeStatus.Requested))
{
position.Status = PositionStatus.Filled;
_logger.LogInformation($"|_ Position is now open and SL/TP are correctly requested");
}
_tradingService.UpdatePosition(position);
}
}
private async Task ManageFilledPositions()
{
var positions = GetPositions(PositionStatus.Filled);
_logger.LogInformation("Filled positions count : {0} ", positions.Count());
foreach (var position in positions)
{
_logger.LogInformation("Managing filled position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", position.Identifier, position.Date, position.OriginDirection, position.Ticker);
var account = await _accountService.GetAccount(position.AccountName, false, false);
var updatedPosition = await _tradingService.ManagePosition(account, position);
_tradingService.UpdatePosition(updatedPosition);
}
}
private IEnumerable<Position> GetPositions(PositionStatus positionStatus)
{
return _tradingService.GetPositionsByStatus(positionStatus)
.Where(p => p.Initiator != PositionInitiator.PaperTrading);
}
private async Task ManageNewPositions()
{
var positions = GetPositions(PositionStatus.New);
_logger.LogInformation("New positions count : {0} ", positions.Count());
foreach (var position in positions)
{
_logger.LogInformation("Managing position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", position.Identifier, position.Date, position.OriginDirection, position.Ticker);
position.Status = PositionStatus.Updating;
_tradingService.UpdatePosition(position);
// Update status if position is open since to long
if (position.Date < DateTime.UtcNow.AddDays(-2))
{
position.Status = PositionStatus.Canceled;
_tradingService.UpdatePosition(position);
_logger.LogInformation($"|_ Position is now Canceled");
continue;
}
var account = await _accountService.GetAccount(position.AccountName, false, false);
if (!(await _exchangeService.GetOpenOrders(account, position.Ticker)).Any())
{
position.Status = PositionStatus.Canceled;
_tradingService.UpdatePosition(position);
_logger.LogInformation($"|_ Position is now Canceled - Position close from exchange");
continue;
}
var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker);
if (quantityInPosition <= 0)
{
position.Status = PositionStatus.New;
_logger.LogInformation("|_ Position is currently waiting for filling");
}
else
{
position.Open.SetStatus(TradeStatus.Filled);
position.Status = PositionStatus.PartiallyFilled;
_logger.LogInformation($"|_ Position is now PartiallyFilled");
}
_tradingService.UpdatePosition(position);
}
}
}

View File

@@ -0,0 +1,40 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public abstract class PricesBaseWorker<T> : BaseWorker<T> where T : class
{
private readonly IPricesService _pricesService;
private readonly IStatisticService _statisticService;
private readonly Timeframe _timeframe;
public PricesBaseWorker(
ILogger<T> logger,
IPricesService pricesService,
IWorkerService workerService,
IStatisticService statisticService,
TimeSpan delay,
WorkerType workerType,
Timeframe timeframe) : base(
workerType,
logger,
delay,
workerService
)
{
_pricesService = pricesService;
_statisticService = statisticService;
_timeframe = timeframe;
}
protected override async Task Run(CancellationToken cancellationToken)
{
var tickers = _statisticService.GetTickers();
foreach (var ticker in tickers)
{
await _pricesService.UpdatePrice(TradingExchanges.Evm, ticker, _timeframe);
}
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesFifteenMinutesWorker : PricesBaseWorker<PricesFifteenMinutesWorker>
{
public PricesFifteenMinutesWorker(
ILogger<PricesFifteenMinutesWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromMinutes(1),
WorkerType.PriceFifteenMinutes,
Timeframe.FifteenMinutes
)
{
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesFiveMinutesWorker : PricesBaseWorker<PricesFiveMinutesWorker>
{
public PricesFiveMinutesWorker(
ILogger<PricesFiveMinutesWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromMinutes(2.5),
WorkerType.PriceFiveMinutes,
Timeframe.FiveMinutes
)
{
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesFourHoursWorker : PricesBaseWorker<PricesFourHoursWorker>
{
public PricesFourHoursWorker(
ILogger<PricesFourHoursWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromHours(2),
WorkerType.PriceFourHour,
Timeframe.FourHour
)
{
}
}

View File

@@ -0,0 +1,23 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesOneDayWorker : PricesBaseWorker<PricesOneDayWorker>
{
public PricesOneDayWorker(
ILogger<PricesOneDayWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromHours(12),
WorkerType.PriceOneDay,
Timeframe.OneDay
)
{
}
}

View File

@@ -0,0 +1,22 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class PricesOneHourWorker : PricesBaseWorker<PricesOneHourWorker>
{
public PricesOneHourWorker(
ILogger<PricesOneHourWorker> logger,
IPricesService pricesService,
IStatisticService statisticService,
IWorkerService workerService) : base(
logger,
pricesService,
workerService,
statisticService,
TimeSpan.FromMinutes(30),
WorkerType.PriceOneHour,
Timeframe.OneHour)
{
}
}

View File

@@ -0,0 +1,34 @@
using Managing.Application.Workers.Abstractions;
using Managing.Common;
namespace Managing.Api.Workers.Workers;
public class SpotlightWorker : BaseWorker<SpotlightWorker>
{
private readonly IStatisticService _statisticService;
public SpotlightWorker(
ILogger<SpotlightWorker> logger,
IWorkerService workerService,
IStatisticService statisticService) : base(
Enums.WorkerType.Spotlight,
logger,
TimeSpan.FromMinutes(5),
workerService)
{
_statisticService = statisticService;
}
protected async override Task Run(CancellationToken cancellationToken)
{
try
{
await _statisticService.UpdateSpotlight();
}
catch (Exception ex)
{
_logger.LogError("Enable to update spotlight", ex);
throw;
}
}
}

View File

@@ -0,0 +1,28 @@
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class TopVolumeTickerWorker : BaseWorker<TopVolumeTickerWorker>
{
private readonly IStatisticService _statisticService;
private static readonly WorkerType _workerType = WorkerType.TopVolumeTicker;
public TopVolumeTickerWorker(
ILogger<TopVolumeTickerWorker> logger,
IWorkerService workerService,
IStatisticService statisticService) : base(
_workerType,
logger,
TimeSpan.FromHours(12),
workerService
)
{
_statisticService = statisticService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await _statisticService.UpdateTopVolumeTicker(TradingExchanges.Evm, 10);
}
}

View File

@@ -0,0 +1,29 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using static Managing.Common.Enums;
namespace Managing.Api.Workers.Workers;
public class TraderWatcher : BaseWorker<FeeWorker>
{
private readonly ITradingService _tradingService;
private static readonly WorkerType _workerType = WorkerType.TraderWatcher;
public TraderWatcher(
ILogger<FeeWorker> logger,
ITradingService tradingService,
IWorkerService workerService) : base(
_workerType,
logger,
TimeSpan.FromSeconds(120),
workerService
)
{
_tradingService = tradingService;
}
protected override async Task Run(CancellationToken cancellationToken)
{
await _tradingService.WatchTrader();
}
}

View File

@@ -0,0 +1,23 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,24 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb",
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"Organization": "",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,39 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://influxdb:8086",
"Token": "OPjdwQBmKr0zQecJ10IDQ4bt32oOJzmFp687QWWzbGeyH0R-gCA6HnXI_B0oQ_InPmSUXKFje8DSAUPbY0hn-w==",
"Organization": "managing-org"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Discord": {
"ApplicationId": "966075382002516031",
"PublicKey": "63028f6bb740cd5d26ae0340b582dee2075624011b28757436255fc002ca8a7c",
"TokenId": "OTY2MDc1MzgyMDAyNTE2MDMx.Yl8dzw.xpeIAaMwGrwTNY4r9JYv0ebzb-U",
"SignalChannelId": 1134858150667898910,
"TradesChannelId": 1134858092530634864,
"TroublesChannelId": 1134858233031446671,
"CopyTradingChannelId": 1134857874896588881,
"RequestsChannelId": 1018589494968078356,
"LeaderboardChannelId": 1133169725237633095,
"NoobiesboardChannelId": 1133504653485690940,
"ButtonExpirationMinutes": 10
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,24 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://influxdb:8086/",
"Organization": "",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,24 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb",
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"Organization": "",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,27 @@
using Managing.Application.Abstractions.Services;
namespace Managing.Api.Authorization;
public class JwtMiddleware
{
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next, IConfiguration config)
{
_next = next;
}
public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var userId = jwtUtils.ValidateJwtToken(token);
if (userId != null)
{
// attach user to context on successful jwt validation
context.Items["User"] = await userService.GetUserByAddressAsync(userId);
}
await _next(context);
}
}

View File

@@ -0,0 +1,70 @@
using Managing.Domain.Users;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Managing.Api.Authorization;
public interface IJwtUtils
{
public string GenerateJwtToken(User user, string publicAddress);
public string ValidateJwtToken(string token);
}
public class JwtUtils : IJwtUtils
{
private readonly string _secret;
public JwtUtils(IConfiguration config)
{
_secret = config.GetValue<string>("Jwt:Secret");
}
public string GenerateJwtToken(User user, string publicAddress)
{
// generate token that is valid for 15 minutes
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("address", publicAddress) }),
Expires = DateTime.UtcNow.AddDays(15),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public string ValidateJwtToken(string token)
{
if (token == null || string.IsNullOrEmpty(token))
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_secret);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var address = jwtToken.Claims.First(x => x.Type == "address").Value;
// return user id from JWT token if validation successful
return address;
}
catch
{
// return null if validation fails
return null;
}
}
}

View File

@@ -0,0 +1,58 @@
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Managing.Api.Controllers
{
[Authorize]
public class AccountController : BaseController
{
private readonly IAccountService _AccountService;
public AccountController(
IAccountService AccountService,
IUserService userService)
: base(userService)
{
_AccountService = AccountService;
}
[HttpPost]
public async Task<ActionResult<Account>> PostAccount(Account Account)
{
var user = await GetUser();
return Ok(await _AccountService.CreateAccount(user, Account));
}
[HttpGet]
[Route("accounts")]
public async Task<ActionResult<IEnumerable<Account>>> GetAccounts()
{
var user = await GetUser();
return Ok(_AccountService.GetAccountsByUser(user, true));
}
[HttpGet]
[Route("balances")]
public async Task<ActionResult<IEnumerable<Account>>> GetAccountsBalances()
{
var user = await GetUser();
return Ok(_AccountService.GetAccountsBalancesByUser(user));
}
[HttpGet]
public async Task<ActionResult<Account>> GetAccount(string name)
{
var user = await GetUser();
return Ok(await _AccountService.GetAccountByUser(user, name, true, true));
}
[HttpDelete]
public ActionResult DeleteAccount(string name)
{
var user = GetUser().Result;
return Ok(_AccountService.DeleteAccount(user, name));
}
}
}

View File

@@ -0,0 +1,133 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Domain.Backtests;
using Managing.Domain.MoneyManagements;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
[Produces("application/json")]
public class BacktestController : ControllerBase
{
private readonly IHubContext<BotHub> _hubContext;
private readonly IBacktester _backtester;
private readonly IScenarioService _scenarioService;
private readonly IAccountService _accountService;
private readonly IMoneyManagementService _moneyManagementService;
public BacktestController(
IHubContext<BotHub> hubContext,
IBacktester backtester,
IScenarioService scenarioService,
IAccountService accountService,
IMoneyManagementService moneyManagementService)
{
_hubContext = hubContext;
_backtester = backtester;
_scenarioService = scenarioService;
_accountService = accountService;
_moneyManagementService = moneyManagementService;
}
[HttpGet]
public ActionResult<IEnumerable<Backtest>> Backtests()
{
return Ok(_backtester.GetBacktests());
}
[HttpDelete]
public ActionResult DeleteBacktest(string id)
{
return Ok(_backtester.DeleteBacktest(id));
}
[HttpDelete]
[Route("deleteAll")]
public ActionResult DeleteBacktests()
{
return Ok(_backtester.DeleteBacktests());
}
[HttpPost]
[Route("Run")]
public async Task<ActionResult<Backtest>> Run(string accountName,
BotType botType,
Ticker ticker,
string scenarioName,
Timeframe timeframe,
bool watchOnly,
int days,
decimal balance,
string moneyManagementName,
MoneyManagement? moneyManagement = null,
bool save = false)
{
if (string.IsNullOrEmpty(accountName))
{
throw new ArgumentException($"'{nameof(accountName)}' cannot be null or empty.", nameof(accountName));
}
if (string.IsNullOrEmpty(scenarioName))
{
throw new ArgumentException($"'{nameof(scenarioName)}' cannot be null or empty.", nameof(scenarioName));
}
if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null)
{
throw new ArgumentException($"'{nameof(moneyManagementName)}' and '{nameof(moneyManagement)}' cannot be null or empty.", nameof(moneyManagementName));
}
if (days > 0)
{
days = days * -1;
}
Backtest backtestResult = null;
var scenario = _scenarioService.GetScenario(scenarioName);
var account = await _accountService.GetAccount(accountName, true, false);
if (!string.IsNullOrEmpty(moneyManagementName) && moneyManagement is null)
{
moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName);
}
else
{
moneyManagement.FormatPercentage();
}
if (scenario == null)
return BadRequest("No scenario found");
switch (botType)
{
case BotType.SimpleBot:
break;
case BotType.ScalpingBot:
backtestResult = _backtester.RunScalpingBotBacktest(account, moneyManagement, ticker, scenario,
timeframe, Convert.ToDouble(days), balance, watchOnly, save);
break;
case BotType.FlippingBot:
backtestResult = _backtester.RunFlippingBotBacktest(account, moneyManagement, ticker, scenario,
timeframe, Convert.ToDouble(days), balance, watchOnly, save);
break;
}
await NotifyBacktesingSubscriberAsync(backtestResult);
return Ok(backtestResult);
}
private async Task NotifyBacktesingSubscriberAsync(Backtest backtesting)
{
if(backtesting != null){
await _hubContext.Clients.All.SendAsync("BacktestsSubscription", backtesting);
}
}
}

View File

@@ -0,0 +1,35 @@
using Managing.Application.Abstractions.Services;
using Managing.Domain.Users;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace Managing.Api.Controllers;
[ApiController]
[Route("[controller]")]
[Produces("application/json")]
public abstract class BaseController : ControllerBase
{
private readonly IUserService _userService;
public BaseController(IUserService userService)
{
_userService = userService;
}
protected async Task<User> GetUser()
{
var identity = HttpContext?.User.Identity as ClaimsIdentity;
if (identity != null)
{
var address = identity.Claims.FirstOrDefault(c => c.Type == "address").Value;
var user = await _userService.GetUserByAddressAsync(address);
if (user != null)
return user;
throw new Exception("User not found for this token");
}
throw new Exception("Not identity assigned to this token");
}
}

View File

@@ -0,0 +1,168 @@
using Managing.Api.Models.Requests;
using Managing.Api.Models.Responses;
using Managing.Application.Abstractions;
using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
[Produces("application/json")]
public class BotController : ControllerBase
{
private readonly IMediator _mediator;
private readonly ILogger<BotController> _logger;
private readonly IHubContext<BotHub> _hubContext;
private readonly IBacktester _backtester;
public BotController(ILogger<BotController> logger, IMediator mediator, IHubContext<BotHub> hubContext, IBacktester backtester)
{
_logger = logger;
_mediator = mediator;
_hubContext = hubContext;
_backtester = backtester;
}
[HttpPost]
[Route("Start")]
public async Task<ActionResult<string>> Start(StartBotRequest request)
{
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, request.IsForWatchOnly));
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpGet]
[Route("Stop")]
public async Task<ActionResult<string>> Stop(BotType botType, string botName)
{
var result = await _mediator.Send(new StopBotCommand(botType, botName));
_logger.LogInformation($"{botType} type called {botName} is now {result}");
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpDelete]
[Route("Delete")]
public async Task<ActionResult<bool>> Delete(string botName)
{
var result = await _mediator.Send(new DeleteBotCommand(botName));
_logger.LogInformation($"{botName} is now deleted");
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpGet]
[Route("StopAll")]
public async Task<string> StopAll()
{
var bots = await GetBotList();
var result = "";
foreach (var bot in bots)
{
result += $"{bot.Name} : ";
result = await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
result += $" |";
}
await NotifyBotSubscriberAsync();
return result;
}
[HttpGet]
[Route("Restart")]
public async Task<ActionResult<string>> Restart(BotType botType, string botName)
{
var result = await _mediator.Send(new RestartBotCommand(botType, botName));
_logger.LogInformation($"{botType} type called {botName} is now {result}");
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpGet]
[Route("RestartAll")]
public async Task<string> RestartAll()
{
var bots = await GetBotList();
var result = "";
foreach (var bot in bots)
{
result += $"{bot.Name} : ";
result = await _mediator.Send(new RestartBotCommand(bot.BotType, bot.Name));
result += $" |";
}
await NotifyBotSubscriberAsync();
return result;
}
[HttpGet]
[Route("ToggleIsForWatching")]
public async Task<ActionResult<string>> ToggleIsForWatching(string botName)
{
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName));
_logger.LogInformation($"{botName} bot is now {result}");
await NotifyBotSubscriberAsync();
return Ok(result);
}
[HttpGet]
public async Task<List<TradingBot>> GetActiveBots()
{
return await GetBotList();
}
private async Task<List<TradingBot>> GetBotList()
{
var result = await _mediator.Send(new GetActiveBotsCommand());
var list = new List<TradingBot>();
foreach (var item in result)
{
list.Add(new TradingBot
{
Status = item.GetStatus(),
Name = item.GetName(),
Candles = item.Candles.ToList(),
Positions = item.Positions,
Signals = item.Signals.ToList(),
WinRate = item.GetWinRate(),
ProfitAndLoss = item.GetProfitAndLoss(),
Timeframe = item.Timeframe,
Ticker = item.Ticker,
AccountName = item.AccountName,
Scenario = item.Scenario,
IsForWatchingOnly = item.IsForWatchingOnly,
BotType = item.BotType,
MoneyManagement = item.MoneyManagement
});
}
return list;
}
private async Task NotifyBotSubscriberAsync()
{
var botsList = await GetBotList();
await _hubContext.Clients.All.SendAsync("BotsSubscription", botsList);
}
}

View File

@@ -0,0 +1,73 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Candles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
public class DataController : ControllerBase
{
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ICacheService _cacheService;
private readonly IStatisticService _statisticService;
private readonly IHubContext<CandleHub> _hubContext;
public DataController(
IExchangeService exchangeService,
IAccountService accountService,
ICacheService cacheService,
IStatisticService statisticService,
IHubContext<CandleHub> hubContext)
{
_exchangeService = exchangeService;
_accountService = accountService;
_cacheService = cacheService;
_statisticService = statisticService;
_hubContext = hubContext;
}
[HttpPost("GetTickers")]
public async Task<ActionResult<Ticker[]>> GetTickers(string accountName, Timeframe timeframe)
{
var account = await _accountService.GetAccount(accountName, true, false);
var cacheKey = string.Concat(accountName, timeframe.ToString());
var tickers = _cacheService.GetOrSave(cacheKey, () =>
{
return _exchangeService.GetTickers(account, timeframe).Result;
}, TimeSpan.FromHours(2));
return Ok(tickers);
}
[HttpGet("Spotlight")]
public ActionResult<SpotlightOverview> GetSpotlight()
{
var overview = _cacheService.GetOrSave(nameof(SpotlightOverview), () =>
{
return _statisticService.GetLastSpotlight(DateTime.Now.AddHours(-3));
}, TimeSpan.FromMinutes(2));
if (overview?.Spotlights.Count < overview?.ScenarioCount)
{
overview = _statisticService.GetLastSpotlight(DateTime.Now.AddHours(-3));
}
return Ok(overview);
}
[HttpGet("GetCandles")]
public async Task<ActionResult<List<Candle>>> GetCandles(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe)
{
return Ok(await _exchangeService.GetCandlesInflux(exchange, ticker, startDate, timeframe));
}
}

View File

@@ -0,0 +1,44 @@
using Managing.Application.Abstractions;
using Managing.Domain.MoneyManagements;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Managing.Api.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
[Produces("application/json")]
public class MoneyManagementController : ControllerBase
{
private readonly IMoneyManagementService _moneyManagementService;
public MoneyManagementController(IMoneyManagementService moneyManagementService)
{
_moneyManagementService = moneyManagementService;
}
[HttpPost]
public async Task<ActionResult<MoneyManagement>> PostMoneyManagement(MoneyManagement moneyManagement)
{
return Ok(await _moneyManagementService.CreateOrUpdateMoneyManagement(moneyManagement));
}
[HttpGet]
[Route("moneymanagements")]
public ActionResult<IEnumerable<MoneyManagement>> GetMoneyManagements()
{
return Ok(_moneyManagementService.GetMoneyMangements());
}
[HttpGet]
public ActionResult<MoneyManagement> GetMoneyManagement(string name)
{
return Ok(_moneyManagementService.GetMoneyMangement(name));
}
[HttpDelete]
public ActionResult DeleteMoneyManagement(string name)
{
return Ok(_moneyManagementService.DeleteMoneyManagement(name));
}
}

View File

@@ -0,0 +1,83 @@
using Managing.Application.Abstractions;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
[Produces("application/json")]
public class ScenarioController : ControllerBase
{
private readonly IScenarioService _scenarioService;
public ScenarioController(IScenarioService scenarioService)
{
_scenarioService = scenarioService;
}
[HttpGet]
public ActionResult<IEnumerable<Scenario>> GetScenarios()
{
return Ok(_scenarioService.GetScenarios());
}
[HttpPost]
public ActionResult<Scenario> CreateScenario(string name, List<string> strategies)
{
return Ok(_scenarioService.CreateScenario(name, strategies));
}
[HttpDelete]
public ActionResult DeleteScenario(string name)
{
return Ok(_scenarioService.DeleteScenario(name));
}
[HttpGet]
[Route("strategy")]
public ActionResult<IEnumerable<Strategy>> GetStrategies()
{
return Ok(_scenarioService.GetStrategies());
}
[HttpPost]
[Route("strategy")]
public ActionResult<Strategy> CreateStrategy(
StrategyType strategyType,
Timeframe timeframe,
string name,
int? period = null,
int? fastPeriods = null,
int? slowPeriods = null,
int? signalPeriods = null,
double? multiplier = null,
int? stochPeriods = null,
int? smoothPeriods = null,
int? cyclePeriods = null)
{
return Ok(_scenarioService.CreateStrategy(
strategyType,
timeframe,
name,
period,
fastPeriods,
slowPeriods,
signalPeriods,
multiplier,
stochPeriods,
smoothPeriods,
cyclePeriods));
}
[HttpDelete]
[Route("strategy")]
public ActionResult DeleteStrategy(string name)
{
return Ok(_scenarioService.DeleteStrategy(name));
}
}

View File

@@ -0,0 +1,30 @@
using Managing.Application.Abstractions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Managing.Api.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
[Produces("application/json")]
public class SettingsController : ControllerBase
{
private readonly ISettingsService _settingsService;
public SettingsController(ISettingsService settingsService)
{
_settingsService = settingsService;
}
[HttpPost]
public ActionResult SetupSettings()
{
return Ok(_settingsService.SetupSettings());
}
[HttpDelete]
public async Task<ActionResult<bool>> ResetSettings()
{
return Ok(await _settingsService.ResetSettings());
}
}

View File

@@ -0,0 +1,107 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Trades;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using static Managing.Common.Enums;
namespace Managing.Api.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
public class TradingController : ControllerBase
{
private readonly ICommandHandler<OpenPositionRequest, Position> _openTradeCommandHandler;
private readonly ICommandHandler<ClosePositionCommand, Position> _closeTradeCommandHandler;
private readonly ITradingService _tradingService;
private readonly IMoneyManagementService _moneyManagementService;
private readonly IMediator _mediator;
private readonly ILogger<TradingController> _logger;
public TradingController(
ILogger<TradingController> logger,
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
ITradingService tradingService,
IMediator mediator)
{
_logger = logger;
_openTradeCommandHandler = openTradeCommandHandler;
_closeTradeCommandHandler = closeTradeCommandHandler;
_tradingService = tradingService;
_mediator = mediator;
}
[HttpGet("GetPositions")]
public async Task<ActionResult<List<Position>>> GetPositions(PositionInitiator positionInitiator)
{
var result = await _mediator.Send(new GetPositionsCommand(positionInitiator));
return Ok(result);
}
[HttpGet("GetTrade")]
public async Task<ActionResult<Trade>> GetTrade(string accountName, Ticker ticker, string exchangeOrderId)
{
var result = await _mediator.Send(new GetTradeCommand(accountName, exchangeOrderId, ticker));
return Ok(result);
}
[HttpGet("GetTrades")]
public async Task<ActionResult<Trade>> GetTrades(string accountName, Ticker ticker, string exchangeOrderId)
{
var result = await _mediator.Send(new GetTradesCommand(ticker, accountName));
return Ok(result);
}
[HttpGet("ClosePosition")]
public async Task<ActionResult<Position>> ClosePosition(string identifier)
{
var position = _tradingService.GetPositionByIdentifier(identifier);
var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position));
return Ok(result);
}
[HttpGet("OpenPosition")]
public async Task<ActionResult<Position>> Trade(
string accountName,
string moneyManagementName,
TradeDirection direction,
Ticker ticker,
RiskLevel riskLevel,
bool isForPaperTrading,
MoneyManagement? moneyManagement = null,
decimal? openPrice = null)
{
if (string.IsNullOrEmpty(accountName))
{
throw new ArgumentException($"'{nameof(accountName)}' cannot be null or empty.", nameof(accountName));
}
if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null)
{
throw new ArgumentException($"'{nameof(moneyManagementName)}' cannot be null or empty.", nameof(moneyManagementName));
}
if (moneyManagement == null)
{
moneyManagement = await _moneyManagementService.GetMoneyMangement(moneyManagementName);
}
var command = new OpenPositionRequest(
accountName,
moneyManagement,
direction,
ticker,
PositionInitiator.User,
DateTime.UtcNow,
isForPaperTrading: isForPaperTrading,
price: openPrice);
var result = await _openTradeCommandHandler.Handle(command);
return Ok(result);
}
}

View File

@@ -0,0 +1,39 @@
using Managing.Api.Authorization;
using Managing.Api.Models.Requests;
using Managing.Application.Abstractions.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Managing.Api.Controllers;
[ApiController]
[Route("[controller]")]
[Produces("application/json")]
public class UserController : ControllerBase
{
private IConfiguration _config;
private readonly IUserService _userService;
private readonly IJwtUtils _jwtUtils;
public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils)
{
_config = config;
_userService = userService;
_jwtUtils = jwtUtils;
}
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult<string>> CreateToken([FromBody] LoginRequest login)
{
var user = await _userService.Authenticate(login.Name, login.Address, login.Message, login.Signature);
if (user != null)
{
var tokenString = _jwtUtils.GenerateJwtToken(user, login.Address);
return Ok(tokenString);
}
return Unauthorized();
}
}

View File

@@ -0,0 +1,45 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Workflows;
using Managing.Domain.Workflows.Synthetics;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Managing.Api.Controllers
{
[Authorize]
public class WorkflowController : BaseController
{
private readonly IWorkflowService _workflowService;
public WorkflowController(IWorkflowService WorkflowService, IUserService userService) : base(userService)
{
_workflowService = WorkflowService;
}
[HttpPost]
public async Task<ActionResult<Workflow>> PostWorkflow([ModelBinder]SyntheticWorkflow workflowRequest)
{
return Ok(await _workflowService.InsertOrUpdateWorkflow(workflowRequest));
}
[HttpGet]
public ActionResult<IEnumerable<SyntheticWorkflow>> GetWorkflows()
{
return Ok(_workflowService.GetWorkflows());
}
[HttpGet]
[Route("flows")]
public async Task<ActionResult<IEnumerable<IFlow>>> GetAvailableFlows()
{
return Ok(await _workflowService.GetAvailableFlows());
}
[HttpDelete]
public ActionResult DeleteWorkflow(string name)
{
return Ok(_workflowService.DeleteWorkflow(name));
}
}
}

View File

@@ -0,0 +1,36 @@
# Use the official Microsoft ASP.NET Core runtime as the base image.
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# Use the official Microsoft .NET SDK image to build the code.
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["Managing.Api/Managing.Api.csproj", "Managing.Api/"]
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
COPY ["Managing.Infrastructure.MongoDb/Managing.Infrastructure.MongoDb.csproj", "Managing.Infrastructure.MongoDb/"]
COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"]
COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"]
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
COPY ["Managing.Domain/Managing.Domain.csproj", "Managing.Domain/"]
COPY ["Managing.Application.Workers/Managing.Application.Workers.csproj", "Managing.Application.Workers/"]
COPY ["Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"]
COPY ["Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj", "Managing.Infrastructure.Exchanges/"]
COPY ["Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj", "Managing.Infrastructure.Database/"]
RUN dotnet restore "Managing.Api/Managing.Api.csproj"
COPY . .
WORKDIR "/src/Managing.Api"
RUN dotnet build "Managing.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Managing.Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
COPY Managing.Api/managing_cert.pfx .
COPY Managing.Api/appsettings.Lowpro.json .
ENTRYPOINT ["dotnet", "Managing.Api.dll"]

View File

@@ -0,0 +1,62 @@
using System.Net;
using System.Text.Json;
namespace Managing.Api.Exceptions;
public class GlobalErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public GlobalErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
HttpStatusCode status;
var exceptionType = exception.GetType();
if (exceptionType == typeof(Exception))
{
status = HttpStatusCode.InternalServerError;
}
else if (exceptionType == typeof(NotImplementedException))
{
status = HttpStatusCode.NotImplemented;
}
else if (exceptionType == typeof(UnauthorizedAccessException))
{
status = HttpStatusCode.Unauthorized;
}
else if (exceptionType == typeof(ArgumentException))
{
status = HttpStatusCode.Unauthorized;
}
else if (exceptionType == typeof(KeyNotFoundException))
{
status = HttpStatusCode.Unauthorized;
}
else
{
status = HttpStatusCode.InternalServerError;
}
var message = exception.Message;
var stackTrace = exception.StackTrace;
var exceptionResult = JsonSerializer.Serialize(new { error = message, stackTrace });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)status;
return context.Response.WriteAsync(exceptionResult);
}
}

View File

@@ -0,0 +1,20 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Managing.Api.Filters
{
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
model.Enum.Clear();
Enum.GetNames(context.Type)
.ToList()
.ForEach(n => model.Enum.Add(new OpenApiString(n)));
}
}
}
}

View File

@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
<UserSecretsId>7476db9f-ade4-414a-a420-e3ab55cb5f8d</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.10" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.20.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageReference Include="xunit" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Oda-sandbox.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Managing.Api.Models.Requests
{
public class CreateScenarioRequest
{
[Required]
public string Name { get; internal set; }
[Required]
public List<string> Strategies { get; internal set; }
}
}

View File

@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using static Managing.Common.Enums;
namespace Managing.Api.Models.Requests;
public class CreateStrategyRequest
{
[Required]
public StrategyType Type { get; internal set; }
[Required]
public Timeframe Timeframe { get; internal set; }
[Required]
public string Name { get; internal set; }
[Required]
public int Period { get; internal set; }
}

View File

@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace Managing.Api.Models.Requests;
public class LoginRequest
{
[Required]
public string Name { get; set; }
[Required]
public string Address { get; set; }
[Required]
public string Signature { get; set; }
[Required]
public string Message { get; set; }
}

View File

@@ -0,0 +1,15 @@
using static Managing.Common.Enums;
namespace Managing.Api.Models.Requests
{
public class RunBacktestRequest
{
public TradingExchanges Exchange { get; set; }
public BotType BotType { get; set; }
public Ticker Ticker { get; set; }
public Timeframe Timeframe { get; set; }
public RiskLevel RiskLevel { get; set; }
public bool WatchOnly { get; set; }
public int Days { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using static Managing.Common.Enums;
namespace Managing.Api.Models.Requests
{
public class StartBotRequest
{
[Required]
public BotType BotType { get; set; }
[Required]
public string BotName { get; set; }
[Required]
public Ticker Ticker { get; set; }
[Required]
public Timeframe Timeframe { get; set; }
[Required]
public bool IsForWatchOnly { get; set; }
[Required]
public string Scenario { get; set; }
[Required]
public string AccountName { get; set; }
[Required]
public string MoneyManagementName { get; set; }
}
}

View File

@@ -0,0 +1,45 @@
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using System.ComponentModel.DataAnnotations;
using static Managing.Common.Enums;
namespace Managing.Api.Models.Responses
{
public class TradingBot
{
[Required]
public string Name { get; internal set; }
[Required]
public string Status { get; internal set; }
[Required]
public List<Signal> Signals { get; internal set; }
[Required]
public List<Position> Positions { get; internal set; }
[Required]
public List<Candle> Candles { get; internal set; }
[Required]
public RiskLevel RiskLevel { get; internal set; }
[Required]
public int WinRate { get; internal set; }
[Required]
public decimal ProfitAndLoss { get; internal set; }
[Required]
public Timeframe Timeframe { get; internal set; }
[Required]
public Ticker Ticker { get; internal set; }
[Required]
public string Scenario { get; internal set; }
[Required]
public TradingExchanges Exchange { get; internal set; }
[Required]
public bool IsForWatchingOnly { get; internal set; }
[Required]
public BotType BotType { get; internal set; }
[Required]
public string AccountName { get; internal set; }
[Required]
public MoneyManagement MoneyManagement { get; internal set; }
}
}

168
src/Managing.Api/Program.cs Normal file
View File

@@ -0,0 +1,168 @@
using System.Text;
using System.Text.Json.Serialization;
using Managing.Api.Authorization;
using Managing.Api.Exceptions;
using Managing.Api.Filters;
using Managing.Application.Hubs;
using Managing.Bootstrap;
using Managing.Common;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using NSwag;
using NSwag.Generation.Processors.Security;
using Serilog;
using Serilog.Sinks.Elasticsearch;
// Builder
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("appsettings.Lowpro.json", optional: true, reloadOnChange: true)
.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(".", "-");
var indexFormat = $"managing-{envName}-" + "{0:yyyy.MM.dd}";
var yourTemplateName = "dotnetlogs";
var es = new ElasticsearchSinkOptions(new Uri(hostBuilder.Configuration["ElasticConfiguration:Uri"]))
{
IndexFormat = indexFormat.ToLower(),
AutoRegisterTemplate = true,
OverwriteTemplate = true,
TemplateName = yourTemplateName,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
TypeName = null,
BatchAction = ElasticOpType.Create,
MinimumLogEventLevel = Serilog.Events.LogEventLevel.Information,
FailureCallback = e => Console.WriteLine($"Unable to submit event {e.RenderMessage()} to ElasticSearch. " +
$"Full message : " + e.Exception.Message),
DetectElasticsearchVersion = true,
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
};
loggerConfiguration
.WriteTo.Console()
.WriteTo.Elasticsearch(es);
});
builder.Services.AddOptions();
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
builder.Services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
{
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Authentication:Schemes:Bearer:ValidIssuer"],
ValidAudience = builder.Configuration["Authentication:Schemes:Bearer:ValidAudiences"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"])),
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true
};
});
builder.Services.AddAuthorization();
builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder
.SetIsOriginAllowed((host) => true)
.AllowAnyOrigin()
.WithOrigins("http://localhost:3000/")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
builder.Services.AddSignalR().AddJsonProtocol();
builder.Services.AddScoped<IJwtUtils, JwtUtils>();
builder.Services.RegisterApiDependencies(builder.Configuration);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(document =>
{
document.AddSecurity("JWT", Enumerable.Empty<string>(), new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.ApiKey,
Name = "Authorization",
In = OpenApiSecurityApiKeyLocation.Header,
Description = "Type into the textbox: Bearer {your JWT token}."
});
document.OperationProcessors.Add(
new AspNetCoreOperationSecurityScopeProcessor("JWT"));
});
builder.Services.AddSwaggerGen(options =>
{
options.SchemaFilter<EnumSchemaFilter>();
options.AddSecurityDefinition("Bearer,", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "Please insert your JWT Token into field : Bearer {your_token}",
Name = "Authorization",
Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Scheme = "Bearer",
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme{
Reference = new Microsoft.OpenApi.Models.OpenApiReference{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[]{}
}
});
});
builder.WebHost.SetupDiscordBot();
// App
var app = builder.Build();
app.UseSerilogRequestLogging();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseOpenApi();
app.UseSwaggerUi3();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Managing API v1");
c.RoutePrefix = string.Empty;
});
app.UseCors("CorsPolicy");
app.UseMiddleware(typeof(GlobalErrorHandlingMiddleware));
app.UseMiddleware<JwtMiddleware>();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<BotHub>("/bothub");
endpoints.MapHub<BacktestHub>("/backtesthub");
endpoints.MapHub<CandleHub>("/candlehub");
});
app.Run();

View File

@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Error",
"System": "Error",
"Microsoft": "Warning"
}
},
"AllowedHosts": "*",
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200/"
}
}

View File

@@ -0,0 +1,34 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"Organization": "",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Discord": {
"ApplicationId": "",
"PublicKey": "",
"SignalChannelId": 1018897743118340180,
"TroublesChannelId": 1018897743118340180,
"TradesChannelId": 1020457417877753886,
"RequestChannelId": 1020463151034138694,
"RequestsChannelId": 1020463151034138694,
"ButtonExpirationMinutes": 2
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,46 @@
{
"Authentication": {
"Schemes": {
"Bearer": {
"ValidAudiences": [ "http://localhost:3000/" ],
"ValidIssuer": "Managing"
}
}
},
"Jwt": {
"Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6"
},
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://influxdb:8086/",
"Organization": "managing-org",
"Token": "OPjdwQBmKr0zQecJ10IDQ4bt32oOJzmFp687QWWzbGeyH0R-gCA6HnXI_B0oQ_InPmSUXKFje8DSAUPbY0hn-w=="
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Discord": {
"ApplicationId": "966075382002516031",
"PublicKey": "63028f6bb740cd5d26ae0340b582dee2075624011b28757436255fc002ca8a7c",
"TokenId": "OTY2MDc1MzgyMDAyNTE2MDMx.Yl8dzw.xpeIAaMwGrwTNY4r9JYv0ebzb-U",
"SignalChannelId": 1134858150667898910,
"TradesChannelId": 1134858092530634864,
"TroublesChannelId": 1134858233031446671,
"CopyTradingChannelId": 1134857874896588881,
"RequestsChannelId": 1018589494968078356,
"ButtonExpirationMinutes": 10
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,37 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://influxdb:8086/",
"Organization": "",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Discord": {
"BotActivity": "trading strategies",
"HandleUserAction": true,
"ApplicationId": "",
"PublicKey": "",
"TokenId": "",
"SignalChannelId": 966080506473099314,
"TradesChannelId": 998374177763491851,
"TroublesChannelId": 1015761955321040917,
"CopyTradingChannelId": 1132022887012909126,
"RequestsChannelId": 1018589494968078356,
"ButtonExpirationMinutes": 10
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,36 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb:27017",
"DatabaseName": "ManagingDb",
"UserName": "admin",
"Password": "!MotdepasseFort11"
},
"InfluxDb": {
"Url": "http://influxdb:8086/",
"Organization": "managing-org",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Discord": {
"ApplicationId": "",
"PublicKey": "",
"TokenId": "",
"SignalChannelId": 966080506473099314,
"TradesChannelId": 998374177763491851,
"TroublesChannelId": 1015761955321040917,
"RequestsChannelId": 1018589494968078356,
"ButtonExpirationMinutes": 2
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,34 @@
{
"ManagingDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://localhost:8086/",
"Organization": "",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Discord": {
"ApplicationId": "",
"PublicKey": "",
"TokenId": "",
"SignalChannelId": 966080506473099314,
"TradesChannelId": 998374177763491851,
"TroublesChannelId": 1015761955321040917,
"RequestsChannelId": 1018589494968078356,
"ButtonExpirationMinutes": 2
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,48 @@
{
"Authentication": {
"Schemes": {
"Bearer": {
"ValidAudiences": [ "http://localhost:3000/" ],
"ValidIssuer": "Managing"
}
}
},
"Jwt": {
"Secret": "2ed5f490-b6c1-4cad-8824-840c911f1fe6"
},
"ManagingDatabase": {
"ConnectionString": "mongodb://managingdb",
"DatabaseName": "ManagingDb"
},
"InfluxDb": {
"Url": "http://influxdb:8086/",
"Organization": "",
"Token": ""
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
},
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200/"
},
"Discord": {
"BotActivity": "trading strategies",
"HandleUserAction": true,
"ApplicationId": "",
"PublicKey": "",
"TokenId": "",
"SignalChannelId": 966080506473099314,
"TradesChannelId": 998374177763491851,
"TroublesChannelId": 1015761955321040917,
"CopyTradingChannelId": 1132022887012909126,
"RequestsChannelId": 1018589494968078356,
"ButtonExpirationMinutes": 10
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "Dockerfile"
}

View File

@@ -0,0 +1,19 @@
{"Timestamp":"2022-09-13T02:59:02.0040536+02:00","Level":"Information","MessageTemplate":"02:59:01 Discord Discord.Net v3.8.0 (API v9)","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:02.0892523+02:00","Level":"Information","MessageTemplate":"02:59:02 Gateway Connecting","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:02.2504922+02:00","Level":"Information","MessageTemplate":"Now listening on: {address}","Properties":{"address":"https://localhost:5001","EventId":{"Id":14,"Name":"ListeningOnAddress"},"SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:02.2556804+02:00","Level":"Information","MessageTemplate":"Now listening on: {address}","Properties":{"address":"http://localhost:5000","EventId":{"Id":14,"Name":"ListeningOnAddress"},"SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:02.2591276+02:00","Level":"Information","MessageTemplate":"Application started. Press Ctrl+C to shut down.","Properties":{"SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:02.2625140+02:00","Level":"Information","MessageTemplate":"Hosting environment: {envName}","Properties":{"envName":"Development","SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:02.2651704+02:00","Level":"Information","MessageTemplate":"Content root path: {contentRoot}","Properties":{"contentRoot":"C:\\Users\\Utilisateur\\Desktop\\Projects\\apps\\Managing\\src\\Managing.Api\\","SourceContext":"Microsoft.Hosting.Lifetime","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:03.3572754+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway You're using the GuildScheduledEvents gateway intent without listening to any events related to that intent, consider removing the intent from your config.","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:03.3628433+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway You're using the GuildInvites gateway intent without listening to any events related to that intent, consider removing the intent from your config.","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:03.3688717+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway Connected","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:03.9425506+02:00","Level":"Information","MessageTemplate":"02:59:03 Gateway Ready","Properties":{"SourceContext":"Managing.Infrastructure.Messengers.Discord.DiscordService","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"}}
{"Timestamp":"2022-09-13T02:59:10.5882097+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/index.html","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/index.html - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000001","RequestPath":"/index.html","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/index.html - -"}]}}
{"Timestamp":"2022-09-13T02:59:11.5711332+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":996.3451,"StatusCode":200,"ContentType":"text/html;charset=utf-8","ContentLength":null,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/index.html","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/index.html - - - 200 - text/html;charset=utf-8 996.3451ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000001","RequestPath":"/index.html","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/index.html - - - 200 - text/html;charset=utf-8 996.3451ms"}]}}
{"Timestamp":"2022-09-13T02:59:11.6123850+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_framework/aspnetcore-browser-refresh.js","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000003","RequestPath":"/_framework/aspnetcore-browser-refresh.js","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - -"}]}}
{"Timestamp":"2022-09-13T02:59:11.6281716+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":15.5581,"StatusCode":200,"ContentType":"application/javascript; charset=utf-8","ContentLength":11994,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_framework/aspnetcore-browser-refresh.js","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - - - 200 11994 application/javascript;+charset=utf-8 15.5581ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000003","RequestPath":"/_framework/aspnetcore-browser-refresh.js","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/_framework/aspnetcore-browser-refresh.js - - - 200 11994 application/javascript;+charset=utf-8 15.5581ms"}]}}
{"Timestamp":"2022-09-13T02:59:11.7015500+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_vs/browserLink","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/_vs/browserLink - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000005","RequestPath":"/_vs/browserLink","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/_vs/browserLink - -"}]}}
{"Timestamp":"2022-09-13T02:59:11.8214511+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":119.7799,"StatusCode":200,"ContentType":"text/javascript; charset=UTF-8","ContentLength":null,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/_vs/browserLink","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/_vs/browserLink - - - 200 - text/javascript;+charset=UTF-8 119.7799ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000005","RequestPath":"/_vs/browserLink","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/_vs/browserLink - - - 200 - text/javascript;+charset=UTF-8 119.7799ms"}]}}
{"Timestamp":"2022-09-13T02:59:11.9652804+02:00","Level":"Information","MessageTemplate":"{HostingRequestStartingLog:l}","Properties":{"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/swagger/v1/swagger.json","QueryString":"","HostingRequestStartingLog":"Request starting HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - -","EventId":{"Id":1},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000007","RequestPath":"/swagger/v1/swagger.json","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestStartingLog":[{"Format":"l","Rendering":"Request starting HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - -"}]}}
{"Timestamp":"2022-09-13T02:59:12.3915820+02:00","Level":"Information","MessageTemplate":"{HostingRequestFinishedLog:l}","Properties":{"ElapsedMilliseconds":426.2987,"StatusCode":200,"ContentType":"application/json;charset=utf-8","ContentLength":null,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:5001","PathBase":"","Path":"/swagger/v1/swagger.json","QueryString":"","HostingRequestFinishedLog":"Request finished HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - - - 200 - application/json;charset=utf-8 426.2987ms","EventId":{"Id":2},"SourceContext":"Microsoft.AspNetCore.Hosting.Diagnostics","RequestId":"0HMKL4CJ4LVHE:00000007","RequestPath":"/swagger/v1/swagger.json","ConnectionId":"0HMKL4CJ4LVHE","Environment":"Microsoft.AspNetCore.Hosting.HostingEnvironment"},"Renderings":{"HostingRequestFinishedLog":[{"Format":"l","Rendering":"Request finished HTTP/2 GET https://localhost:5001/swagger/v1/swagger.json - - - 200 - application/json;charset=utf-8 426.2987ms"}]}}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@
using Managing.Domain.Accounts;
namespace Managing.Application.Abstractions.Repositories;
public interface IAccountRepository
{
Task<Account> GetAccountByNameAsync(string name);
Task<Account> GetAccountByKeyAsync(string key);
Task InsertAccountAsync(Account account);
void DeleteAccountByName(string name);
IEnumerable<Account> GetAccounts();
}

View File

@@ -0,0 +1,12 @@

using Managing.Domain.Backtests;
namespace Managing.Application.Abstractions;
public interface IBacktestRepository
{
void InsertBacktest(Backtest result);
IEnumerable<Backtest> GetBacktests();
void DeleteBacktestById(string id);
void DeleteAllBacktests();
}

View File

@@ -0,0 +1,18 @@
using Managing.Common;
using Managing.Domain.Candles;
namespace Managing.Application.Abstractions.Repositories;
public interface ICandleRepository
{
Task<IList<Candle>> GetCandles(
Enums.TradingExchanges exchange,
Enums.Ticker ticker,
Enums.Timeframe timeframe,
DateTime start);
Task<IList<Enums.Ticker>> GetTickersAsync(
Enums.TradingExchanges exchange,
Enums.Timeframe timeframe,
DateTime start);
void InsertCandle(Candle candle);
}

View File

@@ -0,0 +1,36 @@
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Evm;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Repositories;
public interface IEvmManager
{
(string Key, string Secret) GenerateAddress();
string GetAddressFromMnemo(string mnemo);
Task<decimal> GetAddressBalance(string address);
Task<DateTime> GetBlockDate(int blockNumber);
Task<List<Holder>> GetContractHolders(string contractAddress, DateTime since);
string SignMessage(string message, string privateKey);
string VerifySignature(string signature, string message);
Task<List<EvmBalance>> GetBalances(Chain chain, int page, int pageSize, string publicAddress);
Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress);
Task<List<Candle>> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate, Timeframe interval);
decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker);
Task<List<Ticker>> GetAvailableTicker();
Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker);
Task<bool> InitAddress(string chainName, string publicAddress, string privateKey);
Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey, string receiverAddress);
Task<EvmBalance> GetTokenBalance(string chainName, Ticker ticker, string publicAddress);
Task<bool> CancelOrders(Account account, Ticker ticker);
Task<Trade> IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage = 1);
Task<Trade> GetTrade(Account account, string chainName, Ticker ticker);
Task<Trade> DecreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage);
Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker);
Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage);
Task<decimal> GetFee(string chainName);
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
Task<Trade> GetTrade(string reference, string arbitrum, Ticker ticker);
}

View File

@@ -0,0 +1,13 @@
using Managing.Domain.MoneyManagements;
namespace Managing.Application.Abstractions.Repositories;
public interface ISettingsRepository
{
Task<MoneyManagement> GetMoneyManagement(string name);
Task InsertMoneyManagement(MoneyManagement request);
void UpdateMoneyManagement(MoneyManagement moneyManagement);
IEnumerable<MoneyManagement> GetMoneyManagements();
void DeleteMoneyManagement(string name);
void DeleteMoneyManagements();
}

View File

@@ -0,0 +1,20 @@
using Managing.Domain.Statistics;
namespace Managing.Application.Abstractions.Repositories;
public interface IStatisticRepository
{
Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker);
IList<TopVolumeTicker> GetTopVolumeTickers(DateTime date);
Task SaveSpotligthtOverview(SpotlightOverview overview);
IList<SpotlightOverview> GetSpotlightOverviews(DateTime date);
void UpdateSpotlightOverview(SpotlightOverview overview);
List<Trader> GetBestTraders();
void UpdateBestTrader(Trader trader);
Task InsertBestTrader(Trader trader);
Task RemoveBestTrader(Trader trader);
List<Trader> GetBadTraders();
void UpdateBadTrader(Trader trader);
Task InsertBadTrader(Trader trader);
Task RemoveBadTrader(Trader trader);
}

View File

@@ -0,0 +1,29 @@
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Repositories;
public interface ITradingRepository
{
Scenario GetScenarioByName(string scenario);
void InsertSignal(Signal signal);
void InsertPosition(Position position);
void UpdatePosition(Position position);
Strategy GetStrategyByName(string strategy);
void InsertScenario(Scenario scenario);
void InsertStrategy(Strategy strategy);
IEnumerable<Scenario> GetScenarios();
IEnumerable<Strategy> GetStrategies();
void DeleteScenario(string name);
void DeleteStrategy(string name);
void DeleteScenarios();
void DeleteStrategies();
Position GetPositionByIdentifier(string identifier);
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
IEnumerable<Position> GetPositionsByStatus(PositionStatus positionStatus);
Fee GetFee(TradingExchanges exchange);
void InsertFee(Fee fee);
void UpdateFee(Fee fee);
}

View File

@@ -0,0 +1,9 @@
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Repositories;
public interface IUserRepository
{
Task<User> GetUserByNameAsync(string name);
Task InsertUserAsync(User user);
}

View File

@@ -0,0 +1,14 @@
using Managing.Common;
using Managing.Domain.Workers;
namespace Managing.Application.Abstractions.Repositories;
public interface IWorkerRepository
{
Task DisableWorker(Enums.WorkerType workerType);
Task EnableWorker(Enums.WorkerType workerType);
Task<Worker> GetWorkerAsync(Enums.WorkerType workerType);
IEnumerable<Worker> GetWorkers();
Task InsertWorker(Worker worker);
Task UpdateWorker(Enums.WorkerType workerType, int executionCount);
}

View File

@@ -0,0 +1,12 @@
using Managing.Domain.Workflows.Synthetics;
namespace Managing.Application.Abstractions.Repositories;
public interface IWorkflowRepository
{
bool DeleteWorkflow(string name);
Task<SyntheticWorkflow> GetWorkflow(string name);
IEnumerable<SyntheticWorkflow> GetWorkflows();
Task InsertWorkflow(SyntheticWorkflow workflow);
Task UpdateWorkflow(SyntheticWorkflow workflow);
}

View File

@@ -0,0 +1,16 @@
using Managing.Domain.Accounts;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Services;
public interface IAccountService
{
Task<Account> CreateAccount(User user, Account account);
bool DeleteAccount(User user, string name);
IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true);
IEnumerable<Account> GetAccounts(bool hideSecrets, bool getBalance);
Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance);
Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
}

View File

@@ -0,0 +1,20 @@
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions
{
public interface IBacktester
{
Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker, Scenario scenario, Timeframe timeframe, double days, decimal balance, bool isForWatchingOnly = false, bool save = false);
Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Ticker ticker, Scenario scenario, Timeframe timeframe, double days, decimal balance, bool isForWatchingOnly = false, bool save = false);
IEnumerable<Backtest> GetBacktests();
bool DeleteBacktest(string id);
bool DeleteBacktests();
Backtest RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List<Candle> candles, decimal balance);
Backtest RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Timeframe timeframe, List<Candle> candles, decimal balance);
}
}

View File

@@ -0,0 +1,19 @@
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services;
public interface IDiscordService
{
Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe);
Task SendPosition(Position position);
Task SendClosingPosition(Position position);
Task SendMessage(string v);
Task SendTradeMessage(string message, bool isBadBehavior = false);
Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null);
Task SendClosedPosition(string address, Trade oldTrade);
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
Task SendBestTraders(List<Trader> traders);
Task SendBadTraders(List<Trader> traders);
}

View File

@@ -0,0 +1,45 @@
using Managing.Domain.Trades;
using Managing.Domain.Candles;
using static Managing.Common.Enums;
using Managing.Domain.Accounts;
namespace Managing.Application.Abstractions.Services;
public interface IExchangeService
{
Task<Trade> OpenTrade(
Account account,
Ticker ticker,
TradeDirection direction,
decimal price,
decimal quantity,
decimal? leverage = null,
TradeType tradeType = TradeType.Limit,
bool reduceOnly = false,
bool isForPaperTrading = false,
DateTime? currentDate = null,
bool ioc = true);
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
decimal GetPrice(Account account, Ticker ticker, DateTime date);
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
Task<Trade> OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice,
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
Task<List<Ticker>> GetTickers(Account account, Timeframe timeframe);
Task<Trade> OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection, decimal takeProfitPrice,
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
Task<Trade> ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false);
decimal GetVolume(Account account, Ticker ticker);
Task<List<Trade>> GetTrades(Account account, Ticker ticker);
Task<bool> CancelOrder(Account account, Ticker ticker);
decimal GetFee(Account account, bool isForPaperTrading = false);
Candle GetCandle(Account account, Ticker ticker, DateTime date);
Task<decimal> GetQuantityInPosition(Account account, Ticker ticker);
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe);
decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction);
Orderbook GetOrderbook(Account account, Ticker ticker);
Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage, TradeType tradeType, DateTime dateTime, TradeStatus tradeStatus = TradeStatus.PendingOpen);
Task<List<Trade>> GetOpenOrders(Account account, Ticker ticker);
Task<Trade> GetTrade(string reference, string orderId, Ticker ticker);
}

View File

@@ -0,0 +1,10 @@
using Managing.Common;
using Managing.Domain.Candles;
namespace Managing.Application.Abstractions.Services;
public interface IExchangeStream
{
Task StartBinanceWorker(Enums.Ticker ticker, Func<Candle, Task> action);
Task StopBinanceWorker();
}

View File

@@ -0,0 +1,19 @@
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services;
public interface IMessengerService
{
Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe);
Task SendPosition(Position position);
Task SendClosingPosition(Position position);
Task SendMessage(string v);
Task SendTradeMessage(string message, bool isBadBehavior = false);
Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null);
Task SendClosedPosition(string address, Trade oldTrade);
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
Task SendBestTraders(List<Trader> traders);
Task SendBadTraders(List<Trader> filteredTrader);
}

View File

@@ -0,0 +1,7 @@
namespace Managing.Application.Abstractions.Services;
public interface IStreamService
{
Task SubscribeCandle();
Task UnSubscribeCandle();
}

View File

@@ -0,0 +1,8 @@
using Managing.Common;
namespace Managing.Application.Abstractions.Services;
public interface ITickerService
{
Task<List<Enums.Ticker>> GetAvailableTicker();
}

View File

@@ -0,0 +1,11 @@
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
namespace Managing.Application.Abstractions.Services;
public interface ITradaoService
{
Task<List<Trader>> GetBadTrader();
Task<List<Trader>> GetBestTrader();
Task<List<Trade>> GetTrades(string address);
}

View File

@@ -0,0 +1,35 @@
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Scenarios;
using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services;
public interface ITradingService
{
Scenario GetScenarioByName(string scenario);
void InsertSignal(Signal signal);
void InsertPosition(Position position);
void UpdatePosition(Position position);
Strategy GetStrategyByName(string strategy);
void InsertScenario(Scenario scenario);
void InsertStrategy(Strategy strategy);
IEnumerable<Scenario> GetScenarios();
IEnumerable<Strategy> GetStrategies();
void DeleteScenario(string name);
void DeleteStrategy(string name);
void DeleteScenarios();
void DeleteStrategies();
Position GetPositionByIdentifier(string identifier);
IEnumerable<Position> GetPositions(PositionInitiator positionInitiator);
IEnumerable<Position> GetPositions();
IEnumerable<Position> GetPositionsByStatus(Enums.PositionStatus positionStatus);
Task<Position> ManagePosition(Account account, Position position);
void UpdateFee(TradingExchanges evm);
decimal GetFee(Account account, bool isForPaperTrading = false);
Task WatchTrader();
IEnumerable<Trader> GetTradersWatch();
}

View File

@@ -0,0 +1,9 @@
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Services;
public interface IUserService
{
Task<User> Authenticate(string name, string address, string message, string signature);
Task<User> GetUserByAddressAsync(string address);
}

View File

@@ -0,0 +1,51 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Abstractions;
using Moq;
using Managing.Domain.MoneyManagements;
using static Managing.Common.Enums;
using Managing.Domain.Accounts;
namespace Managing.Application.Tests;
public class BaseTests
{
public readonly Mock<IMoneyManagementService> _moneyManagementService;
public readonly Mock<IAccountService> _accountService;
public readonly IExchangeService _exchangeService;
public readonly Mock<ITradingService> _tradingService;
public readonly Account Account;
public readonly MoneyManagement MoneyManagement;
public const string PublicAddress = "0x0425dEAb364E9121F7CA284129dA854FD5cF22eD";
public const string PrivateKey = "";
public BaseTests()
{
_accountService = new Mock<IAccountService>();
_moneyManagementService = new Mock<IMoneyManagementService>();
MoneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.30m,
Leverage = 2,
Timeframe = Timeframe.FifteenMinutes,
StopLoss = 0.008m,
TakeProfit = 0.02m,
Name = "Default MM"
};
_ =_moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny<string>())).Returns(Task.FromResult(MoneyManagement));
Account = new Account()
{
Exchange = TradingExchanges.Evm,
Key = PublicAddress,
Secret = PrivateKey,
};
_accountService.Setup(a => a.GetAccount(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>()))
.Returns(Task.FromResult(Account));
_tradingService = new Mock<ITradingService>();
_exchangeService = TradingBaseTests.GetExchangeService();
}
}

View File

@@ -0,0 +1,408 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Backtesting;
using Managing.Application.Bots.Base;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Moq;
using System.Collections;
using System.Diagnostics;
using Xunit;
using static Managing.Common.Enums;
namespace Managing.Application.Tests
{
public class BotsTests : BaseTests
{
private readonly IBotFactory _botFactory;
private readonly IBacktester _backtester;
private readonly string _reportPath = "D:\\BacktestingReports\\backtesting.csv";
private string _analysePath = "D:\\BacktestingReports\\analyse";
private readonly string _errorsPath = "D:\\BacktestingReports\\errorsAnalyse.csv";
private readonly string _s = "|";
private List<double> _elapsedTimes { get; set; }
public BotsTests() : base ()
{
var backtestRepository = new Mock<IBacktestRepository>().Object;
var discordService = new Mock<IMessengerService>().Object;
var tradingBotLogger = TradingBaseTests.CreateTradingBotLogger();
var backtestLogger = TradingBaseTests.CreateBacktesterLogger();
_botFactory = new BotFactory(
_exchangeService,
tradingBotLogger,
_moneyManagementService.Object,
discordService,
_accountService.Object,
_tradingService.Object);
_backtester = new Backtester(_exchangeService, _botFactory, backtestRepository, backtestLogger);
_elapsedTimes = new List<double>();
}
[Theory]
[InlineData(Ticker.BTC, Timeframe.OneDay, -100)]
public void SwingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var scenario = new Scenario("ScalpingScenario");
var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 14);
scenario.AddStrategy(strategy);
// Act
var backtestResult = _backtester.RunFlippingBotBacktest(Account, MoneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days*2), 1000);
WriteCsvReport(backtestResult.GetStringReport());
// Assert
Assert.True(backtestResult.FinalPnl > 0);
Assert.True(backtestResult.WinRate >= 30);
Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage);
}
[Theory]
//[InlineData(Enums.Exchanges.Binance, "ADAUSDT", Timeframe.ThirtyMinutes, -5)]
//[InlineData(Enums.Exchanges.Binance, "ADAUSDT", Timeframe.FifteenMinutes, -5)]
//[InlineData(Enums.Exchanges.Binance, "SOLUSDT", Timeframe.ThirtyMinutes, -4)]
//[InlineData(Enums.Exchanges.Binance, "SOLUSDT", Timeframe.FifteenMinutes, -4)]
//[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.ThirtyMinutes, -4)]
//[InlineData(Enums.Exchanges.Binance, "BTCUSDT", Timeframe.FifteenMinutes, -4)]
[InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -14)]
public void ScalpingBot_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var scenario = new Scenario("ScalpingScenario");
var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 5);
scenario.AddStrategy(strategy);
// Act
var backtestResult = _backtester.RunScalpingBotBacktest(Account, MoneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days), 1000);
//WriteCsvReport(backtestResult.GetStringReport());
// Assert
Assert.True(backtestResult.FinalPnl > 0);
Assert.True(backtestResult.WinRate >= 30);
Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage);
}
[Theory]
[InlineData(Ticker.BTC, Timeframe.FifteenMinutes, -8)]
public void MacdCross_Should_Return_Positiv_Profit_For_Every_Position(Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var scenario = new Scenario("ScalpingScenario");
var strategy = ScenarioHelpers.BuildStrategy(StrategyType.MacdCross, timeframe, "RsiDiv", fastPeriods: 12, slowPeriods: 26, signalPeriods: 9);
scenario.AddStrategy(strategy);
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = 0.01m,
TakeProfit = 0.02m
};
// Act
var backtestResult = _backtester.RunScalpingBotBacktest(Account, moneyManagement, ticker, scenario, timeframe, Convert.ToDouble(days), 1000);
WriteCsvReport(backtestResult.GetStringReport());
// Assert
Assert.True(backtestResult.FinalPnl > 0);
Assert.True(backtestResult.WinRate >= 30);
Assert.True(backtestResult.GrowthPercentage > backtestResult.HodlPercentage);
}
[Theory]
[InlineData(Timeframe.FifteenMinutes, -6, StrategyType.RsiDivergenceConfirm, BotType.ScalpingBot)]
//[InlineData(Timeframe.FifteenMinutes, -6, Enums.StrategyType.RsiDivergenceConfirm, Enums.BotType.FlippingBot)]
public void GetBestPeriodRsiForDivergenceFlippingBot(Timeframe timeframe, int days, StrategyType strategyType, BotType botType)
{
var result = new List<Tuple<string, int, decimal, decimal, decimal, decimal>>();
var errors = new List<string>();
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 4
};
var periodRange = new List<int>() { 2, 7};
var stopLossRange = new List<decimal>() { 0.005m, 0.05m, 0.005m };
var takeProfitRange = new List<decimal>() { 0.01m, 0.1m, 0.02m };
var fileIdentifier = $"{strategyType}-{timeframe}";
var completedTest = 0;
var totalTests = GetTotalTrades(periodRange, stopLossRange, takeProfitRange) * Enum.GetNames(typeof(Ticker)).Length;
CleanAnalyseFile(fileIdentifier);
UpdateProgression(totalTests, completedTest);
Parallel.ForEach((Ticker[])Enum.GetValues(typeof(Ticker)), options, ticker => {
var candles = _exchangeService.GetCandles(Account, ticker, DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result;
if (candles == null || candles.Count == 0)
return;
Parallel.For(periodRange[0], periodRange[1], options, i => {
var scenario = new Scenario("ScalpingScenario");
var strategy = ScenarioHelpers.BuildStrategy(strategyType, timeframe, "RsiDiv", period: i);
scenario.AddStrategy(strategy);
// -0.5 to -5
for (decimal s = stopLossRange[0]; s < stopLossRange[1]; s += stopLossRange[2])
{
// +1% to +10% in 1%
for(decimal t = takeProfitRange[0]; t < takeProfitRange[1]; t += takeProfitRange[2])
{
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = s,
TakeProfit = t
};
try
{
var timer = new Stopwatch();
timer.Start();
var backtestResult = botType switch
{
BotType.SimpleBot => throw new NotImplementedException(),
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000),
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000),
_ => throw new NotImplementedException(),
};
timer.Stop();
if (backtestResult.FinalPnl > 0
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30
&& backtestResult.Statistics.MaxDrawdown < 3)
{
var currentResult = new Tuple<string, int, decimal, decimal, decimal, decimal>(ticker.ToString(), i,
backtestResult.FinalPnl, s, t, backtestResult.GrowthPercentage - backtestResult.HodlPercentage);
result.Add(currentResult);
}
completedTest++;
UpdateProgression(totalTests, completedTest, timer.Elapsed.TotalSeconds);
}
catch (Exception ex)
{
completedTest++;
errors.Add($"{ticker}{_s}{i}{_s}{s}{_s}{t}{_s}{ex.Message}");
}
}
}
});
});
foreach (var r in result)
{
WriteCsvAnalyse($"{r.Item1}{_s}{r.Item2}{_s}{r.Item3.ToString("0.000")}{_s}{r.Item4 * 100}{_s}{r.Item5 * 100}{_s}{r.Item6}");
}
foreach (var e in errors)
{
WriteCsvErrors(e);
}
var bestResult = result.OrderByDescending(b => b.Item3).FirstOrDefault();
WriteCsvAnalyse($"Best result : {bestResult.Item1} - Rsi Period : {bestResult.Item2} - {bestResult.Item3} - SL : {bestResult.Item4}% - TP : {bestResult.Item5}%");
Assert.True(result.Any());
}
[Theory]
[InlineData(Timeframe.OneHour, -30, StrategyType.MacdCross, BotType.FlippingBot)]
[InlineData(Timeframe.OneHour, -30, StrategyType.MacdCross, BotType.ScalpingBot)]
public void GetBestMMForMacdFlippingBot(Timeframe timeframe, int days, StrategyType strategyType, BotType botType)
{
var result = new List<Tuple<string, decimal, decimal, decimal, decimal>>();
var errors = new List<string>();
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 4
};
var stopLossRange = new List<decimal>() { 0.005m, 0.05m, 0.005m };
var takeProfitRange = new List<decimal>() { 0.01m, 0.1m, 0.02m };
var fileIdentifier = $"{strategyType}-{timeframe}-{botType}";
var completedTest = 0;
var totalTests = GetTotalTradeForStopLossTakeProfit(stopLossRange, takeProfitRange) * Enum.GetNames(typeof(Ticker)).Length;
CleanAnalyseFile(fileIdentifier);
UpdateProgression(totalTests, completedTest);
Parallel.ForEach((Ticker[])Enum.GetValues(typeof(Ticker)), options, ticker => {
var candles = _exchangeService.GetCandles(Account, ticker, DateTime.Now.AddDays(Convert.ToDouble(days)), timeframe).Result;
if (candles == null || candles.Count == 0)
return;
var scenario = new Scenario("ScalpingScenario");
var strategy = ScenarioHelpers.BuildStrategy(strategyType, timeframe, "RsiDiv", fastPeriods: 12, slowPeriods: 26, signalPeriods: 9);
scenario.AddStrategy(strategy);
// -0.5 to -5
for (decimal s = stopLossRange[0]; s < stopLossRange[1]; s += stopLossRange[2])
{
// +1% to +10% in 1%
for (decimal t = takeProfitRange[0]; t < takeProfitRange[1]; t += takeProfitRange[2])
{
var timer = new Stopwatch();
timer.Start();
try
{
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = s,
TakeProfit = t
};
var backtestResult = botType switch
{
BotType.SimpleBot => throw new NotImplementedException(),
BotType.ScalpingBot => _backtester.RunScalpingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000),
BotType.FlippingBot => _backtester.RunFlippingBotBacktest(Account, moneyManagement, scenario, timeframe, candles, 1000),
_ => throw new NotImplementedException(),
};
if (backtestResult.FinalPnl > 0
&& (backtestResult.GrowthPercentage - backtestResult.HodlPercentage) > 30
&& backtestResult.Statistics.MaxDrawdown < 3)
{
var currentResult = new Tuple<string, decimal, decimal, decimal, decimal>(ticker.ToString(),
backtestResult.FinalPnl, s, t, backtestResult.GrowthPercentage - backtestResult.HodlPercentage);
result.Add(currentResult);
}
completedTest++;
UpdateProgression(totalTests, completedTest, timer.Elapsed.TotalSeconds);
}
catch (Exception ex)
{
completedTest++;
errors.Add($"{ticker}{_s}{s}{_s}{t}{_s}{ex.Message}");
}
timer.Stop();
}
}
});
foreach (var r in result)
{
WriteCsvAnalyse($"{r.Item1}{_s}{r.Item2.ToString("0.000")}{_s}{r.Item3 * 100}{_s}{r.Item4 * 100}{_s}{r.Item5}");
}
foreach (var e in errors)
{
WriteCsvErrors(e);
}
var bestResult = result.OrderByDescending(b => b.Item3).FirstOrDefault();
WriteCsvAnalyse($"Best result : {bestResult.Item1} - Rsi Period : {bestResult.Item2} - {bestResult.Item3} - SL : {bestResult.Item4}% - TP : {bestResult.Item5}%");
Assert.True(result.Any());
}
private void WriteCsvReport(string line)
{
File.AppendAllLines(_reportPath, new[] { line });
}
private void WriteCsvAnalyse(string line, string fileIdentifier = null)
{
if (!string.IsNullOrEmpty(fileIdentifier))
_analysePath += $"-{fileIdentifier}-{DateTime.Now.ToString("dd-MM-HH-mm")}.csv";
File.AppendAllLines(_analysePath , new[] { line });
}
private void WriteCsvErrors(string line)
{
File.AppendAllLines(_errorsPath, new[] { line });
}
private void CleanAnalyseFile(string fileIdentifier)
{
WriteCsvAnalyse("", fileIdentifier);
}
private decimal GetTotalTrades(List<int> periodRange, List<decimal> stopLossRange, List<decimal> takeProfitRange)
{
var stopLossRangeTotalTest = stopLossRange[1] / stopLossRange[2];
var takeProfitRangeTotalTest = takeProfitRange[1] / takeProfitRange[2];
var totalTrades = GetTotalTradeForStopLossTakeProfit(stopLossRange, takeProfitRange) * stopLossRangeTotalTest * takeProfitRangeTotalTest;
return totalTrades;
}
private decimal GetTotalTradeForStopLossTakeProfit(List<decimal> stopLossRange, List<decimal> takeProfitRange)
{
var stopLossRangeTotalTest = stopLossRange[1] / stopLossRange[2];
var takeProfitRangeTotalTest = takeProfitRange[1] / takeProfitRange[2];
var totalTrades = stopLossRangeTotalTest * takeProfitRangeTotalTest;
return totalTrades;
}
private void UpdateProgression(decimal totalTest, int completedTest, double? elapsed = null)
{
var timeRemaining = "";
if (elapsed.HasValue && completedTest > 0)
{
//_elapsedTimes.Add((elapsed.Value / completedTest) * ((double)totalTest - completedTest));
_elapsedTimes.Add(elapsed.Value);
//var t = (_elapsedTimes.Average() / completedTest) * ((double)totalTest - completedTest);
var t = (_elapsedTimes.Average() * (double)(totalTest - completedTest));
timeRemaining = $" Remaining time: {t} seconds - Estimated end date: {DateTime.Now.AddSeconds(t)}";
}
ModifyFirstRow(_analysePath, $"{completedTest}/{totalTest}{timeRemaining}");
}
private void ModifyFirstRow(string filepath, string newValue)
{
ArrayList rows = new ArrayList();
using (StreamReader reader = new StreamReader(filepath))
{
string row = null;
while ((row = reader.ReadLine()) != null)
{
rows.Add(row);
}
}
// Determ if the file even contains rows.
if (rows.Count > 0)
{
// Replace the first row.
rows[0] = newValue;
}
else
{
// Add the new value because there
// where no rows found in the file.
rows.Add(newValue);
}
// Write the modified content to the file.
using (StreamWriter writer = new StreamWriter(filepath, false))
{
foreach (String row in rows)
{
writer.WriteLine(row);
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
using Xunit;
namespace Managing.Application.Tests
{
public class CandleHelpersTests
{
[Fact]
public void Shoud_Result_Correct_Range_Date()
{
// Arrange
var expectedDate = DateTime.Now.AddMinutes(-15*5);
var currentCandleDate = DateTime.Now;
var previousCandleDate = DateTime.Now.AddMinutes(-15);
// Act
//var result = CandleHelpers.GetRangeDateFromTimeframe
// Assert
}
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<PlatformTarget>AnyCPU</PlatformTarget>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.10" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="Microsoft.TestPlatform.AdapterUtilities" Version="17.7.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Api\Managing.Api.csproj" />
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj" />
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj" />
<ProjectReference Include="..\Managing.Core\Managing.Core.csproj" />
<ProjectReference Include="..\Managing.Domain\Managing.Domain.csproj" />
<ProjectReference Include="..\Managing.Infrastructure.Exchanges\Managing.Infrastructure.Exchanges.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using Managing.Core;
using Xunit;
namespace Managing.Application.Tests
{
public class MathHelpersTests
{
[Theory]
[InlineData(0.00010, 4)]
public void Should_Return_Correct_Precision(decimal n, int expectedValue)
{
var precision = MathHelpers.GetDecimalPlaces(n);
Assert.Equal(expectedValue, precision);
}
[Theory]
[InlineData("0.00010", 4)]
public void Should_Return_Correct_PrecisionTest(string s, int expectedValue)
{
var precision = MathHelpers.GetDecimalPlaces(s);
Assert.Equal(expectedValue, precision);
}
}
}

View File

@@ -0,0 +1,56 @@
using Managing.Application.Trading;
using Managing.Application.Trading.Commands;
using Managing.Domain.Trades;
using Moq;
using Xunit;
using static Managing.Common.Enums;
namespace Managing.Application.Tests;
public class PositionTests : BaseTests
{
public PositionTests() : base()
{
}
[Fact]
public async void Should_Open_Position()
{
var command = new OpenPositionRequest(
"test",
MoneyManagement,
TradeDirection.Short,
Ticker.BTC,
PositionInitiator.User,
DateTime.UtcNow,
isForPaperTrading: false);
var handler = new OpenPositionCommandHandler(
_exchangeService,
_accountService.Object,
_tradingService.Object);
var position = await handler.Handle(command);
Assert.NotNull(position);
}
[Fact]
public async void Shoud_Close_Position()
{
var openTrade = await _exchangeService.GetTrade(Account, "", Ticker.BTC);
var position = new Position("", TradeDirection.Long, Ticker.BTC, MoneyManagement, PositionInitiator.User, DateTime.UtcNow)
{
Open = openTrade
};
var command = new ClosePositionCommand(position);
_ = _tradingService.Setup(m => m.GetPositionByIdentifier(It.IsAny<string>())).Returns(position);
var handler = new ClosePositionCommandHandler(
_exchangeService,
_accountService.Object,
_tradingService.Object);
var closedPosition = await handler.Handle(command);
Assert.NotNull(closedPosition);
}
}

View File

@@ -0,0 +1,251 @@
using Managing.Domain.Trades;
using Xunit;
using static Managing.Common.Enums;
namespace Managing.Application.Tests
{
public class ProfitAndLossTests
{
[Theory]
[InlineData(1, 100, 110, 10)]
[InlineData(2, 100, 110, 20)]
public void Should_Return_Correct_ProfitAndLoss_Amount(decimal quantity, decimal price, decimal exitPrice, decimal expectedResult)
{
// Arrange
var init = new List<Tuple<decimal, decimal>>();
init.Add(new Tuple<decimal, decimal>(quantity, price));
var pnl = new ProfitAndLoss(init, TradeDirection.Long);
// Act
var result = pnl.FloatingForTheoriticalExit(exitPrice);
// Assert
Assert.Equal(expectedResult, result);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit()
{
// Arrange
var position = GetFakeShortPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
// Trigger Stop Loss
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection);
// Assert
Assert.Equal(20, position.ProfitAndLoss.Realized);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Long_Solana_failed_Position_After_Took_One_Profit()
{
// Arrange
var position = GetSolanaLongPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
// Trigger Stop Loss
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection);
// Assert
Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Long_Solana_failed_Position_After_Took_One_Profit2()
{
// Arrange
var position = GetSolanaLongPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price),
new Tuple<decimal, decimal>(-position.TakeProfit2.Quantity, position.StopLoss.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
// Assert
Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit2()
{
// Arrange
var position = GetFakeShortPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price),
new Tuple<decimal, decimal>(-position.TakeProfit2.Quantity, position.StopLoss.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
// Assert
Assert.Equal(20, position.ProfitAndLoss.Realized);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit3()
{
// Arrange
var position = GetFakeShortPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
position.ProfitAndLoss.AddFill(-position.TakeProfit1.Quantity, position.TakeProfit1.Price, TradeDirection.Short);
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Short);
// Assert
Assert.Equal(20, position.ProfitAndLoss.Realized);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Short_Succeeded_Position_After_Two_Take_Profit()
{
// Arrange
var position = GetFakeShortPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
// Trigger Stop Loss
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Short);
// Assert
Assert.Equal(120, position.ProfitAndLoss.Realized);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Long_failed_Position_After_Took_One_Profit()
{
// Arrange
var position = GetFakeLongPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
// Trigger Stop Loss
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Long);
// Assert
Assert.Equal(20, position.ProfitAndLoss.Realized);
}
[Fact]
public void Should_Return_Correct_Pnl_For_Long_Succeeded_Position_After_Two_Take_Profit()
{
// Arrange
var position = GetFakeLongPosition();
// Setup Open and first take profit
var orders = new List<Tuple<decimal, decimal>>
{
new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
};
// Act
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
// Trigger Stop Loss
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Long);
// Assert
Assert.Equal(120, position.ProfitAndLoss.Realized);
}
private static Position GetFakeShortPosition()
{
return new Position("FakeAccount", TradeDirection.Short, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
{
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
TradeType.StopMarket, Ticker.ADA, 10, 110, 1, "StopLossOrderId", ""),
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
TradeType.TakeProfitLimit, Ticker.ADA, 6, 90, 1, "TakeProfit1OrderId", ""),
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
TradeType.TakeProfitLimit, Ticker.ADA, 4, 85, 1, "TakeProfit1OrderId", "")
};
}
private static Position GetSolanaLongPosition()
{
return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
{
Open = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.Filled,
TradeType.Market, Ticker.ADA, (decimal)6.0800904000245037980887037491, (decimal)81.6200, 1, "OpenOrderId", ""),
StopLoss = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
TradeType.StopMarket, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)79.987600, 1, "StopLossOrderId", ""),
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)2.4320361600098015192354814996, (decimal)85.701000, 1, "TakeProfit1OrderId", ""),
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)89.782000, 1, "TakeProfit1OrderId", "")
};
}
private static Position GetFakeLongPosition()
{
return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
{
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
TradeType.StopMarket, Ticker.ADA, 10, 90, 1, "StopLossOrderId", ""),
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
TradeType.TakeProfitLimit, Ticker.ADA, 6, 110, 1, "TakeProfit1OrderId", ""),
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
TradeType.TakeProfitLimit, Ticker.ADA, 4, 115, 1, "TakeProfit1OrderId", "")
};
}
}
}

View File

@@ -0,0 +1,62 @@
using Managing.Domain.MoneyManagements;
using Managing.Domain.Shared.Helpers;
using Xunit;
using static Managing.Common.Enums;
namespace Managing.Application.Tests
{
public class RiskHelpersTests
{
private readonly MoneyManagement _moneyManagement;
public RiskHelpersTests()
{
_moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = Timeframe.OneDay,
StopLoss = 0.008m,
TakeProfit = 0.02m
};
}
[Theory]
[InlineData(1000, 992)]
public void Should_Return_Correct_Stop_Loss_Price_For_Long(decimal price, decimal expectedResult)
{
var stopLossPrice = RiskHelpers.GetStopLossPrice(TradeDirection.Long, price, _moneyManagement);
Assert.Equal(expectedResult, stopLossPrice);
}
[Theory]
[InlineData(1000, 1008)]
public void Should_Return_Correct_Stop_Loss_Price_For_Short(decimal price, decimal expectedResult)
{
var stopLossPrice = RiskHelpers.GetStopLossPrice(TradeDirection.Short, price, _moneyManagement);
Assert.Equal(expectedResult, stopLossPrice);
}
[Theory]
[InlineData(1000, 980)]
public void Should_Return_Correct_Take_Profit_Price_For_Short(decimal price, decimal expectedResult)
{
var stopLossPrice = RiskHelpers.GetTakeProfitPrice(TradeDirection.Short, price, _moneyManagement);
Assert.Equal(expectedResult, stopLossPrice);
}
[Theory]
[InlineData(1000, 1020)]
public void Should_Return_Correct_Take_Profit_Price_For_Long(decimal price, decimal expectedResult)
{
var stopLossPrice = RiskHelpers.GetTakeProfitPrice(TradeDirection.Long, price, _moneyManagement);
Assert.Equal(expectedResult, stopLossPrice);
}
[Fact]
public void Test()
{
const decimal test = (decimal)34523.4352;
Assert.Equal((decimal)34523.4, Math.Round(test, 1));
}
}
}

View File

@@ -0,0 +1,212 @@
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Managing.Domain.Strategies;
using Xunit;
using static Managing.Common.Enums;
namespace Managing.Application.Tests
{
public class StrategyTests
{
private readonly IExchangeService _exchangeService;
public StrategyTests()
{
_exchangeService = TradingBaseTests.GetExchangeService();
}
[Theory]
[InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)]
[InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)]
[InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)]
public void Shoud_Return_Signal_On_Rsi_BullishDivergence2(TradingExchanges exchange, Ticker ticker,
Timeframe timeframe)
{
var account = GetAccount(exchange);
// Arrange
var rsiStrategy = new RSIDivergenceStrategy("unittest", timeframe, 5);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
var resultSignal = new List<Signal>();
// Act
foreach (var candle in candles)
{
rsiStrategy.Candles.Add(candle);
var signals = rsiStrategy.Run();
}
if (rsiStrategy.Signals != null && rsiStrategy.Signals.Count > 0)
resultSignal.AddRange(rsiStrategy.Signals);
// Assert
Assert.IsType<List<Signal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
}
private static Account GetAccount(TradingExchanges exchange)
{
return new Account()
{
Exchange = exchange
};
}
[Theory]
[InlineData(TradingExchanges.Binance, Ticker.ADA, Timeframe.OneDay)]
public void Shoud_Return_Signal_On_Rsi_BearishDivergence(TradingExchanges exchange, Ticker ticker,
Timeframe timeframe)
{
// Arrange
var account = GetAccount(exchange);
var rsiStrategy = new RSIDivergenceStrategy("unittest", timeframe, 5);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(-50), timeframe).Result;
var resultSignal = new List<Signal>();
// Act
foreach (var candle in candles)
{
rsiStrategy.Candles.Add(candle);
var signals = rsiStrategy.Run();
}
if (rsiStrategy.Signals != null && rsiStrategy.Signals.Count > 0)
resultSignal.AddRange(rsiStrategy.Signals);
// Assert
Assert.IsType<List<Signal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
}
[Theory]
[InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)]
public void Shoud_Return_Signal_On_Macd_Cross(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var account = GetAccount(exchange);
var rsiStrategy = new MACDCrossStrategy("unittest", timeframe, 12, 26, 9);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>();
// Act
foreach (var candle in candles)
{
rsiStrategy.Candles.Add(candle);
var signals = rsiStrategy.Run();
}
if (rsiStrategy.Signals != null && rsiStrategy.Signals.Count > 0)
resultSignal.AddRange(rsiStrategy.Signals);
// Assert
Assert.IsType<List<Signal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
}
[Theory]
[InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)]
public void Shoud_Return_Signal_On_SuperTrend(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var account = GetAccount(exchange);
var superTrendStrategy = new SuperTrendStrategy("unittest", timeframe, 10, 3);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>();
// Act
foreach (var candle in candles)
{
superTrendStrategy.Candles.Add(candle);
var signals = superTrendStrategy.Run();
}
if (superTrendStrategy.Signals != null && superTrendStrategy.Signals.Count > 0)
resultSignal.AddRange(superTrendStrategy.Signals);
// Assert
Assert.IsType<List<Signal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
}
[Theory]
[InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)]
public void Shoud_Return_Signal_On_ChandelierExist(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var account = GetAccount(exchange);
var chandelierExitStrategy = new ChandelierExitStrategy("unittest", timeframe, 22, 3);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>();
// Act
foreach (var candle in candles)
{
chandelierExitStrategy.Candles.Add(candle);
var signals = chandelierExitStrategy.Run();
}
if (chandelierExitStrategy.Signals != null && chandelierExitStrategy.Signals.Count > 0)
resultSignal.AddRange(chandelierExitStrategy.Signals);
// Assert
Assert.IsType<List<Signal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
}
[Theory]
[InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)]
public void Shoud_Return_Signal_On_EmaTrend(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var account = GetAccount(exchange);
var emaTrendSrategy = new EmaTrendStrategy("unittest", timeframe, 200);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>();
// Act
foreach (var candle in candles)
{
emaTrendSrategy.Candles.Add(candle);
var signals = emaTrendSrategy.Run();
}
if (emaTrendSrategy.Signals != null && emaTrendSrategy.Signals.Count > 0)
resultSignal.AddRange(emaTrendSrategy.Signals);
// Assert
Assert.IsType<List<Signal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
}
[Theory]
[InlineData(TradingExchanges.Ftx, Ticker.ADA, Timeframe.OneDay, -500)]
public void Shoud_Return_Signal_On_StochRsi(TradingExchanges exchange, Ticker ticker, Timeframe timeframe, int days)
{
// Arrange
var account = GetAccount(exchange);
var stochRsiStrategy = new StochRsiTrendStrategy("unittest", timeframe, 14, 14, 3, 1);
var candles = _exchangeService.GetCandles(account, ticker, DateTime.Now.AddDays(days), timeframe).Result;
var resultSignal = new List<Signal>();
// Act
foreach (var candle in candles)
{
stochRsiStrategy.Candles.Add(candle);
var signals = stochRsiStrategy.Run();
}
if (stochRsiStrategy.Signals != null && stochRsiStrategy.Signals.Count > 0)
resultSignal.AddRange(stochRsiStrategy.Signals);
// Assert
Assert.IsType<List<Signal>>(resultSignal);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Short);
Assert.Contains(resultSignal, s => s.Direction == TradeDirection.Long);
}
}
}

View File

@@ -0,0 +1,85 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Backtesting;
using Managing.Application.Bots;
using Managing.Infrastructure.Databases;
using Managing.Infrastructure.Databases.InfluxDb;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Evm;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Subgraphs;
using Managing.Infrastructure.Exchanges;
using Managing.Infrastructure.Exchanges.Abstractions;
using Managing.Infrastructure.Exchanges.Exchanges;
using Microsoft.Extensions.Logging;
using Moq;
using static Managing.Common.Enums;
namespace Managing.Application.Tests
{
public static class TradingBaseTests
{
public static IExchangeService GetExchangeService()
{
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
var ChainlinkGmx = new ChainlinkGmx(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkGmx));
var Chainlink = new Chainlink(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkPrice));
var GbcFeed = new Gbc(SubgraphService.GetSubgraphClient(SubgraphProvider.Gbc));
var Subgraphs = new List<ISubgraphPrices>
{
ChainlinkGmx,
Chainlink,
GbcFeed
};
var evmManager = new EvmManager(Subgraphs);
var evmProcessor = new EvmProcessor(new Mock<ILogger<EvmProcessor>>().Object, evmManager);
var exchangeProcessors = new List<IExchangeProcessor>()
{
//new Mock<FtxProcessor>().Object,
//new Mock<BinanceProcessor>().Object,
evmProcessor
};
return new ExchangeService(loggerFactory.CreateLogger<ExchangeService>(), GetCandleRepository(), exchangeProcessors);
}
public static ILogger<TradingBot> CreateTradingBotLogger()
{
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
return loggerFactory.CreateLogger<TradingBot>();
}
public static ILogger<Backtester> CreateBacktesterLogger()
{
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
return loggerFactory.CreateLogger<Backtester>();
}
public static ILogger<CandleRepository> CreateCandleRepositoryLogger()
{
ILoggerFactory loggerFactory = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
return loggerFactory.CreateLogger<CandleRepository>();
}
public static ICandleRepository GetCandleRepository()
{
var settings = new InfluxDbSettings()
{
Url = "http://localhost:8086/",
Token = "6b-OjFNaZRprYroZEx8zeLScvPqvOp9la1lEksXl8xRT0d96UyuN18iKpB6jKYFt8JJEX1NaxVMXhk-Sgy8sgg==",
Organization = "managing-org"
};
var influxdb = new InfluxDbRepository(settings);
var candleRepository = new CandleRepository(influxdb, CreateCandleRepositoryLogger());
return candleRepository;
}
}
}

View File

@@ -0,0 +1,54 @@
using Managing.Application.Workflows.Flows.Feeds;
using Managing.Domain.Workflows;
using Xunit;
using static Managing.Common.Enums;
namespace Managing.Application.Tests;
public class WorkflowTests : BaseTests
{
[Fact]
public async void Should_Create_Workflow_with_Feed_Ticker_Flow()
{
// Arrange
var workflow = new Workflow
{
Name = "Bot trading",
Usage = WorkflowUsage.Trading,
Description = "Basic trading Workflow",
Flows = new List<IFlow>()
};
// var rsiDivFlow = new RsiDiv()
// {
// Parameters = "{\"Period\": 14,\"Timeframe\":1}",
// Children = new List<IFlow>(),
// };
// var tickerFlow = new FeedTicker(_exchangeService)
// {
// Parameters = "{\"Exchange\": 3,\"Ticker\":9,\"Timeframe\":1}",
// Children = new List<IFlow>()
// {
// rsiDivFlow
// }
// };
// workflow.Flows.Add(tickerFlow);
// Act
await workflow.Execute();
// Assert
foreach (var f in workflow.Flows)
{
Assert.False(string.IsNullOrEmpty(f.Output));
}
Assert.NotNull(workflow);
Assert.NotNull(workflow.Flows);
Assert.Single(workflow.Flows);
Assert.Equal("Feed Ticker", workflow.Name);
Assert.Equal(WorkflowUsage.Trading, workflow.Usage);
Assert.Equal("Basic trading Workflow", workflow.Description);
}
}

View File

@@ -0,0 +1,8 @@
using static Managing.Common.Enums;
namespace Managing.Application.Workers.Abstractions;
public interface IPricesService
{
Task UpdatePrice(TradingExchanges exchange, Ticker ticker, Timeframe timeframe);
}

View File

@@ -0,0 +1,19 @@
using Managing.Common;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
namespace Managing.Application.Workers.Abstractions;
public interface IStatisticService
{
List<Trader> GetBadTraders();
List<Trader> GetBestTraders();
SpotlightOverview GetLastSpotlight(DateTime dateTime);
IList<TopVolumeTicker> GetLastTopVolumeTicker();
Task<List<Trade>> GetLeadboardPositons();
IList<Enums.Ticker> GetTickers();
Task UpdateLeaderboard();
Task UpdateNoobiesboard();
Task UpdateSpotlight();
Task UpdateTopVolumeTicker(Enums.TradingExchanges exchange, int top);
}

View File

@@ -0,0 +1,15 @@
using Managing.Domain.Workers;
using static Managing.Common.Enums;
namespace Managing.Application.Workers.Abstractions;
public interface IWorkerService
{
Task DisableWorker(WorkerType workerType);
Task EnableWorker(WorkerType workerType);
Task<Worker> GetWorker(WorkerType workerType);
IEnumerable<Worker> GetWorkers();
Task InsertWorker(WorkerType workerType, TimeSpan delay);
Task<bool> ToggleWorker(WorkerType workerType);
Task UpdateWorker(WorkerType workerType, int executionCount);
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,64 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Candles;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Workers;
public class PricesService : IPricesService
{
private readonly ILogger<PricesService> _logger;
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ICandleRepository _candleRepository;
public PricesService(
ILogger<PricesService> logger,
IExchangeService exchangeService,
ICandleRepository candleRepository,
IAccountService accountService)
{
_logger = logger;
_exchangeService = exchangeService;
_candleRepository = candleRepository;
_accountService = accountService;
}
public async Task UpdatePrice(TradingExchanges exchange, Ticker ticker, Timeframe timeframe)
{
try
{
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange);
if (account == null)
throw new Exception($"Enable to found account for exchange {exchange}");
var lastCandles = await _candleRepository.GetCandles(exchange, ticker, timeframe, DateTime.UtcNow.AddDays(-2));
var lastCandle = lastCandles.LastOrDefault();
var startDate = lastCandle != null ? lastCandle.Date : CandleExtensions.GetPreloadSinceFromTimeframe(timeframe);
var newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe);
var candles = !lastCandles.Any() ? newCandles : newCandles.Where(c => c.Date > lastCandle?.Date);
var candlesInserted = 0;
foreach (var newCandle in candles)
{
if (lastCandle == null || newCandle.Date > lastCandle.Date)
{
_candleRepository.InsertCandle(newCandle);
candlesInserted++;
}
}
if (candlesInserted > 0)
_logger.LogInformation($"[{exchange}][{ticker}][{timeframe}] New candles inserted : {candlesInserted}");
}
catch (Exception ex)
{
_logger.LogError($"[{exchange}][{ticker}][{timeframe}] Error : {ex.Message} | {ex.StackTrace}");
}
}
}

View File

@@ -0,0 +1,304 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Workers;
public class StatisticService : IStatisticService
{
private readonly IStatisticRepository _statisticRepository;
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly IEvmManager _evmManager;
private readonly ITradingService _tradingService;
private readonly IBacktester _backtester;
private readonly ITradaoService _tradaoService;
private readonly IMessengerService _messengerService;
private readonly ILogger<StatisticService> _logger;
public StatisticService(
IExchangeService exchangeService,
IAccountService accountService,
ILogger<StatisticService> logger,
IStatisticRepository statisticRepository,
IEvmManager evmManager,
ITradingService tradingService,
IBacktester backtester,
ITradaoService tradaoService,
IMessengerService messengerService)
{
_exchangeService = exchangeService;
_accountService = accountService;
_logger = logger;
_statisticRepository = statisticRepository;
_evmManager = evmManager;
_tradingService = tradingService;
_backtester = backtester;
_tradaoService = tradaoService;
_messengerService = messengerService;
}
public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top)
{
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange);
var date = DateTime.UtcNow;
if (account == null)
throw new Exception($"Enable to found account for exchange {exchange}");
var lastTop = GetLastTopVolumeTicker();
if (lastTop != null && lastTop.Count > 0)
{
_logger.LogInformation($"A top of {lastTop.Count} already exist for the current rage");
return;
}
var volumeTickers = new Dictionary<Ticker, decimal>();
foreach (var ticker in (Ticker[])Enum.GetValues(typeof(Ticker)))
{
var volume = _exchangeService.GetVolume(account, ticker);
var price = _exchangeService.GetPrice(account, ticker, date);
volumeTickers.Add(ticker, volume * price);
}
var currentTop = volumeTickers.OrderByDescending(v => v.Value).Take(top).ToList();
for (int rank = 0; rank < currentTop.Count; rank++)
{
var dto = new TopVolumeTicker()
{
Date = date,
Rank = rank + 1,
Ticker = currentTop[rank].Key,
Volume = currentTop[rank].Value,
Exchange = exchange
};
await _statisticRepository.InsertTopVolumeTicker(dto);
}
}
public IList<TopVolumeTicker> GetLastTopVolumeTicker()
{
var from = DateTime.UtcNow.AddDays(-1);
return _statisticRepository.GetTopVolumeTickers(from);
}
public IList<Ticker> GetTickers()
{
return _evmManager.GetAvailableTicker().Result;
}
public async Task UpdateSpotlight()
{
var scenarios = _tradingService.GetScenarios();
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == TradingExchanges.Evm);
if (account == null)
throw new Exception($"Enable to found default account");
var overview = GetLastSpotlight(DateTime.Now.AddMinutes(-20));
if (overview != null)
{
if(overview.Spotlights.Count < overview.ScenarioCount)
{
_logger.LogInformation($"Spotlights not up to date. {overview.Spotlights.Count}/{overview.ScenarioCount}");
}
else
{
_logger.LogInformation("No need to update spotlights");
return;
}
}
else
{
overview = new SpotlightOverview
{
Spotlights = new List<Spotlight>(),
DateTime = DateTime.Now,
Identifier = Guid.NewGuid(),
ScenarioCount = scenarios.Count(),
};
await _statisticRepository.SaveSpotligthtOverview(overview);
}
var tickers = GetTickers();
foreach (var scenario in scenarios)
{
if (overview.Spotlights.Any(s => s.Scenario.Name == scenario.Name))
continue;
var spotlight = new Spotlight
{
TickerSignals = new List<TickerSignal>(),
Scenario = scenario
};
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 2
};
_ = Parallel.ForEach(tickers, options, ticker =>
{
spotlight.TickerSignals.Add(new TickerSignal
{
Ticker = ticker,
FiveMinutes = GetSignals(account, scenario, ticker, Timeframe.FiveMinutes),
FifteenMinutes = GetSignals(account, scenario, ticker, Timeframe.FifteenMinutes),
OneHour = GetSignals(account, scenario, ticker, Timeframe.OneHour),
FourHour = GetSignals(account, scenario, ticker, Timeframe.FourHour),
OneDay = GetSignals(account, scenario, ticker, Timeframe.OneDay)
});
});
overview.Spotlights.Add(spotlight);
_statisticRepository.UpdateSpotlightOverview(overview);
}
overview.DateTime = DateTime.Now;
_statisticRepository.UpdateSpotlightOverview(overview);
}
private List<Signal> GetSignals(Account account, Scenario scenario, Ticker ticker, Timeframe timeframe)
{
try
{
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = 0.008m,
TakeProfit = 0.02m
};
var backtest = _backtester.RunScalpingBotBacktest(
account,
moneyManagement,
ticker,
scenario,
timeframe,
CandleExtensions.GetMinimalDays(timeframe),
1000,
isForWatchingOnly: true);
return backtest.Signals;
}
catch (Exception ex)
{
_logger.LogError("Backtest cannot be run", ex);
}
return null;
}
public SpotlightOverview GetLastSpotlight(DateTime dateTime)
{
var overviews = _statisticRepository.GetSpotlightOverviews(dateTime);
if (overviews.Any())
{
return overviews.OrderBy(o => o.DateTime).Last();
}
return null;
}
public List<Trader> GetBestTraders()
{
return _statisticRepository.GetBestTraders();
}
public List<Trader> GetBadTraders()
{
return _statisticRepository.GetBadTraders();
}
public async Task<List<Trade>> GetLeadboardPositons()
{
var customWatchAccount = _tradingService.GetTradersWatch();
var trades = new List<Trade>();
foreach (var trader in customWatchAccount)
{
trades.AddRange(await _tradaoService.GetTrades(trader.Address));
}
return trades;
}
public async Task UpdateLeaderboard()
{
var previousBestTraders = _statisticRepository.GetBestTraders();
var lastBestTrader = (await _tradaoService.GetBestTrader()).FindGoodTrader();
// Update / Insert best trader
foreach (var trader in lastBestTrader)
{
if (previousBestTraders.Exists((p) => p.Address == trader.Address))
{
_statisticRepository.UpdateBestTrader(trader);
}
else
{
await _statisticRepository.InsertBestTrader(trader);
}
}
// Remove trader that wasnt good enough
foreach (var trader in previousBestTraders)
{
if (!lastBestTrader.Exists((t) => t.Address == trader.Address))
{
await _statisticRepository.RemoveBestTrader(trader);
}
}
await _messengerService.SendBestTraders(lastBestTrader);
}
public async Task UpdateNoobiesboard()
{
var previousBadTraders = _statisticRepository.GetBadTraders();
var lastBadTrader = (await _tradaoService.GetBadTrader()).FindBadTrader();
// Update / Insert best trader
foreach (var trader in lastBadTrader)
{
if (previousBadTraders.Exists((p) => p.Address == trader.Address))
{
_statisticRepository.UpdateBadTrader(trader);
}
else
{
await _statisticRepository.InsertBadTrader(trader);
}
}
// Remove trader that wasnt good enough
foreach (var trader in previousBadTraders)
{
if (!lastBadTrader.Exists((t) => t.Address == trader.Address))
{
await _statisticRepository.RemoveBadTrader(trader);
}
}
await _messengerService.SendBadTraders(lastBadTrader);
}
}

Some files were not shown because too many files have changed in this diff Show More