diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index 8778a237..1aa72375 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -365,7 +365,7 @@ public class BotController : BaseController string? name = null, string? ticker = null, string? agentName = null, - string sortBy = "CreateDate", + BotSortableColumn sortBy = BotSortableColumn.CreateDate, string sortDirection = "Desc") { try @@ -456,6 +456,7 @@ public class BotController : BaseController ? item.TradeWins / (item.TradeWins + item.TradeLosses) : 0, ProfitAndLoss = item.Pnl, + Roi = item.Roi, Identifier = item.Identifier.ToString(), AgentName = item.User.AgentName, CreateDate = item.CreateDate, diff --git a/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs b/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs index 976a062c..05c83de3 100644 --- a/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs +++ b/src/Managing.Api/Models/Requests/GetBotsPaginatedRequest.cs @@ -43,10 +43,10 @@ public class GetBotsPaginatedRequest public string? AgentName { get; set; } /// - /// Sort field. Valid values: "Name", "Ticker", "Status", "CreateDate", "StartupTime", "Pnl", "WinRate", "AgentName". - /// Default is "CreateDate". + /// Sort field as enum to restrict allowed values. + /// Default is CreateDate. /// - public string SortBy { get; set; } = "CreateDate"; + public BotSortableColumn SortBy { get; set; } = BotSortableColumn.CreateDate; /// /// Sort direction. Default is "Desc" (descending). diff --git a/src/Managing.Api/Models/Responses/TradingBotResponse.cs b/src/Managing.Api/Models/Responses/TradingBotResponse.cs index b0bdc276..a5025f3f 100644 --- a/src/Managing.Api/Models/Responses/TradingBotResponse.cs +++ b/src/Managing.Api/Models/Responses/TradingBotResponse.cs @@ -44,6 +44,12 @@ namespace Managing.Api.Models.Responses [Required] public decimal ProfitAndLoss { get; internal set; } + /// + /// Current return on investment percentage + /// + [Required] + public decimal Roi { get; internal set; } + /// /// Unique identifier for the bot /// diff --git a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs index 82d902cb..942e6f57 100644 --- a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs @@ -35,7 +35,7 @@ public interface IBotRepository string? name = null, string? ticker = null, string? agentName = null, - string sortBy = "CreateDate", + BotSortableColumn sortBy = BotSortableColumn.CreateDate, string sortDirection = "Desc"); /// diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index 27615e98..0bb8e5e1 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -55,7 +55,7 @@ public interface IBotService string? name = null, string? ticker = null, string? agentName = null, - string sortBy = "CreateDate", + BotSortableColumn sortBy = BotSortableColumn.CreateDate, string sortDirection = "Desc"); /// diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 74cd937e..76768b4e 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -451,7 +451,7 @@ namespace Managing.Application.ManageBot string? name = null, string? ticker = null, string? agentName = null, - string sortBy = "CreateDate", + BotSortableColumn sortBy = BotSortableColumn.CreateDate, string sortDirection = "Desc") { return await ServiceScopeHelpers.WithScopedService Bots, int TotalCount)>( diff --git a/src/Managing.Common/Enums.cs b/src/Managing.Common/Enums.cs index 63f2204e..e28f766b 100644 --- a/src/Managing.Common/Enums.cs +++ b/src/Managing.Common/Enums.cs @@ -81,6 +81,22 @@ public static class Enums Running, } + /// + /// Sortable columns for bots pagination endpoints + /// + public enum BotSortableColumn + { + CreateDate, + Name, + Ticker, + Status, + StartupTime, + Roi, + Pnl, + WinRate, + AgentName + } + public enum SignalStatus { WaitingForPosition, diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs index b7e70089..3046e8c9 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs @@ -170,7 +170,7 @@ public class PostgreSqlBotRepository : IBotRepository string? name = null, string? ticker = null, string? agentName = null, - string sortBy = "CreateDate", + BotSortableColumn sortBy = BotSortableColumn.CreateDate, string sortDirection = "Desc") { // Build the query with filters @@ -204,32 +204,35 @@ public class PostgreSqlBotRepository : IBotRepository var totalCount = await query.CountAsync().ConfigureAwait(false); // Apply sorting - query = sortBy.ToLower() switch + query = sortBy switch { - "name" => sortDirection.ToLower() == "asc" + BotSortableColumn.Name => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Name) : query.OrderByDescending(b => b.Name), - "ticker" => sortDirection.ToLower() == "asc" + BotSortableColumn.Ticker => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Ticker) : query.OrderByDescending(b => b.Ticker), - "status" => sortDirection.ToLower() == "asc" + BotSortableColumn.Status => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Status) : query.OrderByDescending(b => b.Status), - "startuptime" => sortDirection.ToLower() == "asc" + BotSortableColumn.StartupTime => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.StartupTime) : query.OrderByDescending(b => b.StartupTime), - "pnl" => sortDirection.ToLower() == "asc" + BotSortableColumn.Roi => sortDirection.ToLower() == "asc" + ? query.OrderBy(b => b.Roi) + : query.OrderByDescending(b => b.Roi), + BotSortableColumn.Pnl => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Pnl) : query.OrderByDescending(b => b.Pnl), - "winrate" => sortDirection.ToLower() == "asc" + BotSortableColumn.WinRate => sortDirection.ToLower() == "asc" ? query.OrderBy(b => (b.TradeWins + b.TradeLosses) > 0 ? (double)b.TradeWins / (b.TradeWins + b.TradeLosses) : 0) : query.OrderByDescending(b => (b.TradeWins + b.TradeLosses) > 0 ? (double)b.TradeWins / (b.TradeWins + b.TradeLosses) : 0), - "agentname" => sortDirection.ToLower() == "asc" + BotSortableColumn.AgentName => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.User.AgentName) : query.OrderByDescending(b => b.User.AgentName), _ => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.CreateDate) - : query.OrderByDescending(b => b.CreateDate) // Default to CreateDate + : query.OrderByDescending(b => b.CreateDate) }; // Apply pagination diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index 1aa6be4f..3ede22e2 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -1534,7 +1534,7 @@ export class BotClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - bot_GetBotsPaginated(pageNumber: number | undefined, pageSize: number | undefined, status: BotStatus | null | undefined, name: string | null | undefined, ticker: string | null | undefined, agentName: string | null | undefined, sortBy: string | null | undefined, sortDirection: string | null | undefined): Promise { + bot_GetBotsPaginated(pageNumber: number | undefined, pageSize: number | undefined, status: BotStatus | null | undefined, name: string | null | undefined, ticker: string | null | undefined, agentName: string | null | undefined, sortBy: BotSortableColumn | undefined, sortDirection: string | null | undefined): Promise { let url_ = this.baseUrl + "/Bot/Paginated?"; if (pageNumber === null) throw new Error("The parameter 'pageNumber' cannot be null."); @@ -1552,7 +1552,9 @@ export class BotClient extends AuthorizedApiBase { url_ += "ticker=" + encodeURIComponent("" + ticker) + "&"; if (agentName !== undefined && agentName !== null) url_ += "agentName=" + encodeURIComponent("" + agentName) + "&"; - if (sortBy !== undefined && sortBy !== null) + if (sortBy === null) + throw new Error("The parameter 'sortBy' cannot be null."); + else if (sortBy !== undefined) url_ += "sortBy=" + encodeURIComponent("" + sortBy) + "&"; if (sortDirection !== undefined && sortDirection !== null) url_ += "sortDirection=" + encodeURIComponent("" + sortDirection) + "&"; @@ -2179,55 +2181,6 @@ export class DataClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - data_GetBestAgents(startDate: Date | undefined, endDate: Date | null | undefined, page: number | undefined, pageSize: number | undefined): Promise { - let url_ = this.baseUrl + "/Data/GetBestAgents?"; - if (startDate === null) - throw new Error("The parameter 'startDate' cannot be null."); - else if (startDate !== undefined) - url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&"; - if (endDate !== undefined && endDate !== null) - url_ += "endDate=" + encodeURIComponent(endDate ? "" + endDate.toISOString() : "") + "&"; - if (page === null) - throw new Error("The parameter 'page' cannot be null."); - else if (page !== undefined) - url_ += "page=" + encodeURIComponent("" + page) + "&"; - if (pageSize === null) - throw new Error("The parameter 'pageSize' cannot be null."); - else if (pageSize !== undefined) - url_ += "pageSize=" + encodeURIComponent("" + pageSize) + "&"; - url_ = url_.replace(/[?&]$/, ""); - - let options_: RequestInit = { - method: "GET", - headers: { - "Accept": "application/json" - } - }; - - return this.transformOptions(options_).then(transformedOptions_ => { - return this.http.fetch(url_, transformedOptions_); - }).then((_response: Response) => { - return this.processData_GetBestAgents(_response); - }); - } - - protected processData_GetBestAgents(response: Response): Promise { - 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 BestAgentsResponse; - return result200; - }); - } else if (status !== 200 && status !== 204) { - return response.text().then((_responseText) => { - return throwException("An unexpected server error occurred.", status, _responseText, _headers); - }); - } - return Promise.resolve(null as any); - } - data_GetOnlineAgent(): Promise { let url_ = this.baseUrl + "/Data/GetOnlineAgent"; url_ = url_.replace(/[?&]$/, ""); @@ -4288,6 +4241,17 @@ export interface PaginatedResponseOfTradingBotResponse { hasNextPage?: boolean; } +export enum BotSortableColumn { + CreateDate = "CreateDate", + Name = "Name", + Ticker = "Ticker", + Status = "Status", + StartupTime = "StartupTime", + Pnl = "Pnl", + WinRate = "WinRate", + AgentName = "AgentName", +} + export interface OpenPositionManuallyRequest { identifier?: string; direction?: TradeDirection; @@ -4607,14 +4571,6 @@ export interface AgentBalance { time?: Date; } -export interface BestAgentsResponse { - agents?: AgentBalanceHistory[] | null; - totalCount?: number; - currentPage?: number; - pageSize?: number; - totalPages?: number; -} - export interface ScenarioViewModel { name: string; indicators: IndicatorViewModel[]; diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index 8b6dadcd..87c23a15 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -771,6 +771,17 @@ export interface PaginatedResponseOfTradingBotResponse { hasNextPage?: boolean; } +export enum BotSortableColumn { + CreateDate = "CreateDate", + Name = "Name", + Ticker = "Ticker", + Status = "Status", + StartupTime = "StartupTime", + Pnl = "Pnl", + WinRate = "WinRate", + AgentName = "AgentName", +} + export interface OpenPositionManuallyRequest { identifier?: string; direction?: TradeDirection; @@ -1090,14 +1101,6 @@ export interface AgentBalance { time?: Date; } -export interface BestAgentsResponse { - agents?: AgentBalanceHistory[] | null; - totalCount?: number; - currentPage?: number; - pageSize?: number; - totalPages?: number; -} - export interface ScenarioViewModel { name: string; indicators: IndicatorViewModel[];