docker files fixes from liaqat
This commit is contained in:
1
src/.dockerignore
Normal file
1
src/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
4
src/.editorconfig
Normal file
4
src/.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
[*.cs]
|
||||
|
||||
# IDE0008: Use explicit type
|
||||
dotnet_diagnostic.IDE0008.severity = none
|
||||
45
src/Managing.Api.Workers.csproj
Normal file
45
src/Managing.Api.Workers.csproj
Normal 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>
|
||||
31
src/Managing.Api.Workers/Controllers/WorkerController.cs
Normal file
31
src/Managing.Api.Workers/Controllers/WorkerController.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
36
src/Managing.Api.Workers/Dockerfile
Normal file
36
src/Managing.Api.Workers/Dockerfile
Normal 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"]
|
||||
20
src/Managing.Api.Workers/Filters/EnumSchemaFilter.cs
Normal file
20
src/Managing.Api.Workers/Filters/EnumSchemaFilter.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/Managing.Api.Workers/Managing.Api.Workers.csproj
Normal file
45
src/Managing.Api.Workers/Managing.Api.Workers.csproj
Normal 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>
|
||||
150
src/Managing.Api.Workers/Program.cs
Normal file
150
src/Managing.Api.Workers/Program.cs
Normal 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();
|
||||
75
src/Managing.Api.Workers/Workers/BaseWorker.cs
Normal file
75
src/Managing.Api.Workers/Workers/BaseWorker.cs
Normal 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);
|
||||
}
|
||||
29
src/Managing.Api.Workers/Workers/FeeWorker.cs
Normal file
29
src/Managing.Api.Workers/Workers/FeeWorker.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
28
src/Managing.Api.Workers/Workers/LeaderboardWorker.cs
Normal file
28
src/Managing.Api.Workers/Workers/LeaderboardWorker.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
28
src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs
Normal file
28
src/Managing.Api.Workers/Workers/NoobiesboardWorker.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
37
src/Managing.Api.Workers/Workers/PositionFetcher.cs
Normal file
37
src/Managing.Api.Workers/Workers/PositionFetcher.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
200
src/Managing.Api.Workers/Workers/PositionManagerWorker.cs
Normal file
200
src/Managing.Api.Workers/Workers/PositionManagerWorker.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
40
src/Managing.Api.Workers/Workers/PricesBaseWorker.cs
Normal file
40
src/Managing.Api.Workers/Workers/PricesBaseWorker.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
23
src/Managing.Api.Workers/Workers/PricesFiveMinutesWorker.cs
Normal file
23
src/Managing.Api.Workers/Workers/PricesFiveMinutesWorker.cs
Normal 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
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
23
src/Managing.Api.Workers/Workers/PricesFourHoursWorker.cs
Normal file
23
src/Managing.Api.Workers/Workers/PricesFourHoursWorker.cs
Normal 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
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
23
src/Managing.Api.Workers/Workers/PricesOneDayWorker.cs
Normal file
23
src/Managing.Api.Workers/Workers/PricesOneDayWorker.cs
Normal 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
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
22
src/Managing.Api.Workers/Workers/PricesOneHourWorker.cs
Normal file
22
src/Managing.Api.Workers/Workers/PricesOneHourWorker.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
34
src/Managing.Api.Workers/Workers/SpotlightWorker.cs
Normal file
34
src/Managing.Api.Workers/Workers/SpotlightWorker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs
Normal file
28
src/Managing.Api.Workers/Workers/TopVolumeTickerWorker.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
29
src/Managing.Api.Workers/Workers/TraderWatcher.cs
Normal file
29
src/Managing.Api.Workers/Workers/TraderWatcher.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
src/Managing.Api.Workers/appsettings.Development.json
Normal file
23
src/Managing.Api.Workers/appsettings.Development.json
Normal 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": "*"
|
||||
}
|
||||
24
src/Managing.Api.Workers/appsettings.Khalid.json
Normal file
24
src/Managing.Api.Workers/appsettings.Khalid.json
Normal 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": "*"
|
||||
}
|
||||
39
src/Managing.Api.Workers/appsettings.Lowpro.json
Normal file
39
src/Managing.Api.Workers/appsettings.Lowpro.json
Normal 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": "*"
|
||||
}
|
||||
24
src/Managing.Api.Workers/appsettings.Oda-docker.json
Normal file
24
src/Managing.Api.Workers/appsettings.Oda-docker.json
Normal 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": "*"
|
||||
}
|
||||
24
src/Managing.Api.Workers/appsettings.Oda.json
Normal file
24
src/Managing.Api.Workers/appsettings.Oda.json
Normal 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": "*"
|
||||
}
|
||||
27
src/Managing.Api/Authorization/JwtMiddleware.cs
Normal file
27
src/Managing.Api/Authorization/JwtMiddleware.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
70
src/Managing.Api/Authorization/JwtUtils.cs
Normal file
70
src/Managing.Api/Authorization/JwtUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/Managing.Api/Controllers/AccountController.cs
Normal file
58
src/Managing.Api/Controllers/AccountController.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
133
src/Managing.Api/Controllers/BacktestController.cs
Normal file
133
src/Managing.Api/Controllers/BacktestController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/Managing.Api/Controllers/BaseController.cs
Normal file
35
src/Managing.Api/Controllers/BaseController.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
168
src/Managing.Api/Controllers/BotController.cs
Normal file
168
src/Managing.Api/Controllers/BotController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
73
src/Managing.Api/Controllers/DataController.cs
Normal file
73
src/Managing.Api/Controllers/DataController.cs
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
44
src/Managing.Api/Controllers/MoneyManagementController.cs
Normal file
44
src/Managing.Api/Controllers/MoneyManagementController.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
83
src/Managing.Api/Controllers/ScenarioController.cs
Normal file
83
src/Managing.Api/Controllers/ScenarioController.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
30
src/Managing.Api/Controllers/SettingsController.cs
Normal file
30
src/Managing.Api/Controllers/SettingsController.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
107
src/Managing.Api/Controllers/TradingController.cs
Normal file
107
src/Managing.Api/Controllers/TradingController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
39
src/Managing.Api/Controllers/UserController.cs
Normal file
39
src/Managing.Api/Controllers/UserController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
45
src/Managing.Api/Controllers/WorkflowController.cs
Normal file
45
src/Managing.Api/Controllers/WorkflowController.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/Managing.Api/Dockerfile
Normal file
36
src/Managing.Api/Dockerfile
Normal 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"]
|
||||
62
src/Managing.Api/Exceptions/GlobalErrorHandlingMiddleware.cs
Normal file
62
src/Managing.Api/Exceptions/GlobalErrorHandlingMiddleware.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
20
src/Managing.Api/Filters/EnumSchemaFilter.cs
Normal file
20
src/Managing.Api/Filters/EnumSchemaFilter.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/Managing.Api/Managing.Api.csproj
Normal file
43
src/Managing.Api/Managing.Api.csproj
Normal 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>
|
||||
12
src/Managing.Api/Models/Requests/CreateScenarioRequest.cs
Normal file
12
src/Managing.Api/Models/Requests/CreateScenarioRequest.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
16
src/Managing.Api/Models/Requests/CreateStrategyRequest.cs
Normal file
16
src/Managing.Api/Models/Requests/CreateStrategyRequest.cs
Normal 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; }
|
||||
}
|
||||
15
src/Managing.Api/Models/Requests/LoginRequest.cs
Normal file
15
src/Managing.Api/Models/Requests/LoginRequest.cs
Normal 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; }
|
||||
}
|
||||
15
src/Managing.Api/Models/Requests/RunBacktestRequest.cs
Normal file
15
src/Managing.Api/Models/Requests/RunBacktestRequest.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
25
src/Managing.Api/Models/Requests/StartBotRequest.cs
Normal file
25
src/Managing.Api/Models/Requests/StartBotRequest.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
45
src/Managing.Api/Models/Responses/TradingBot.cs
Normal file
45
src/Managing.Api/Models/Responses/TradingBot.cs
Normal 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
168
src/Managing.Api/Program.cs
Normal 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();
|
||||
13
src/Managing.Api/appsettings.Development.json
Normal file
13
src/Managing.Api/appsettings.Development.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Error",
|
||||
"System": "Error",
|
||||
"Microsoft": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ElasticConfiguration": {
|
||||
"Uri": "http://elasticsearch:9200/"
|
||||
}
|
||||
}
|
||||
34
src/Managing.Api/appsettings.Khalid.json
Normal file
34
src/Managing.Api/appsettings.Khalid.json
Normal 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": "*"
|
||||
}
|
||||
46
src/Managing.Api/appsettings.Lowpro.json
Normal file
46
src/Managing.Api/appsettings.Lowpro.json
Normal 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": "*"
|
||||
}
|
||||
37
src/Managing.Api/appsettings.Oda-Sandbox.json
Normal file
37
src/Managing.Api/appsettings.Oda-Sandbox.json
Normal 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": "*"
|
||||
}
|
||||
36
src/Managing.Api/appsettings.Oda-docker.json
Normal file
36
src/Managing.Api/appsettings.Oda-docker.json
Normal 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": "*"
|
||||
}
|
||||
34
src/Managing.Api/appsettings.Oda.json
Normal file
34
src/Managing.Api/appsettings.Oda.json
Normal 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": "*"
|
||||
}
|
||||
48
src/Managing.Api/appsettings.json
Normal file
48
src/Managing.Api/appsettings.json
Normal 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": "*"
|
||||
}
|
||||
4
src/Managing.Api/captain-definition
Normal file
4
src/Managing.Api/captain-definition
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"dockerfilePath": "Dockerfile"
|
||||
}
|
||||
19
src/Managing.Api/failures.txt
Normal file
19
src/Managing.Api/failures.txt
Normal 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"}]}}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Managing.Application.Abstractions.Services;
|
||||
|
||||
public interface IStreamService
|
||||
{
|
||||
Task SubscribeCandle();
|
||||
Task UnSubscribeCandle();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Managing.Common;
|
||||
|
||||
namespace Managing.Application.Abstractions.Services;
|
||||
|
||||
public interface ITickerService
|
||||
{
|
||||
Task<List<Enums.Ticker>> GetAvailableTicker();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
51
src/Managing.Application.Tests/BaseTests.cs
Normal file
51
src/Managing.Application.Tests/BaseTests.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
408
src/Managing.Application.Tests/BotsTests.cs
Normal file
408
src/Managing.Application.Tests/BotsTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Managing.Application.Tests/CandleHelpersTests.cs
Normal file
20
src/Managing.Application.Tests/CandleHelpersTests.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
24
src/Managing.Application.Tests/MathHelpersTests.cs
Normal file
24
src/Managing.Application.Tests/MathHelpersTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/Managing.Application.Tests/PositionTests.cs
Normal file
56
src/Managing.Application.Tests/PositionTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
251
src/Managing.Application.Tests/ProfitAndLossTests.cs
Normal file
251
src/Managing.Application.Tests/ProfitAndLossTests.cs
Normal 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", "")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/Managing.Application.Tests/RiskHelpersTests.cs
Normal file
62
src/Managing.Application.Tests/RiskHelpersTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
212
src/Managing.Application.Tests/StrategyTests.cs
Normal file
212
src/Managing.Application.Tests/StrategyTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/Managing.Application.Tests/TradingBaseTests.cs
Normal file
85
src/Managing.Application.Tests/TradingBaseTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/Managing.Application.Tests/WorkflowTests.cs
Normal file
54
src/Managing.Application.Tests/WorkflowTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
64
src/Managing.Application.Workers/PricesService.cs
Normal file
64
src/Managing.Application.Workers/PricesService.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
304
src/Managing.Application.Workers/StatisticService.cs
Normal file
304
src/Managing.Application.Workers/StatisticService.cs
Normal 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
Reference in New Issue
Block a user