diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs
index 4ddcaa1e..33593735 100644
--- a/src/Managing.Api/Controllers/BacktestController.cs
+++ b/src/Managing.Api/Controllers/BacktestController.cs
@@ -419,21 +419,30 @@ public class BacktestController : BaseController
/// Creates a bundle backtest request with the specified configurations.
/// This endpoint creates a request that will be processed by a background worker.
///
- /// The list of backtest requests to execute.
- /// Display name for the bundle (required).
+ /// The bundle backtest request with variant lists.
/// The bundle backtest request with ID for tracking progress.
[HttpPost]
[Route("BacktestBundle")]
public async Task> RunBundle([FromBody] RunBundleBacktestRequest request)
{
- if (request?.Requests == null || !request.Requests.Any())
+ if (request?.UniversalConfig == null)
{
- return BadRequest("At least one backtest request is required");
+ return BadRequest("Universal configuration is required");
}
- if (request.Requests.Count > 10)
+ if (request.DateTimeRanges == null || !request.DateTimeRanges.Any())
{
- return BadRequest("Maximum of 10 backtests allowed per bundle request");
+ return BadRequest("At least one DateTime range is required");
+ }
+
+ if (request.MoneyManagementVariants == null || !request.MoneyManagementVariants.Any())
+ {
+ return BadRequest("At least one money management variant is required");
+ }
+
+ if (request.TickerVariants == null || !request.TickerVariants.Any())
+ {
+ return BadRequest("At least one ticker variant is required");
}
if (string.IsNullOrWhiteSpace(request.Name))
@@ -441,32 +450,35 @@ public class BacktestController : BaseController
return BadRequest("Bundle name is required");
}
+ // Calculate total number of backtests
+ var totalBacktests = request.DateTimeRanges.Count * request.MoneyManagementVariants.Count * request.TickerVariants.Count;
+
+ if (totalBacktests > 100)
+ {
+ return BadRequest("Maximum of 100 backtests allowed per bundle request");
+ }
+
try
{
var user = await GetUser();
- // Validate all requests before creating the bundle
- foreach (var req in request.Requests)
+ // Validate universal configuration
+ if (string.IsNullOrEmpty(request.UniversalConfig.AccountName))
{
- if (req?.Config == null)
- {
- return BadRequest("Invalid request: Configuration is required");
- }
+ return BadRequest("Account name is required in universal configuration");
+ }
- if (string.IsNullOrEmpty(req.Config.AccountName))
- {
- return BadRequest("Invalid request: Account name is required");
- }
+ if (string.IsNullOrEmpty(request.UniversalConfig.ScenarioName) && request.UniversalConfig.Scenario == null)
+ {
+ return BadRequest("Either scenario name or scenario object is required in universal configuration");
+ }
- if (string.IsNullOrEmpty(req.Config.ScenarioName) && req.Config.Scenario == null)
+ // Validate all money management variants
+ foreach (var mmVariant in request.MoneyManagementVariants)
+ {
+ if (mmVariant.MoneyManagement == null)
{
- return BadRequest("Invalid request: Either scenario name or scenario object is required");
- }
-
- if (string.IsNullOrEmpty(req.Config.MoneyManagementName) && req.Config.MoneyManagement == null)
- {
- return BadRequest(
- "Invalid request: Either money management name or money management object is required");
+ return BadRequest("Each money management variant must have a money management object");
}
}
@@ -474,8 +486,11 @@ public class BacktestController : BaseController
var bundleRequest = new BundleBacktestRequest
{
User = user,
- BacktestRequestsJson = JsonSerializer.Serialize(request.Requests),
- TotalBacktests = request.Requests.Count,
+ UniversalConfigJson = JsonSerializer.Serialize(request.UniversalConfig),
+ DateTimeRangesJson = JsonSerializer.Serialize(request.DateTimeRanges),
+ MoneyManagementVariantsJson = JsonSerializer.Serialize(request.MoneyManagementVariants),
+ TickerVariantsJson = JsonSerializer.Serialize(request.TickerVariants),
+ TotalBacktests = totalBacktests,
CompletedBacktests = 0,
FailedBacktests = 0,
Status = BundleBacktestRequestStatus.Pending,
@@ -491,6 +506,72 @@ public class BacktestController : BaseController
}
}
+ ///
+ /// Generates individual backtest requests from variant configuration
+ ///
+ /// The bundle backtest request
+ /// List of individual backtest requests
+ private List GenerateBacktestRequests(RunBundleBacktestRequest request)
+ {
+ var backtestRequests = new List();
+
+ foreach (var dateRange in request.DateTimeRanges)
+ {
+ foreach (var mmVariant in request.MoneyManagementVariants)
+ {
+ foreach (var ticker in request.TickerVariants)
+ {
+ var config = new TradingBotConfigRequest
+ {
+ AccountName = request.UniversalConfig.AccountName,
+ Ticker = ticker,
+ Timeframe = request.UniversalConfig.Timeframe,
+ IsForWatchingOnly = request.UniversalConfig.IsForWatchingOnly,
+ BotTradingBalance = request.UniversalConfig.BotTradingBalance,
+ Name = $"{request.UniversalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}",
+ FlipPosition = request.UniversalConfig.FlipPosition,
+ CooldownPeriod = request.UniversalConfig.CooldownPeriod,
+ MaxLossStreak = request.UniversalConfig.MaxLossStreak,
+ Scenario = request.UniversalConfig.Scenario,
+ ScenarioName = request.UniversalConfig.ScenarioName,
+ MoneyManagement = mmVariant.MoneyManagement,
+ MaxPositionTimeHours = request.UniversalConfig.MaxPositionTimeHours,
+ CloseEarlyWhenProfitable = request.UniversalConfig.CloseEarlyWhenProfitable,
+ FlipOnlyWhenInProfit = request.UniversalConfig.FlipOnlyWhenInProfit,
+ UseSynthApi = request.UniversalConfig.UseSynthApi,
+ UseForPositionSizing = request.UniversalConfig.UseForPositionSizing,
+ UseForSignalFiltering = request.UniversalConfig.UseForSignalFiltering,
+ UseForDynamicStopLoss = request.UniversalConfig.UseForDynamicStopLoss
+ };
+
+ var backtestRequest = new RunBacktestRequest
+ {
+ Config = config,
+ StartDate = dateRange.StartDate,
+ EndDate = dateRange.EndDate,
+ Balance = request.UniversalConfig.BotTradingBalance,
+ WatchOnly = request.UniversalConfig.WatchOnly,
+ Save = request.UniversalConfig.Save,
+ WithCandles = request.UniversalConfig.WithCandles,
+ MoneyManagement = mmVariant.MoneyManagement != null ?
+ new MoneyManagement
+ {
+ Name = mmVariant.MoneyManagement.Name,
+ Timeframe = mmVariant.MoneyManagement.Timeframe,
+ StopLoss = mmVariant.MoneyManagement.StopLoss,
+ TakeProfit = mmVariant.MoneyManagement.TakeProfit,
+ Leverage = mmVariant.MoneyManagement.Leverage
+ } : null
+ };
+
+ backtestRequests.Add(backtestRequest);
+ }
+ }
+ }
+
+ return backtestRequests;
+ }
+
///
/// Retrieves all bundle backtest requests for the authenticated user.
///
@@ -741,35 +822,3 @@ public class BacktestController : BaseController
};
}
}
-
-///
-/// Request model for running a backtest
-///
-public class RunBacktestRequest
-{
- ///
- /// The trading bot configuration request to use for the backtest
- ///
- public TradingBotConfigRequest Config { get; set; }
-
- ///
- /// The start date for the backtest
- ///
- public DateTime StartDate { get; set; }
-
- ///
- /// The end date for the backtest
- ///
- public DateTime EndDate { get; set; }
-
- ///
- /// Whether to save the backtest results
- ///
- public bool Save { get; set; } = false;
-
- ///
- /// Whether to include candles and indicators values in the response.
- /// Set to false to reduce response size dramatically.
- ///
- public bool WithCandles { get; set; } = false;
-}
\ No newline at end of file
diff --git a/src/Managing.Api/Models/Requests/RunBundleBacktestRequest.cs b/src/Managing.Api/Models/Requests/RunBundleBacktestRequest.cs
index d964f8e7..e34304fc 100644
--- a/src/Managing.Api/Models/Requests/RunBundleBacktestRequest.cs
+++ b/src/Managing.Api/Models/Requests/RunBundleBacktestRequest.cs
@@ -1,11 +1,41 @@
using System.ComponentModel.DataAnnotations;
-using Managing.Api.Controllers;
+using Managing.Domain.Backtests;
+using static Managing.Common.Enums;
namespace Managing.Api.Models.Requests;
+///
+/// Request model for running bundle backtests with variant lists instead of individual requests
+///
public class RunBundleBacktestRequest
{
- [Required] public string Name { get; set; } = string.Empty;
+ ///
+ /// Display name for the bundle backtest
+ ///
+ [Required]
+ public string Name { get; set; } = string.Empty;
- [Required] public List Requests { get; set; } = new();
+ ///
+ /// Universal configuration that applies to all backtests in the bundle
+ ///
+ [Required]
+ public BundleBacktestUniversalConfig UniversalConfig { get; set; } = new();
+
+ ///
+ /// List of DateTime ranges to test (each range will generate a separate backtest)
+ ///
+ [Required]
+ public List DateTimeRanges { get; set; } = new();
+
+ ///
+ /// List of money management configurations to test (each will generate a separate backtest)
+ ///
+ [Required]
+ public List MoneyManagementVariants { get; set; } = new();
+
+ ///
+ /// List of tickers to test (each will generate a separate backtest)
+ ///
+ [Required]
+ public List TickerVariants { get; set; } = new();
}
\ No newline at end of file
diff --git a/src/Managing.Application/Grains/BundleBacktestGrain.cs b/src/Managing.Application/Grains/BundleBacktestGrain.cs
index a4252e29..370810f3 100644
--- a/src/Managing.Application/Grains/BundleBacktestGrain.cs
+++ b/src/Managing.Application/Grains/BundleBacktestGrain.cs
@@ -12,6 +12,7 @@ using Managing.Domain.Strategies;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Orleans.Concurrency;
+using static Managing.Common.Enums;
namespace Managing.Application.Grains;
@@ -103,12 +104,11 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
bundleRequest.Status = BundleBacktestRequestStatus.Running;
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
- // Deserialize the backtest requests as strongly-typed objects
- var backtestRequests =
- JsonSerializer.Deserialize>(bundleRequest.BacktestRequestsJson);
- if (backtestRequests == null)
+ // Generate backtest requests from variant configuration
+ var backtestRequests = GenerateBacktestRequestsFromVariants(bundleRequest);
+ if (backtestRequests == null || !backtestRequests.Any())
{
- throw new InvalidOperationException("Failed to deserialize backtest requests");
+ throw new InvalidOperationException("Failed to generate backtest requests from variants");
}
// Process each backtest request sequentially
@@ -130,6 +130,90 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
}
}
+ ///
+ /// Generates individual backtest requests from variant configuration
+ ///
+ private List GenerateBacktestRequestsFromVariants(BundleBacktestRequest bundleRequest)
+ {
+ try
+ {
+ // Deserialize the variant configurations
+ var universalConfig = JsonSerializer.Deserialize(bundleRequest.UniversalConfigJson);
+ var dateTimeRanges = JsonSerializer.Deserialize>(bundleRequest.DateTimeRangesJson);
+ var moneyManagementVariants = JsonSerializer.Deserialize>(bundleRequest.MoneyManagementVariantsJson);
+ var tickerVariants = JsonSerializer.Deserialize>(bundleRequest.TickerVariantsJson);
+
+ if (universalConfig == null || dateTimeRanges == null || moneyManagementVariants == null || tickerVariants == null)
+ {
+ _logger.LogError("Failed to deserialize variant configurations for bundle request {RequestId}", bundleRequest.RequestId);
+ return new List();
+ }
+
+ var backtestRequests = new List();
+
+ foreach (var dateRange in dateTimeRanges)
+ {
+ foreach (var mmVariant in moneyManagementVariants)
+ {
+ foreach (var ticker in tickerVariants)
+ {
+ var config = new TradingBotConfigRequest
+ {
+ AccountName = universalConfig.AccountName,
+ Ticker = ticker,
+ Timeframe = universalConfig.Timeframe,
+ IsForWatchingOnly = universalConfig.IsForWatchingOnly,
+ BotTradingBalance = universalConfig.BotTradingBalance,
+ Name = $"{universalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}",
+ FlipPosition = universalConfig.FlipPosition,
+ CooldownPeriod = universalConfig.CooldownPeriod,
+ MaxLossStreak = universalConfig.MaxLossStreak,
+ Scenario = universalConfig.Scenario,
+ ScenarioName = universalConfig.ScenarioName,
+ MoneyManagement = mmVariant.MoneyManagement,
+ MaxPositionTimeHours = universalConfig.MaxPositionTimeHours,
+ CloseEarlyWhenProfitable = universalConfig.CloseEarlyWhenProfitable,
+ FlipOnlyWhenInProfit = universalConfig.FlipOnlyWhenInProfit,
+ UseSynthApi = universalConfig.UseSynthApi,
+ UseForPositionSizing = universalConfig.UseForPositionSizing,
+ UseForSignalFiltering = universalConfig.UseForSignalFiltering,
+ UseForDynamicStopLoss = universalConfig.UseForDynamicStopLoss
+ };
+
+ var backtestRequest = new RunBacktestRequest
+ {
+ Config = config,
+ StartDate = dateRange.StartDate,
+ EndDate = dateRange.EndDate,
+ Balance = universalConfig.BotTradingBalance,
+ WatchOnly = universalConfig.WatchOnly,
+ Save = universalConfig.Save,
+ WithCandles = false, // Bundle backtests never return candles
+ MoneyManagement = mmVariant.MoneyManagement != null ?
+ new MoneyManagement
+ {
+ Name = mmVariant.MoneyManagement.Name,
+ Timeframe = mmVariant.MoneyManagement.Timeframe,
+ StopLoss = mmVariant.MoneyManagement.StopLoss,
+ TakeProfit = mmVariant.MoneyManagement.TakeProfit,
+ Leverage = mmVariant.MoneyManagement.Leverage
+ } : null
+ };
+
+ backtestRequests.Add(backtestRequest);
+ }
+ }
+ }
+
+ return backtestRequests;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error generating backtest requests from variants for bundle request {RequestId}", bundleRequest.RequestId);
+ return new List();
+ }
+ }
+
private async Task ProcessSingleBacktest(
IBacktester backtester,
RunBacktestRequest runBacktestRequest,
@@ -138,10 +222,8 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
{
try
{
- // Get total count from deserialized requests instead of string splitting
- var backtestRequests =
- JsonSerializer.Deserialize>(bundleRequest.BacktestRequestsJson);
- var totalCount = backtestRequests?.Count ?? 0;
+ // Calculate total count from the variant configuration
+ var totalCount = bundleRequest.TotalBacktests;
// Update current backtest being processed
bundleRequest.CurrentBacktest = $"Backtest {index + 1} of {totalCount}";
diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs
index 813b25ef..f5e7e90b 100644
--- a/src/Managing.Application/Users/UserService.cs
+++ b/src/Managing.Application/Users/UserService.cs
@@ -64,8 +64,17 @@ public class UserService : IUserService
throw new Exception($"Address {recoveredAddress} not corresponding");
}
+ User user = null;
+
// Check if account exist
- var user = await _userRepository.GetUserByNameAsync(name);
+ try
+ {
+ user = await _userRepository.GetUserByNameAsync(name);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
if (user != null)
{
diff --git a/src/Managing.Application/Workers/BundleBacktestWorker.cs b/src/Managing.Application/Workers/BundleBacktestWorker.cs
index 8d35c06a..0c1d08f1 100644
--- a/src/Managing.Application/Workers/BundleBacktestWorker.cs
+++ b/src/Managing.Application/Workers/BundleBacktestWorker.cs
@@ -93,13 +93,11 @@ public class BundleBacktestWorker : BaseWorker
bundleRequest.Status = BundleBacktestRequestStatus.Running;
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
- // Deserialize the backtest requests as strongly-typed objects
- var backtestRequests =
- JsonSerializer.Deserialize>(
- bundleRequest.BacktestRequestsJson);
- if (backtestRequests == null)
+ // Generate backtest requests from the new variant structure
+ var backtestRequests = GenerateBacktestRequestsFromVariants(bundleRequest);
+ if (backtestRequests == null || !backtestRequests.Any())
{
- throw new InvalidOperationException("Failed to deserialize backtest requests");
+ throw new InvalidOperationException("Failed to generate backtest requests from variants");
}
// Process each backtest request
@@ -298,11 +296,9 @@ public class BundleBacktestWorker : BaseWorker
// Use Results property to determine which backtests need to be retried
var succeededIds = new HashSet(failedBundle.Results ?? new List());
- // Deserialize the original requests
- var originalRequests =
- JsonSerializer
- .Deserialize>(failedBundle.BacktestRequestsJson);
- if (originalRequests == null) continue;
+ // Generate backtest requests from the new variant structure
+ var originalRequests = GenerateBacktestRequestsFromVariants(failedBundle);
+ if (originalRequests == null || !originalRequests.Any()) continue;
for (int i = failedBundle.CompletedBacktests; i < originalRequests.Count; i++)
{
@@ -339,4 +335,88 @@ public class BundleBacktestWorker : BaseWorker
}
}
}
+
+ ///
+ /// Generates individual backtest requests from variant configuration
+ ///
+ private List GenerateBacktestRequestsFromVariants(BundleBacktestRequest bundleRequest)
+ {
+ try
+ {
+ // Deserialize the variant configurations
+ var universalConfig = JsonSerializer.Deserialize(bundleRequest.UniversalConfigJson);
+ var dateTimeRanges = JsonSerializer.Deserialize>(bundleRequest.DateTimeRangesJson);
+ var moneyManagementVariants = JsonSerializer.Deserialize>(bundleRequest.MoneyManagementVariantsJson);
+ var tickerVariants = JsonSerializer.Deserialize>(bundleRequest.TickerVariantsJson);
+
+ if (universalConfig == null || dateTimeRanges == null || moneyManagementVariants == null || tickerVariants == null)
+ {
+ _logger.LogError("Failed to deserialize variant configurations for bundle request {RequestId}", bundleRequest.RequestId);
+ return new List();
+ }
+
+ var backtestRequests = new List();
+
+ foreach (var dateRange in dateTimeRanges)
+ {
+ foreach (var mmVariant in moneyManagementVariants)
+ {
+ foreach (var ticker in tickerVariants)
+ {
+ var config = new TradingBotConfigRequest
+ {
+ AccountName = universalConfig.AccountName,
+ Ticker = ticker,
+ Timeframe = universalConfig.Timeframe,
+ IsForWatchingOnly = universalConfig.IsForWatchingOnly,
+ BotTradingBalance = universalConfig.BotTradingBalance,
+ Name = $"{universalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}",
+ FlipPosition = universalConfig.FlipPosition,
+ CooldownPeriod = universalConfig.CooldownPeriod,
+ MaxLossStreak = universalConfig.MaxLossStreak,
+ Scenario = universalConfig.Scenario,
+ ScenarioName = universalConfig.ScenarioName,
+ MoneyManagement = mmVariant.MoneyManagement,
+ MaxPositionTimeHours = universalConfig.MaxPositionTimeHours,
+ CloseEarlyWhenProfitable = universalConfig.CloseEarlyWhenProfitable,
+ FlipOnlyWhenInProfit = universalConfig.FlipOnlyWhenInProfit,
+ UseSynthApi = universalConfig.UseSynthApi,
+ UseForPositionSizing = universalConfig.UseForPositionSizing,
+ UseForSignalFiltering = universalConfig.UseForSignalFiltering,
+ UseForDynamicStopLoss = universalConfig.UseForDynamicStopLoss
+ };
+
+ var backtestRequest = new RunBacktestRequest
+ {
+ Config = config,
+ StartDate = dateRange.StartDate,
+ EndDate = dateRange.EndDate,
+ Balance = universalConfig.BotTradingBalance,
+ WatchOnly = universalConfig.WatchOnly,
+ Save = universalConfig.Save,
+ WithCandles = false, // Bundle backtests never return candles
+ MoneyManagement = mmVariant.MoneyManagement != null ?
+ new MoneyManagement
+ {
+ Name = mmVariant.MoneyManagement.Name,
+ Timeframe = mmVariant.MoneyManagement.Timeframe,
+ StopLoss = mmVariant.MoneyManagement.StopLoss,
+ TakeProfit = mmVariant.MoneyManagement.TakeProfit,
+ Leverage = mmVariant.MoneyManagement.Leverage
+ } : null
+ };
+
+ backtestRequests.Add(backtestRequest);
+ }
+ }
+ }
+
+ return backtestRequests;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error generating backtest requests from variants for bundle request {RequestId}", bundleRequest.RequestId);
+ return new List();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Managing.Domain/Backtests/BundleBacktestRequest.cs b/src/Managing.Domain/Backtests/BundleBacktestRequest.cs
index f0dcf064..17c8f6cd 100644
--- a/src/Managing.Domain/Backtests/BundleBacktestRequest.cs
+++ b/src/Managing.Domain/Backtests/BundleBacktestRequest.cs
@@ -1,10 +1,11 @@
+#nullable enable
using System.ComponentModel.DataAnnotations;
using Managing.Domain.Users;
namespace Managing.Domain.Backtests;
///
-/// Domain model for bundle backtest requests
+/// Domain model for bundle backtest requests with variant-based configuration
///
public class BundleBacktestRequest
{
@@ -14,7 +15,10 @@ public class BundleBacktestRequest
CreatedAt = DateTime.UtcNow;
Status = BundleBacktestRequestStatus.Pending;
Results = new List();
- BacktestRequestsJson = string.Empty;
+ UniversalConfigJson = string.Empty;
+ DateTimeRangesJson = string.Empty;
+ MoneyManagementVariantsJson = string.Empty;
+ TickerVariantsJson = string.Empty;
}
///
@@ -27,7 +31,10 @@ public class BundleBacktestRequest
CreatedAt = DateTime.UtcNow;
Status = BundleBacktestRequestStatus.Pending;
Results = new List();
- BacktestRequestsJson = string.Empty;
+ UniversalConfigJson = string.Empty;
+ DateTimeRangesJson = string.Empty;
+ MoneyManagementVariantsJson = string.Empty;
+ TickerVariantsJson = string.Empty;
}
///
@@ -66,10 +73,28 @@ public class BundleBacktestRequest
public string Name { get; set; }
///
- /// The list of backtest requests to execute (serialized as JSON)
+ /// The universal configuration that applies to all backtests (serialized as JSON)
///
[Required]
- public string BacktestRequestsJson { get; set; } = string.Empty;
+ public string UniversalConfigJson { get; set; } = string.Empty;
+
+ ///
+ /// The list of DateTime ranges to test (serialized as JSON)
+ ///
+ [Required]
+ public string DateTimeRangesJson { get; set; } = string.Empty;
+
+ ///
+ /// The list of money management variants to test (serialized as JSON)
+ ///
+ [Required]
+ public string MoneyManagementVariantsJson { get; set; } = string.Empty;
+
+ ///
+ /// The list of ticker variants to test (serialized as JSON)
+ ///
+ [Required]
+ public string TickerVariantsJson { get; set; } = string.Empty;
///
/// The results of the bundle backtest execution
@@ -118,6 +143,7 @@ public class BundleBacktestRequest
/// Estimated time remaining in seconds
///
public int? EstimatedTimeRemainingSeconds { get; set; }
+
}
///
diff --git a/src/Managing.Domain/Backtests/BundleBacktestUniversalConfig.cs b/src/Managing.Domain/Backtests/BundleBacktestUniversalConfig.cs
new file mode 100644
index 00000000..22e266c4
--- /dev/null
+++ b/src/Managing.Domain/Backtests/BundleBacktestUniversalConfig.cs
@@ -0,0 +1,118 @@
+#nullable enable
+using System.ComponentModel.DataAnnotations;
+using static Managing.Common.Enums;
+
+namespace Managing.Domain.Backtests;
+
+///
+/// Universal configuration that applies to all backtests in the bundle
+///
+public class BundleBacktestUniversalConfig
+{
+ ///
+ /// The account name to use for all backtests
+ ///
+ [Required]
+ public string AccountName { get; set; } = string.Empty;
+
+ ///
+ /// The timeframe for trading decisions
+ ///
+ [Required]
+ public Timeframe Timeframe { get; set; }
+
+ ///
+ /// Whether this bot is for watching only (no actual trading)
+ ///
+ [Required]
+ public bool IsForWatchingOnly { get; set; }
+
+ ///
+ /// The initial trading balance for the bot
+ ///
+ [Required]
+ public decimal BotTradingBalance { get; set; }
+
+ ///
+ /// The name/identifier for this bot
+ ///
+ [Required]
+ public string BotName { get; set; } = string.Empty;
+
+ ///
+ /// Whether to flip positions
+ ///
+ [Required]
+ public bool FlipPosition { get; set; }
+
+ ///
+ /// Cooldown period between trades (in candles)
+ ///
+ public int? CooldownPeriod { get; set; }
+
+ ///
+ /// Maximum consecutive losses before stopping the bot
+ ///
+ public int MaxLossStreak { get; set; }
+
+ ///
+ /// The scenario configuration (takes precedence over ScenarioName)
+ ///
+ public ScenarioRequest? Scenario { get; set; }
+
+ ///
+ /// The scenario name to load from database (only used when Scenario is not provided)
+ ///
+ public string? ScenarioName { get; set; }
+
+ ///
+ /// Maximum time in hours that a position can remain open before being automatically closed
+ ///
+ public decimal? MaxPositionTimeHours { get; set; }
+
+ ///
+ /// Whether to close positions early when they become profitable
+ ///
+ public bool CloseEarlyWhenProfitable { get; set; } = false;
+
+ ///
+ /// Whether to only flip positions when the current position is in profit
+ ///
+ public bool FlipOnlyWhenInProfit { get; set; } = true;
+
+ ///
+ /// Whether to use Synth API for predictions and risk assessment
+ ///
+ public bool UseSynthApi { get; set; } = false;
+
+ ///
+ /// Whether to use Synth predictions for position sizing adjustments
+ ///
+ public bool UseForPositionSizing { get; set; } = true;
+
+ ///
+ /// Whether to use Synth predictions for signal filtering
+ ///
+ public bool UseForSignalFiltering { get; set; } = true;
+
+ ///
+ /// Whether to use Synth predictions for dynamic stop-loss/take-profit adjustments
+ ///
+ public bool UseForDynamicStopLoss { get; set; } = true;
+
+ ///
+ /// Whether to only watch the backtest without executing trades
+ ///
+ public bool WatchOnly { get; set; } = false;
+
+ ///
+ /// Whether to save the backtest results
+ ///
+ public bool Save { get; set; } = false;
+
+ ///
+ /// Whether to include candles and indicators values in the response.
+ /// Note: This is always ignored for bundle backtests - candles are never returned.
+ ///
+ public bool WithCandles { get; set; } = false;
+}
diff --git a/src/Managing.Domain/Backtests/DateTimeRange.cs b/src/Managing.Domain/Backtests/DateTimeRange.cs
new file mode 100644
index 00000000..cbe595bf
--- /dev/null
+++ b/src/Managing.Domain/Backtests/DateTimeRange.cs
@@ -0,0 +1,22 @@
+#nullable enable
+using System.ComponentModel.DataAnnotations;
+
+namespace Managing.Domain.Backtests;
+
+///
+/// Represents a date range for backtesting
+///
+public class DateTimeRange
+{
+ ///
+ /// The start date for the backtest
+ ///
+ [Required]
+ public DateTime StartDate { get; set; }
+
+ ///
+ /// The end date for the backtest
+ ///
+ [Required]
+ public DateTime EndDate { get; set; }
+}
diff --git a/src/Managing.Domain/Backtests/MoneyManagementVariant.cs b/src/Managing.Domain/Backtests/MoneyManagementVariant.cs
new file mode 100644
index 00000000..4fec86ee
--- /dev/null
+++ b/src/Managing.Domain/Backtests/MoneyManagementVariant.cs
@@ -0,0 +1,13 @@
+#nullable enable
+namespace Managing.Domain.Backtests;
+
+///
+/// Represents a money management variant for backtesting
+///
+public class MoneyManagementVariant
+{
+ ///
+ /// The money management details
+ ///
+ public MoneyManagementRequest MoneyManagement { get; set; } = new();
+}
diff --git a/src/Managing.Infrastructure.Database/Migrations/20251012063850_UpdateBundleBacktestRequestToVariants.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20251012063850_UpdateBundleBacktestRequestToVariants.Designer.cs
new file mode 100644
index 00000000..089d28b8
--- /dev/null
+++ b/src/Managing.Infrastructure.Database/Migrations/20251012063850_UpdateBundleBacktestRequestToVariants.Designer.cs
@@ -0,0 +1,1486 @@
+//
+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("20251012063850_UpdateBundleBacktestRequestToVariants")]
+ partial class UpdateBundleBacktestRequestToVariants
+ {
+ ///
+ 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("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("Metadata")
+ .HasColumnType("text");
+
+ b.Property("MoneyManagementJson")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ 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("SignalsJson")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("StartDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("StatisticsJson")
+ .HasColumnType("jsonb");
+
+ 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", "Score");
+
+ 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("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")
+ .HasColumnType("integer");
+
+ b.Property("TradeLosses")
+ .HasColumnType("integer");
+
+ b.Property("TradeWins")
+ .HasColumnType("integer");
+
+ 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("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.HasKey("Id");
+
+ b.HasIndex("RequestId")
+ .IsUnique();
+
+ b.HasIndex("Status");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("UserId", "CreatedAt");
+
+ 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.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("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