From a254db6d2470e41023960f8777203103d24c82e3 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Thu, 11 Dec 2025 23:32:06 +0700 Subject: [PATCH] Update bot market type --- .../Controllers/BacktestController.cs | 37 +- .../Models/Requests/LightBacktestResponse.cs | 5 +- .../Models/Responses/TradingBotResponse.cs | 8 +- .../Services/IFlagsmithService.cs | 20 +- .../Shared/BacktestsFilter.cs | 3 + .../Shared/FlagsmithService.cs | 51 +- src/Managing.Domain/Bots/Bot.cs | 3 +- ...dTradingTypeToBacktestsAndBots.Designer.cs | 1752 +++++++++++++++++ ...151923_AddTradingTypeToBacktestsAndBots.cs | 56 + .../ManagingDbContextModelSnapshot.cs | 12 +- .../PostgreSql/Entities/BacktestEntity.cs | 4 + .../PostgreSql/Entities/BotEntity.cs | 2 + .../PostgreSql/ManagingDbContext.cs | 3 + .../PostgreSqlBacktestRepository.cs | 6 + .../PostgreSql/PostgreSqlMappers.cs | 3 + .../src/generated/ManagingApiTypes.ts | 1 + .../organism/Backtest/backtestTable.tsx | 43 +- .../src/generated/ManagingApi.ts | 9 +- .../src/generated/ManagingApiTypes.ts | 1 + .../pages/backtestPage/backtestScanner.tsx | 11 +- 20 files changed, 1986 insertions(+), 44 deletions(-) create mode 100644 src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.Designer.cs create mode 100644 src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.cs diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index 9ddfe289..83a32364 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using Managing.Api.Models.Requests; using Managing.Api.Models.Responses; using Managing.Application.Abstractions.Repositories; @@ -35,6 +35,7 @@ public class BacktestController : BaseController private readonly IAccountService _accountService; private readonly IMoneyManagementService _moneyManagementService; private readonly IGeneticService _geneticService; + private readonly IFlagsmithService _flagsmithService; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILogger _logger; @@ -54,6 +55,7 @@ public class BacktestController : BaseController IAccountService accountService, IMoneyManagementService moneyManagementService, IGeneticService geneticService, + IFlagsmithService flagsmithService, IUserService userService, IServiceScopeFactory serviceScopeFactory, ILogger logger) : base(userService) @@ -63,6 +65,7 @@ public class BacktestController : BaseController _accountService = accountService; _moneyManagementService = moneyManagementService; _geneticService = geneticService; + _flagsmithService = flagsmithService; _serviceScopeFactory = serviceScopeFactory; _logger = logger; } @@ -152,7 +155,8 @@ public class BacktestController : BaseController [FromQuery] string? indicators = null, [FromQuery] double? durationMinDays = null, [FromQuery] double? durationMaxDays = null, - [FromQuery] string? name = null) + [FromQuery] string? name = null, + [FromQuery] TradingType? tradingType = null) { var user = await GetUser(); @@ -211,7 +215,8 @@ public class BacktestController : BaseController Tickers = tickerList, Indicators = indicatorList, DurationMin = durationMinDays.HasValue ? TimeSpan.FromDays(durationMinDays.Value) : (TimeSpan?)null, - DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null + DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null, + TradingType = tradingType }; try @@ -317,7 +322,8 @@ public class BacktestController : BaseController ScoreMessage = b.ScoreMessage, InitialBalance = b.InitialBalance, NetPnl = b.NetPnl, - PositionCount = b.PositionCount + PositionCount = b.PositionCount, + TradingType = b.Config.TradingType }), TotalCount = totalCount, CurrentPage = page, @@ -354,7 +360,8 @@ public class BacktestController : BaseController [FromQuery] string? indicators = null, [FromQuery] double? durationMinDays = null, [FromQuery] double? durationMaxDays = null, - [FromQuery] string? name = null) + [FromQuery] string? name = null, + [FromQuery] TradingType? tradingType = null) { var user = await GetUser(); @@ -427,7 +434,8 @@ public class BacktestController : BaseController Tickers = tickerList, Indicators = indicatorList, DurationMin = durationMinDays.HasValue ? TimeSpan.FromDays(durationMinDays.Value) : (TimeSpan?)null, - DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null + DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null, + TradingType = tradingType }; var (backtests, totalCount) = @@ -459,7 +467,8 @@ public class BacktestController : BaseController ScoreMessage = b.ScoreMessage, InitialBalance = b.InitialBalance, NetPnl = b.NetPnl, - PositionCount = b.PositionCount + PositionCount = b.PositionCount, + TradingType = b.Config.TradingType }), TotalCount = totalCount, CurrentPage = page, @@ -651,6 +660,20 @@ public class BacktestController : BaseController { var user = await GetUser(); + // Check if trading type is futures and verify the user has permission via feature flag + if (request.UniversalConfig.TradingType == TradingType.Futures || + request.UniversalConfig.TradingType == TradingType.BacktestFutures) + { + var isTradingFutureEnabled = await _flagsmithService.IsFeatureEnabledAsync(user.Name, "trading_future"); + + if (!isTradingFutureEnabled) + { + _logger.LogWarning("User {UserName} attempted to create futures bundle backtest but does not have the trading_future feature flag enabled", + user.Name); + return Forbid("Futures trading is not enabled for your account. Please contact support to enable this feature."); + } + } + if (string.IsNullOrEmpty(request.UniversalConfig.ScenarioName) && request.UniversalConfig.Scenario == null) { return BadRequest("Either scenario name or scenario object is required in universal configuration"); diff --git a/src/Managing.Api/Models/Requests/LightBacktestResponse.cs b/src/Managing.Api/Models/Requests/LightBacktestResponse.cs index 77da466f..e2f090b6 100644 --- a/src/Managing.Api/Models/Requests/LightBacktestResponse.cs +++ b/src/Managing.Api/Models/Requests/LightBacktestResponse.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using Managing.Domain.Backtests; using Managing.Domain.Bots; +using static Managing.Common.Enums; namespace Managing.Api.Models.Requests; @@ -23,6 +24,7 @@ public class LightBacktestResponse [Required] public decimal InitialBalance { get; set; } [Required] public decimal NetPnl { get; set; } [Required] public int PositionCount { get; set; } + [Required] public TradingType TradingType { get; set; } } public static class LightBacktestResponseMapper @@ -47,7 +49,8 @@ public static class LightBacktestResponseMapper ScoreMessage = b.ScoreMessage, InitialBalance = b.InitialBalance, NetPnl = b.NetPnl, - PositionCount = b.PositionCount + PositionCount = b.PositionCount, + TradingType = b.Config.TradingType }; } } \ No newline at end of file diff --git a/src/Managing.Api/Models/Responses/TradingBotResponse.cs b/src/Managing.Api/Models/Responses/TradingBotResponse.cs index 96481488..eaf15aa2 100644 --- a/src/Managing.Api/Models/Responses/TradingBotResponse.cs +++ b/src/Managing.Api/Models/Responses/TradingBotResponse.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Managing.Domain.Candles; using Managing.Domain.Indicators; using Managing.Domain.Trades; @@ -82,6 +82,12 @@ namespace Managing.Api.Models.Responses [Required] public Ticker Ticker { get; set; } + /// + /// The trading type (Futures, Spot, etc.) + /// + [Required] + public TradingType TradingType { get; set; } + /// /// The agent name of the master bot's owner (for copy trading bots) /// diff --git a/src/Managing.Application.Abstractions/Services/IFlagsmithService.cs b/src/Managing.Application.Abstractions/Services/IFlagsmithService.cs index 42503917..47d114c1 100644 --- a/src/Managing.Application.Abstractions/Services/IFlagsmithService.cs +++ b/src/Managing.Application.Abstractions/Services/IFlagsmithService.cs @@ -6,27 +6,27 @@ namespace Managing.Application.Abstractions.Services; public interface IFlagsmithService { /// - /// Gets flags for a specific user identity + /// Gets flags for a specific user. The username is hashed internally for privacy. /// - /// The user identity identifier - /// Flags object for the identity - Task GetIdentityFlagsAsync(string identity); + /// The username to get flags for + /// Flags object for the user + Task GetIdentityFlagsAsync(string username); /// - /// Checks if a feature is enabled for a specific identity + /// Checks if a feature is enabled for a specific user. The username is hashed internally for privacy. /// - /// The user identity identifier + /// The username to check /// The name of the feature flag /// True if the feature is enabled - Task IsFeatureEnabledAsync(string identity, string featureName); + Task IsFeatureEnabledAsync(string username, string featureName); /// - /// Gets the feature value for a specific identity + /// Gets the feature value for a specific user. The username is hashed internally for privacy. /// - /// The user identity identifier + /// The username to get feature value for /// The name of the feature flag /// The feature value as string - Task GetFeatureValueAsync(string identity, string featureName); + Task GetFeatureValueAsync(string username, string featureName); } /// diff --git a/src/Managing.Application.Abstractions/Shared/BacktestsFilter.cs b/src/Managing.Application.Abstractions/Shared/BacktestsFilter.cs index 77287152..e9d1e4cd 100644 --- a/src/Managing.Application.Abstractions/Shared/BacktestsFilter.cs +++ b/src/Managing.Application.Abstractions/Shared/BacktestsFilter.cs @@ -1,3 +1,5 @@ +using static Managing.Common.Enums; + namespace Managing.Application.Abstractions.Shared; public class BacktestsFilter @@ -12,6 +14,7 @@ public class BacktestsFilter public IEnumerable? Indicators { get; set; } public TimeSpan? DurationMin { get; set; } public TimeSpan? DurationMax { get; set; } + public TradingType? TradingType { get; set; } } diff --git a/src/Managing.Application/Shared/FlagsmithService.cs b/src/Managing.Application/Shared/FlagsmithService.cs index bc33e1ec..32084afd 100644 --- a/src/Managing.Application/Shared/FlagsmithService.cs +++ b/src/Managing.Application/Shared/FlagsmithService.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography; +using System.Text; using Flagsmith; using Managing.Application.Abstractions.Services; using Microsoft.Extensions.Logging; @@ -30,30 +32,32 @@ public class FlagsmithService : IFlagsmithService _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task GetIdentityFlagsAsync(string identity) + public async Task GetIdentityFlagsAsync(string username) { - if (string.IsNullOrWhiteSpace(identity)) + if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentException("Identity cannot be null or empty", nameof(identity)); + throw new ArgumentException("Username cannot be null or empty", nameof(username)); } + var hashedIdentity = HashUsername(username); + try { - var flags = await _flagsmithClient.GetIdentityFlags(identity); + var flags = await _flagsmithClient.GetIdentityFlags(hashedIdentity); return new FlagsmithFlagsWrapper(flags); } catch (Exception ex) { - _logger.LogError(ex, "Error getting flags for identity {Identity}", identity); + _logger.LogError(ex, "Error getting flags for username {Username} (hashed: {HashedIdentity})", username, hashedIdentity); throw; } } - public async Task IsFeatureEnabledAsync(string identity, string featureName) + public async Task IsFeatureEnabledAsync(string username, string featureName) { - if (string.IsNullOrWhiteSpace(identity)) + if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentException("Identity cannot be null or empty", nameof(identity)); + throw new ArgumentException("Username cannot be null or empty", nameof(username)); } if (string.IsNullOrWhiteSpace(featureName)) @@ -63,21 +67,21 @@ public class FlagsmithService : IFlagsmithService try { - var flags = await GetIdentityFlagsAsync(identity); + var flags = await GetIdentityFlagsAsync(username); return await flags.IsFeatureEnabled(featureName); } catch (Exception ex) { - _logger.LogError(ex, "Error checking feature {FeatureName} for identity {Identity}", featureName, identity); + _logger.LogError(ex, "Error checking feature {FeatureName} for username {Username}", featureName, username); return false; // Default to false on error } } - public async Task GetFeatureValueAsync(string identity, string featureName) + public async Task GetFeatureValueAsync(string username, string featureName) { - if (string.IsNullOrWhiteSpace(identity)) + if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentException("Identity cannot be null or empty", nameof(identity)); + throw new ArgumentException("Username cannot be null or empty", nameof(username)); } if (string.IsNullOrWhiteSpace(featureName)) @@ -87,15 +91,32 @@ public class FlagsmithService : IFlagsmithService try { - var flags = await GetIdentityFlagsAsync(identity); + var flags = await GetIdentityFlagsAsync(username); return await flags.GetFeatureValue(featureName); } catch (Exception ex) { - _logger.LogError(ex, "Error getting feature value {FeatureName} for identity {Identity}", featureName, identity); + _logger.LogError(ex, "Error getting feature value {FeatureName} for username {Username}", featureName, username); return null; // Default to null on error } } + + /// + /// Hashes the username using SHA256 to create a privacy-preserving identity for Flagsmith + /// + /// The username to hash + /// SHA256 hash of the username as a hexadecimal string + private static string HashUsername(string username) + { + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentException("Username cannot be null or empty", nameof(username)); + } + + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(username)); + return Convert.ToHexString(hashBytes).ToLowerInvariant(); + } } /// diff --git a/src/Managing.Domain/Bots/Bot.cs b/src/Managing.Domain/Bots/Bot.cs index a83e00cc..dbaba6cd 100644 --- a/src/Managing.Domain/Bots/Bot.cs +++ b/src/Managing.Domain/Bots/Bot.cs @@ -1,4 +1,4 @@ -using Managing.Domain.Users; +using Managing.Domain.Users; using static Managing.Common.Enums; namespace Managing.Domain.Bots @@ -9,6 +9,7 @@ namespace Managing.Domain.Bots public Guid Identifier { get; set; } public string Name { get; set; } public Ticker Ticker { get; set; } + public TradingType TradingType { get; set; } public BotStatus Status { get; set; } public DateTime StartupTime { get; set; } public DateTime CreateDate { get; set; } diff --git a/src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.Designer.cs new file mode 100644 index 00000000..f2e7015a --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.Designer.cs @@ -0,0 +1,1752 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20251211151923_AddTradingTypeToBacktestsAndBots")] + partial class AddTradingTypeToBacktestsAndBots + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsGmxInitialized") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Key") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStrategiesCount") + .HasColumnType("integer"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("BacktestCount") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Losses") + .HasColumnType("integer"); + + b.Property("NetPnL") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Runtime") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalBalance") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("TotalFees") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("TotalPnL") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalROI") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalVolume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Wins") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AgentName") + .IsUnique(); + + b.HasIndex("TotalPnL"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AgentSummaries"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Duration") + .ValueGeneratedOnAdd() + .HasColumnType("interval") + .HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0)); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorsCount") + .HasColumnType("integer"); + + b.Property("IndicatorsCsv") + .IsRequired() + .HasColumnType("text"); + + b.Property("InitialBalance") + .HasColumnType("decimal(18,8)"); + + b.Property("MaxDrawdown") + .ValueGeneratedOnAdd() + .HasColumnType("decimal(18,8)") + .HasDefaultValue(0m); + + b.Property("MaxDrawdownRecoveryTime") + .ValueGeneratedOnAdd() + .HasColumnType("interval") + .HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0)); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("NetPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("PositionCount") + .HasColumnType("integer"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SharpeRatio") + .ValueGeneratedOnAdd() + .HasColumnType("decimal(18,8)") + .HasDefaultValue(0m); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("Ticker") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Timeframe") + .HasColumnType("integer"); + + b.Property("TradingType") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("UserId"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserId", "Name"); + + b.HasIndex("UserId", "Score"); + + b.HasIndex("UserId", "Ticker"); + + b.HasIndex("UserId", "Timeframe"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("AccumulatedRunTimeSeconds") + .HasColumnType("bigint"); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("LastStartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LastStopTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LongPositionCount") + .HasColumnType("integer"); + + b.Property("MasterBotUserId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("NetPnL") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("ShortPositionCount") + .HasColumnType("integer"); + + b.Property("StartupTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeLosses") + .HasColumnType("integer"); + + b.Property("TradeWins") + .HasColumnType("integer"); + + b.Property("TradingType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("MasterBotUserId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("Bots"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("DateTimeRangesJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("MoneyManagementVariantsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TickerVariantsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UniversalConfigJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "CreatedAt"); + + b.HasIndex("UserId", "Name", "Version"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.JobEntity", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedWorkerId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("BundleRequestId") + .HasColumnType("uuid"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("FailureCategory") + .HasColumnType("integer"); + + b.Property("GeneticRequestId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsRetryable") + .HasColumnType("boolean"); + + b.Property("JobType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("LastHeartbeat") + .HasColumnType("timestamp with time zone"); + + b.Property("MaxRetries") + .HasColumnType("integer"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("ProgressPercentage") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("RequestId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultJson") + .HasColumnType("jsonb"); + + b.Property("RetryAfter") + .HasColumnType("timestamp with time zone"); + + b.Property("RetryCount") + .HasColumnType("integer"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BundleRequestId") + .HasDatabaseName("idx_bundle_request"); + + b.HasIndex("GeneticRequestId") + .HasDatabaseName("idx_genetic_request"); + + b.HasIndex("AssignedWorkerId", "Status") + .HasDatabaseName("idx_assigned_worker"); + + b.HasIndex("UserId", "Status") + .HasDatabaseName("idx_user_status"); + + b.HasIndex("Status", "JobType", "Priority", "CreatedAt") + .HasDatabaseName("idx_status_jobtype_priority_created"); + + b.ToTable("Jobs", (string)null); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("GasFees") + .HasColumnType("decimal(18,8)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("InitiatorIdentifier") + .HasColumnType("uuid"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("NetPnL") + .HasColumnType("decimal(18,8)"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradingType") + .HasColumnType("integer"); + + b.Property("UiFees") + .HasColumnType("decimal(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("InitiatorIdentifier"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Date"); + + b.HasIndex("Identifier", "Date", "UserId") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("LastConnectionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("OwnerWalletAddress") + .HasColumnType("text"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("AgentName"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WhitelistAccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmbeddedWallet") + .IsRequired() + .HasMaxLength(42) + .HasColumnType("character varying(42)"); + + b.Property("ExternalEthereumAccount") + .HasMaxLength(42) + .HasColumnType("character varying(42)"); + + b.Property("IsWhitelisted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("PrivyCreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivyId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TwitterAccount") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("EmbeddedWallet") + .IsUnique(); + + b.HasIndex("ExternalEthereumAccount"); + + b.HasIndex("PrivyId") + .IsUnique(); + + b.HasIndex("TwitterAccount"); + + b.ToTable("WhitelistAccounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany("Accounts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "MasterBotUser") + .WithMany() + .HasForeignKey("MasterBotUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("MasterBotUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.JobEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.cs b/src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.cs new file mode 100644 index 00000000..552fcd18 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AddTradingTypeToBacktestsAndBots : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Ticker", + table: "Bots", + type: "text", + nullable: false, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AddColumn( + name: "TradingType", + table: "Bots", + type: "text", + nullable: false, + defaultValue: "Spot"); + + migrationBuilder.AddColumn( + name: "TradingType", + table: "Backtests", + type: "integer", + nullable: false, + defaultValue: 1); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TradingType", + table: "Bots"); + + migrationBuilder.DropColumn( + name: "TradingType", + table: "Backtests"); + + migrationBuilder.AlterColumn( + name: "Ticker", + table: "Bots", + type: "integer", + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs index 8193a57d..4771d886 100644 --- a/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs +++ b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs @@ -259,6 +259,9 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("Timeframe") .HasColumnType("integer"); + b.Property("TradingType") + .HasColumnType("integer"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); @@ -348,8 +351,9 @@ namespace Managing.Infrastructure.Databases.Migrations .IsRequired() .HasColumnType("text"); - b.Property("Ticker") - .HasColumnType("integer"); + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); b.Property("TradeLosses") .HasColumnType("integer"); @@ -357,6 +361,10 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("TradeWins") .HasColumnType("integer"); + b.Property("TradingType") + .IsRequired() + .HasColumnType("text"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs index f4cb32c5..7178a389 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs @@ -50,6 +50,10 @@ public class BacktestEntity [Required] public int Timeframe { get; set; } + // Stored trading type as enum numeric value for direct filtering + [Required] + public int TradingType { get; set; } + // Comma-separated indicator types for filtering, e.g., "EMA_CROSS,MACD_CROSS" [Column(TypeName = "text")] public string IndicatorsCsv { get; set; } = string.Empty; diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs index 85c70d2e..12baf955 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs @@ -13,6 +13,8 @@ public class BotEntity public Ticker Ticker { get; set; } + public TradingType TradingType { get; set; } + public int UserId { get; set; } [ForeignKey("UserId")] public UserEntity User { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs index 4dc63d49..816b731c 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs @@ -161,6 +161,7 @@ public class ManagingDbContext : DbContext entity.Property(e => e.Name).IsRequired().HasMaxLength(255); entity.Property(e => e.Ticker).HasMaxLength(32); entity.Property(e => e.Timeframe).IsRequired(); + entity.Property(e => e.TradingType).IsRequired(); entity.Property(e => e.IndicatorsCsv).HasColumnType("text"); entity.Property(e => e.IndicatorsCount).IsRequired(); entity.Property(e => e.PositionsJson).HasColumnType("jsonb"); @@ -522,6 +523,8 @@ public class ManagingDbContext : DbContext entity.HasKey(e => e.Identifier); entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255); entity.Property(e => e.Name).IsRequired().HasMaxLength(255); + entity.Property(e => e.Ticker).IsRequired().HasConversion(); + entity.Property(e => e.TradingType).IsRequired().HasConversion(); entity.Property(e => e.Status).IsRequired().HasConversion(); entity.Property(e => e.CreateDate).IsRequired(); entity.Property(e => e.StartupTime).IsRequired(); diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs index df18a0ad..85faf24f 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs @@ -438,6 +438,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value); if (filter.DurationMax.HasValue) baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value); + if (filter.TradingType.HasValue) + baseQuery = baseQuery.Where(b => b.TradingType == (int)filter.TradingType.Value); } var entities = await baseQuery.ToListAsync().ConfigureAwait(false); @@ -503,6 +505,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value); if (filter.DurationMax.HasValue) baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value); + if (filter.TradingType.HasValue) + baseQuery = baseQuery.Where(b => b.TradingType == (int)filter.TradingType.Value); } var afterQueryMs = stopwatch.ElapsedMilliseconds; @@ -642,6 +646,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value); if (filter.DurationMax.HasValue) baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value); + if (filter.TradingType.HasValue) + baseQuery = baseQuery.Where(b => b.TradingType == (int)filter.TradingType.Value); } var afterQueryMs = stopwatch.ElapsedMilliseconds; diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs index 593babc6..019f5ad8 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs @@ -350,6 +350,7 @@ public static class PostgreSqlMappers Name = backtest.Config?.Name ?? string.Empty, Ticker = backtest.Config?.Ticker.ToString() ?? string.Empty, Timeframe = (int)backtest.Config.Timeframe, + TradingType = (int)backtest.Config.TradingType, IndicatorsCsv = string.Join(',', backtest.Config.Scenario.Indicators.Select(i => i.Type.ToString())), IndicatorsCount = backtest.Config.Scenario.Indicators.Count, PositionsJson = JsonConvert.SerializeObject(backtest.Positions.Values.ToList(), jsonSettings), @@ -750,6 +751,7 @@ public static class PostgreSqlMappers CreateDate = entity.CreateDate, Name = entity.Name, Ticker = entity.Ticker, + TradingType = entity.TradingType, StartupTime = entity.StartupTime, LastStartTime = entity.LastStartTime, LastStopTime = entity.LastStopTime, @@ -782,6 +784,7 @@ public static class PostgreSqlMappers CreateDate = bot.CreateDate, Name = bot.Name, Ticker = bot.Ticker, + TradingType = bot.TradingType, StartupTime = bot.StartupTime, LastStartTime = bot.LastStartTime, LastStopTime = bot.LastStopTime, diff --git a/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts b/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts index fefca04e..4460a4b7 100644 --- a/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts +++ b/src/Managing.Web3Proxy/src/generated/ManagingApiTypes.ts @@ -987,6 +987,7 @@ export interface TradingBotResponse { startupTime: Date; name: string; ticker: Ticker; + tradingType: TradingType; masterAgentName?: string | null; } diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx index f43f2bdc..06c2eb12 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestTable.tsx @@ -5,7 +5,7 @@ import {useExpanded, useFilters, usePagination, useSortBy, useTable,} from 'reac import useApiUrlStore from '../../../app/store/apiStore' import useBacktestStore from '../../../app/store/backtestStore' import type {Backtest, LightBacktestResponse} from '../../../generated/ManagingApi' -import {BacktestClient, BacktestSortableColumn, IndicatorType} from '../../../generated/ManagingApi' +import {BacktestClient, BacktestSortableColumn, IndicatorType, TradingType} from '../../../generated/ManagingApi' import {ConfigDisplayModal, IndicatorsDisplay, SelectColumnFilter} from '../../mollecules' import {UnifiedTradingModal} from '../index' import Toast from '../../mollecules/Toast/Toast' @@ -148,6 +148,7 @@ interface BacktestTableProps { indicators?: string[] | null durationMinDays?: number | null durationMaxDays?: number | null + tradingType?: TradingType | null }) => void filters?: { nameContains?: string | null @@ -160,6 +161,7 @@ interface BacktestTableProps { indicators?: string[] | null durationMinDays?: number | null durationMaxDays?: number | null + tradingType?: TradingType | null } openFiltersTrigger?: number // When this changes, open the filter sidebar } @@ -196,6 +198,7 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh const [selectedIndicators, setSelectedIndicators] = useState([]) const [durationMinDays, setDurationMinDays] = useState(null) const [durationMaxDays, setDurationMaxDays] = useState(null) + const [selectedTradingType, setSelectedTradingType] = useState(null) // Delete confirmation state const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) @@ -227,6 +230,7 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh setSelectedIndicators([]) setDurationMinDays(null) setDurationMaxDays(null) + setSelectedTradingType(null) } // Refresh data function @@ -249,6 +253,7 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh indicators: selectedIndicators.length ? selectedIndicators : null, durationMinDays, durationMaxDays, + tradingType: selectedTradingType, }) setIsFilterOpen(false) } @@ -270,7 +275,8 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh filters.indicators?.join(',') || undefined, filters.durationMinDays || undefined, filters.durationMaxDays || undefined, - filters.nameContains || undefined + filters.nameContains || undefined, + filters.tradingType || undefined ) // Parse the response to get the deleted count @@ -305,7 +311,8 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh filters.tickers?.length || filters.indicators?.length || filters.durationMinDays !== null || - filters.durationMaxDays !== null + filters.durationMaxDays !== null || + filters.tradingType !== null ) } @@ -335,6 +342,7 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh setSelectedIndicators(filters.indicators ? [...filters.indicators] : []) setDurationMinDays(filters.durationMinDays ?? null) setDurationMaxDays(filters.durationMaxDays ?? null) + setSelectedTradingType(filters.tradingType ?? null) }, [filters]) // Handle external trigger to open filters @@ -507,6 +515,16 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh accessor: 'config.timeframe', disableSortBy: true, }, + { + Filter: SelectColumnFilter, + Header: 'Trading Type', + accessor: 'tradingType', + Cell: ({cell}: any) => { + const tradingType = cell.value as TradingType; + return {tradingType}; + }, + disableSortBy: true, + }, { Header: 'Indicators', accessor: 'config.scenario.indicators', @@ -798,6 +816,25 @@ const BacktestTable: React.FC = ({list, isFetching, onSortCh + {/* Trading Type */} +
+
Trading Type
+
+ {Object.values(TradingType).map((type) => ( + + ))} + {selectedTradingType && ( + + )} +
+
+
diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index 84909337..9b8925d6 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -797,7 +797,7 @@ export class BacktestClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - backtest_DeleteBacktestsByFilters(scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined): Promise { + backtest_DeleteBacktestsByFilters(scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined, tradingType: TradingType | null | undefined): Promise { let url_ = this.baseUrl + "/Backtest/ByFilters?"; if (scoreMin !== undefined && scoreMin !== null) url_ += "scoreMin=" + encodeURIComponent("" + scoreMin) + "&"; @@ -819,6 +819,8 @@ export class BacktestClient extends AuthorizedApiBase { url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&"; if (name !== undefined && name !== null) url_ += "name=" + encodeURIComponent("" + name) + "&"; + if (tradingType !== undefined && tradingType !== null) + url_ += "tradingType=" + encodeURIComponent("" + tradingType) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -945,7 +947,7 @@ export class BacktestClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - backtest_GetBacktestsPaginated(page: number | undefined, pageSize: number | undefined, sortBy: BacktestSortableColumn | undefined, sortOrder: string | null | undefined, scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined): Promise { + backtest_GetBacktestsPaginated(page: number | undefined, pageSize: number | undefined, sortBy: BacktestSortableColumn | undefined, sortOrder: string | null | undefined, scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined, tradingType: TradingType | null | undefined): Promise { let url_ = this.baseUrl + "/Backtest/Paginated?"; if (page === null) throw new Error("The parameter 'page' cannot be null."); @@ -981,6 +983,8 @@ export class BacktestClient extends AuthorizedApiBase { url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&"; if (name !== undefined && name !== null) url_ += "name=" + encodeURIComponent("" + name) + "&"; + if (tradingType !== undefined && tradingType !== null) + url_ += "tradingType=" + encodeURIComponent("" + tradingType) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -5482,6 +5486,7 @@ export interface TradingBotResponse { startupTime: Date; name: string; ticker: Ticker; + tradingType: TradingType; masterAgentName?: string | null; } diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index fefca04e..4460a4b7 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -987,6 +987,7 @@ export interface TradingBotResponse { startupTime: Date; name: string; ticker: Ticker; + tradingType: TradingType; masterAgentName?: string | null; } diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtestScanner.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtestScanner.tsx index e1586e91..267dfeb6 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtestScanner.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtestScanner.tsx @@ -7,7 +7,7 @@ import {Loader, Slider} from '../../components/atoms' import {BottomMenuBar, Modal, Toast} from '../../components/mollecules' import {BacktestTable, UnifiedTradingModal} from '../../components/organism' import type {LightBacktestResponse} from '../../generated/ManagingApi' -import {BacktestClient, BacktestSortableColumn} from '../../generated/ManagingApi' +import {BacktestClient, BacktestSortableColumn, TradingType} from '../../generated/ManagingApi' const PAGE_SIZE = 50 @@ -38,6 +38,7 @@ const BacktestScanner: React.FC = () => { indicators?: string[] | null durationMinDays?: number | null durationMaxDays?: number | null + tradingType?: TradingType | null }>({}) const { apiUrl } = useApiUrlStore() @@ -69,6 +70,7 @@ const BacktestScanner: React.FC = () => { filters.durationMinDays ?? null, filters.durationMaxDays ?? null, filters.nameContains ?? null, + filters.tradingType ?? null, ) return { backtests: (response.backtests as LightBacktestResponse[]) || [], @@ -217,6 +219,7 @@ const BacktestScanner: React.FC = () => { indicators?: string[] | null durationMinDays?: number | null durationMaxDays?: number | null + tradingType?: TradingType | null }) => { setFilters(newFilters) setCurrentPage(1) @@ -246,7 +249,8 @@ const BacktestScanner: React.FC = () => { (filters.tickers && filters.tickers.length) || (filters.indicators && filters.indicators.length) || (filters.durationMinDays !== undefined && filters.durationMinDays !== null) || - (filters.durationMaxDays !== undefined && filters.durationMaxDays !== null) + (filters.durationMaxDays !== undefined && filters.durationMaxDays !== null) || + (filters.tradingType !== undefined && filters.tradingType !== null) ) ? (
Active filters: @@ -274,6 +278,9 @@ const BacktestScanner: React.FC = () => { {(filters.durationMinDays !== undefined && filters.durationMinDays !== null) || (filters.durationMaxDays !== undefined && filters.durationMaxDays !== null) ? (
Duration: {filters.durationMinDays ?? 0}–{filters.durationMaxDays ?? '∞'} days
) : null} + {filters.tradingType !== undefined && filters.tradingType !== null && ( +
Trading Type: {filters.tradingType}
+ )}
) : null}