Deserialized variant for bundle backtest
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Abstractions.Shared;
|
||||
using Managing.Application.Hubs;
|
||||
@@ -677,75 +678,6 @@ public class BacktestController : BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates individual backtest requests from variant configuration
|
||||
/// </summary>
|
||||
/// <param name="request">The bundle backtest request</param>
|
||||
/// <param name="accountName">The account name to use for all backtests</param>
|
||||
/// <returns>List of individual backtest requests</returns>
|
||||
private List<RunBacktestRequest> GenerateBacktestRequests(RunBundleBacktestRequest request, string accountName)
|
||||
{
|
||||
var backtestRequests = new List<RunBacktestRequest>();
|
||||
|
||||
foreach (var dateRange in request.DateTimeRanges)
|
||||
{
|
||||
foreach (var mmVariant in request.MoneyManagementVariants)
|
||||
{
|
||||
foreach (var ticker in request.TickerVariants)
|
||||
{
|
||||
var config = new TradingBotConfigRequest
|
||||
{
|
||||
AccountName = 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all bundle backtest requests for the authenticated user.
|
||||
/// </summary>
|
||||
@@ -781,7 +713,8 @@ public class BacktestController : BaseController
|
||||
return NotFound($"Bundle backtest request with ID {id} not found or doesn't belong to the current user.");
|
||||
}
|
||||
|
||||
return Ok(bundleRequest);
|
||||
var viewModel = BundleBacktestRequestViewModel.FromDomain(bundleRequest);
|
||||
return Ok(viewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
using Managing.Domain.Backtests;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// View model for bundle backtest requests with deserialized variant lists for frontend consumption
|
||||
/// </summary>
|
||||
public class BundleBacktestRequestViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for the bundle backtest request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Guid RequestId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the request was created
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the request was completed (if completed)
|
||||
/// </summary>
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current status of the bundle backtest request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public BundleBacktestRequestStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the bundle backtest request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The universal configuration that applies to all backtests
|
||||
/// </summary>
|
||||
[Required]
|
||||
public BundleBacktestUniversalConfig UniversalConfig { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of DateTime ranges to test
|
||||
/// </summary>
|
||||
[Required]
|
||||
public List<DateTimeRange> DateTimeRanges { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of money management variants to test
|
||||
/// </summary>
|
||||
[Required]
|
||||
public List<MoneyManagementVariant> MoneyManagementVariants { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of ticker variants to test
|
||||
/// </summary>
|
||||
[Required]
|
||||
public List<Ticker> TickerVariants { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The results of the bundle backtest execution
|
||||
/// </summary>
|
||||
public List<string> Results { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Total number of backtests in the bundle
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int TotalBacktests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of backtests completed so far
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int CompletedBacktests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of backtests that failed
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int FailedBacktests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Progress percentage (0-100)
|
||||
/// </summary>
|
||||
public double ProgressPercentage => TotalBacktests > 0 ? (double)CompletedBacktests / TotalBacktests * 100 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the request failed
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Progress information (JSON serialized)
|
||||
/// </summary>
|
||||
public string? ProgressInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current backtest being processed
|
||||
/// </summary>
|
||||
public string? CurrentBacktest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Estimated time remaining in seconds
|
||||
/// </summary>
|
||||
public int? EstimatedTimeRemainingSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps a domain model to a view model by deserializing JSON fields
|
||||
/// </summary>
|
||||
/// <param name="request">The domain bundle backtest request</param>
|
||||
/// <returns>A view model with deserialized lists</returns>
|
||||
public static BundleBacktestRequestViewModel FromDomain(BundleBacktestRequest request)
|
||||
{
|
||||
var viewModel = new BundleBacktestRequestViewModel
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
CreatedAt = request.CreatedAt,
|
||||
CompletedAt = request.CompletedAt,
|
||||
Status = request.Status,
|
||||
Name = request.Name,
|
||||
Results = request.Results,
|
||||
TotalBacktests = request.TotalBacktests,
|
||||
CompletedBacktests = request.CompletedBacktests,
|
||||
FailedBacktests = request.FailedBacktests,
|
||||
ErrorMessage = request.ErrorMessage,
|
||||
ProgressInfo = request.ProgressInfo,
|
||||
CurrentBacktest = request.CurrentBacktest,
|
||||
EstimatedTimeRemainingSeconds = request.EstimatedTimeRemainingSeconds
|
||||
};
|
||||
|
||||
// Deserialize UniversalConfig
|
||||
if (!string.IsNullOrEmpty(request.UniversalConfigJson))
|
||||
{
|
||||
viewModel.UniversalConfig = JsonSerializer.Deserialize<BundleBacktestUniversalConfig>(request.UniversalConfigJson)
|
||||
?? new BundleBacktestUniversalConfig();
|
||||
}
|
||||
|
||||
// Deserialize DateTimeRanges
|
||||
if (!string.IsNullOrEmpty(request.DateTimeRangesJson))
|
||||
{
|
||||
viewModel.DateTimeRanges = JsonSerializer.Deserialize<List<DateTimeRange>>(request.DateTimeRangesJson)
|
||||
?? new List<DateTimeRange>();
|
||||
}
|
||||
|
||||
// Deserialize MoneyManagementVariants
|
||||
if (!string.IsNullOrEmpty(request.MoneyManagementVariantsJson))
|
||||
{
|
||||
viewModel.MoneyManagementVariants = JsonSerializer.Deserialize<List<MoneyManagementVariant>>(request.MoneyManagementVariantsJson)
|
||||
?? new List<MoneyManagementVariant>();
|
||||
}
|
||||
|
||||
// Deserialize TickerVariants
|
||||
if (!string.IsNullOrEmpty(request.TickerVariantsJson))
|
||||
{
|
||||
viewModel.TickerVariants = JsonSerializer.Deserialize<List<Ticker>>(request.TickerVariantsJson)
|
||||
?? new List<Ticker>();
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,23 +12,23 @@ public class WebhookService : IWebhookService
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<WebhookService> _logger;
|
||||
private readonly string _n8nWebhookUrl;
|
||||
|
||||
public WebhookService(HttpClient httpClient, IConfiguration configuration, ILogger<WebhookService> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_n8nWebhookUrl = _configuration["N8n:WebhookUrl"] ?? string.Empty;
|
||||
}
|
||||
|
||||
public async Task SendTradeNotification(User user, string message, bool isBadBehavior = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the n8n webhook URL from configuration
|
||||
var webhookUrl = _configuration["N8n:WebhookUrl"];
|
||||
if (string.IsNullOrEmpty(webhookUrl))
|
||||
if (string.IsNullOrEmpty(user.TelegramChannel))
|
||||
{
|
||||
_logger.LogWarning("N8n webhook URL not configured, skipping webhook notification");
|
||||
_logger.LogWarning("No telegram channel configured");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class WebhookService : IWebhookService
|
||||
};
|
||||
|
||||
// Send the webhook notification
|
||||
var response = await _httpClient.PostAsJsonAsync(webhookUrl, payload);
|
||||
var response = await _httpClient.PostAsJsonAsync(_n8nWebhookUrl, payload);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -64,11 +64,9 @@ public class WebhookService : IWebhookService
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the n8n webhook URL from configuration
|
||||
var webhookUrl = _configuration["N8n:WebhookUrl"];
|
||||
if (string.IsNullOrEmpty(webhookUrl))
|
||||
if (string.IsNullOrEmpty(telegramChannel))
|
||||
{
|
||||
_logger.LogWarning("N8n webhook URL not configured, skipping webhook message");
|
||||
_logger.LogWarning("No telegram channel configured");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,7 +80,7 @@ public class WebhookService : IWebhookService
|
||||
};
|
||||
|
||||
// Send the webhook notification
|
||||
var response = await _httpClient.PostAsJsonAsync(webhookUrl, payload);
|
||||
var response = await _httpClient.PostAsJsonAsync(_n8nWebhookUrl, payload);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
|
||||
@@ -872,7 +872,7 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<BundleBacktestRequest>(null as any);
|
||||
}
|
||||
|
||||
backtest_GetBundleBacktestRequests(): Promise<BundleBacktestRequest[]> {
|
||||
backtest_GetBundleBacktestRequests(): Promise<BundleBacktestRequestViewModel[]> {
|
||||
let url_ = this.baseUrl + "/Backtest/Bundle";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -890,13 +890,13 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_GetBundleBacktestRequests(response: Response): Promise<BundleBacktestRequest[]> {
|
||||
protected processBacktest_GetBundleBacktestRequests(response: Response): Promise<BundleBacktestRequestViewModel[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BundleBacktestRequest[];
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BundleBacktestRequestViewModel[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -904,10 +904,10 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<BundleBacktestRequest[]>(null as any);
|
||||
return Promise.resolve<BundleBacktestRequestViewModel[]>(null as any);
|
||||
}
|
||||
|
||||
backtest_GetBundleBacktestRequest(id: string): Promise<BundleBacktestRequest> {
|
||||
backtest_GetBundleBacktestRequest(id: string): Promise<BundleBacktestRequestViewModel> {
|
||||
let url_ = this.baseUrl + "/Backtest/Bundle/{id}";
|
||||
if (id === undefined || id === null)
|
||||
throw new Error("The parameter 'id' must be defined.");
|
||||
@@ -928,13 +928,13 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_GetBundleBacktestRequest(response: Response): Promise<BundleBacktestRequest> {
|
||||
protected processBacktest_GetBundleBacktestRequest(response: Response): Promise<BundleBacktestRequestViewModel> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BundleBacktestRequest;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BundleBacktestRequestViewModel;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -942,7 +942,7 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<BundleBacktestRequest>(null as any);
|
||||
return Promise.resolve<BundleBacktestRequestViewModel>(null as any);
|
||||
}
|
||||
|
||||
backtest_DeleteBundleBacktestRequest(id: string): Promise<FileResponse> {
|
||||
@@ -4536,6 +4536,27 @@ export interface MoneyManagementVariant {
|
||||
moneyManagement?: MoneyManagementRequest;
|
||||
}
|
||||
|
||||
export interface BundleBacktestRequestViewModel {
|
||||
requestId: string;
|
||||
createdAt: Date;
|
||||
completedAt?: Date | null;
|
||||
status: BundleBacktestRequestStatus;
|
||||
name: string;
|
||||
universalConfig: BundleBacktestUniversalConfig;
|
||||
dateTimeRanges: DateTimeRange[];
|
||||
moneyManagementVariants: MoneyManagementVariant[];
|
||||
tickerVariants: Ticker[];
|
||||
results?: string[];
|
||||
totalBacktests: number;
|
||||
completedBacktests: number;
|
||||
failedBacktests: number;
|
||||
progressPercentage?: number;
|
||||
errorMessage?: string | null;
|
||||
progressInfo?: string | null;
|
||||
currentBacktest?: string | null;
|
||||
estimatedTimeRemainingSeconds?: number | null;
|
||||
}
|
||||
|
||||
export interface GeneticRequest {
|
||||
requestId: string;
|
||||
user: User;
|
||||
|
||||
@@ -711,6 +711,27 @@ export interface MoneyManagementVariant {
|
||||
moneyManagement?: MoneyManagementRequest;
|
||||
}
|
||||
|
||||
export interface BundleBacktestRequestViewModel {
|
||||
requestId: string;
|
||||
createdAt: Date;
|
||||
completedAt?: Date | null;
|
||||
status: BundleBacktestRequestStatus;
|
||||
name: string;
|
||||
universalConfig: BundleBacktestUniversalConfig;
|
||||
dateTimeRanges: DateTimeRange[];
|
||||
moneyManagementVariants: MoneyManagementVariant[];
|
||||
tickerVariants: Ticker[];
|
||||
results?: string[];
|
||||
totalBacktests: number;
|
||||
completedBacktests: number;
|
||||
failedBacktests: number;
|
||||
progressPercentage?: number;
|
||||
errorMessage?: string | null;
|
||||
progressInfo?: string | null;
|
||||
currentBacktest?: string | null;
|
||||
estimatedTimeRemainingSeconds?: number | null;
|
||||
}
|
||||
|
||||
export interface GeneticRequest {
|
||||
requestId: string;
|
||||
user: User;
|
||||
|
||||
Reference in New Issue
Block a user