diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index 6332add..3682e95 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -340,16 +340,6 @@ public class DataController : ControllerBase [HttpGet("GetTopStrategies")] public async Task> GetTopStrategies() { - const string cacheKey = "TopStrategies"; - - // Check if the top strategies are already cached - var cachedStrategies = _cacheService.GetValue(cacheKey); - - if (cachedStrategies != null) - { - return Ok(cachedStrategies); - } - // Get active bots var activeBots = await _mediator.Send(new GetBotsByStatusCommand(BotStatus.Running)); @@ -372,12 +362,43 @@ public class DataController : ControllerBase .ToList() }; - // Cache the result for 10 minutes - _cacheService.SaveValue(cacheKey, topStrategies, TimeSpan.FromMinutes(10)); - return Ok(topStrategies); } + /// + /// Retrieves the top 3 performing strategies based on ROI percentage. + /// + /// A containing the top performing strategies by ROI. + [HttpGet("GetTopStrategiesByRoi")] + public async Task> GetTopStrategiesByRoi() + { + // Get active bots + var activeBots = await _mediator.Send(new GetBotsByStatusCommand(BotStatus.Running)); + + // Filter bots with valid ROI data and order by ROI + var botsWithRoi = activeBots + .Select(bot => new { Bot = bot, Roi = bot.Roi, PnL = bot.Pnl, Volume = bot.Volume }) + .OrderByDescending(item => item.Roi) + .Take(3) + .ToList(); + + // Map to view model + var topStrategiesByRoi = new TopStrategiesByRoiViewModel + { + TopStrategiesByRoi = botsWithRoi + .Select(item => new StrategyRoiPerformance + { + StrategyName = item.Bot.Name, + Roi = item.Roi, + PnL = item.PnL, + Volume = item.Volume + }) + .ToList() + }; + + return Ok(topStrategiesByRoi); + } + /// /// Retrieves list of the active strategies for a user with detailed information /// @@ -474,13 +495,13 @@ public class DataController : ControllerBase { // Get the platform summary grain var platformSummaryGrain = _grainFactory.GetGrain("platform-summary"); - + // Get the platform summary from the grain (handles caching and real-time updates) var abstractionsSummary = await platformSummaryGrain.GetPlatformSummaryAsync(); - + // Convert to API ViewModel var summary = abstractionsSummary.ToApiViewModel(); - + return Ok(summary); } catch (Exception ex) diff --git a/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs b/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs index 8e5009a..dfc85f8 100644 --- a/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs +++ b/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs @@ -1,4 +1,5 @@ using Managing.Api.Models.Responses; +using Managing.Common; using AbstractionsPlatformSummaryViewModel = Managing.Application.Abstractions.Models.PlatformSummaryViewModel; namespace Managing.Api.Extensions; @@ -28,13 +29,13 @@ public static class PlatformSummaryExtensions VolumeChange24h = abstractionsModel.VolumeChange24h, OpenInterestChange24h = abstractionsModel.OpenInterestChange24h, PositionCountChange24h = abstractionsModel.PositionCountChange24h, - VolumeByAsset = abstractionsModel.VolumeByAsset ?? new Dictionary(), - PositionCountByAsset = abstractionsModel.PositionCountByAsset ?? new Dictionary(), + VolumeByAsset = abstractionsModel.VolumeByAsset ?? new Dictionary(), + PositionCountByAsset = abstractionsModel.PositionCountByAsset ?? new Dictionary(), PositionCountByDirection = abstractionsModel.PositionCountByDirection?.ToDictionary( - kvp => kvp.Key.ToString(), - kvp => kvp.Value) ?? new Dictionary(), + kvp => kvp.Key, + kvp => kvp.Value) ?? new Dictionary(), LastUpdated = abstractionsModel.LastUpdated, Last24HourSnapshot = abstractionsModel.Last24HourSnapshot }; } -} +} \ No newline at end of file diff --git a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs index 27f9db5..56d970b 100644 --- a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs +++ b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs @@ -1,3 +1,5 @@ +using Managing.Common; + namespace Managing.Api.Models.Responses { /// @@ -9,39 +11,39 @@ namespace Managing.Api.Models.Responses /// AgentName of the agent /// public string AgentName { get; set; } - + /// /// Total profit and loss in USD /// public decimal TotalPnL { get; set; } - + /// /// Total return on investment as a percentage /// public decimal TotalROI { get; set; } - + /// /// Number of winning trades /// public int Wins { get; set; } - + /// /// Number of losing trades /// public int Losses { get; set; } - - + + /// /// Number of active strategies for this agent /// public int ActiveStrategiesCount { get; set; } - + /// /// Total volume traded by this agent in USD /// public decimal TotalVolume { get; set; } } - + /// /// Platform-wide statistics without individual agent details /// @@ -51,96 +53,96 @@ namespace Managing.Api.Models.Responses /// Total number of agents on the platform /// public required int TotalAgents { get; set; } - + /// /// Total number of active strategies across all agents /// public required int TotalActiveStrategies { get; set; } - + /// /// Total platform-wide profit and loss in USD /// public required decimal TotalPlatformPnL { get; set; } - + /// /// Total volume traded across all agents in USD /// public required decimal TotalPlatformVolume { get; set; } - + /// /// Total volume traded across all agents in the last 24 hours in USD /// public required decimal TotalPlatformVolumeLast24h { get; set; } - + /// /// Total open interest across all positions in USD /// public required decimal TotalOpenInterest { get; set; } - + /// /// Total number of open positions across all strategies /// public required int TotalPositionCount { get; set; } - + // 24-hour changes /// /// Change in agent count over the last 24 hours /// public required int AgentsChange24h { get; set; } - + /// /// Change in strategy count over the last 24 hours /// public required int StrategiesChange24h { get; set; } - + /// /// Change in PnL over the last 24 hours /// public required decimal PnLChange24h { get; set; } - + /// /// Change in volume over the last 24 hours /// public required decimal VolumeChange24h { get; set; } - + /// /// Change in open interest over the last 24 hours /// public required decimal OpenInterestChange24h { get; set; } - + /// /// Change in position count over the last 24 hours /// public required int PositionCountChange24h { get; set; } - + // Breakdowns /// /// Volume breakdown by asset/ticker /// - public required Dictionary VolumeByAsset { get; set; } - + public required Dictionary VolumeByAsset { get; set; } + /// /// Position count breakdown by asset/ticker /// - public required Dictionary PositionCountByAsset { get; set; } - + public required Dictionary PositionCountByAsset { get; set; } + /// /// Position count breakdown by direction (Long/Short) /// - public required Dictionary PositionCountByDirection { get; set; } - + public required Dictionary PositionCountByDirection { get; set; } + // Metadata /// /// When the data was last updated /// public required DateTime LastUpdated { get; set; } - + /// /// When the last 24-hour snapshot was taken /// public required DateTime Last24HourSnapshot { get; set; } } - + /// /// Response model containing a list of agent summaries for the agent index /// @@ -150,10 +152,10 @@ namespace Managing.Api.Models.Responses /// List of agent summaries sorted by performance /// public List AgentSummaries { get; set; } = new List(); - + /// /// Time filter applied to the data /// public string TimeFilter { get; set; } = "Total"; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs b/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs index 590b149..03789ac 100644 --- a/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs +++ b/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs @@ -16,6 +16,32 @@ namespace Managing.Api.Models.Responses public decimal PnL { get; set; } } + /// + /// Represents a high-performing strategy with its name and ROI value + /// + public class StrategyRoiPerformance + { + /// + /// Name of the strategy bot + /// + public string StrategyName { get; set; } + + /// + /// Return on Investment percentage of the strategy + /// + public decimal Roi { get; set; } + + /// + /// Profit and Loss value of the strategy + /// + public decimal PnL { get; set; } + + /// + /// Volume traded by the strategy + /// + public decimal Volume { get; set; } + } + /// /// View model containing the top performing strategies by ROI /// @@ -26,4 +52,15 @@ namespace Managing.Api.Models.Responses /// public List TopStrategies { get; set; } = new List(); } + + /// + /// View model containing the top performing strategies by ROI + /// + public class TopStrategiesByRoiViewModel + { + /// + /// List of the top performing strategies by ROI + /// + public List TopStrategiesByRoi { get; set; } = new List(); + } } \ No newline at end of file diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index a218c56..22499fa 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -1904,6 +1904,41 @@ export class DataClient extends AuthorizedApiBase { return Promise.resolve(null as any); } + data_GetTopStrategiesByRoi(): Promise { + let url_ = this.baseUrl + "/Data/GetTopStrategiesByRoi"; + 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_GetTopStrategiesByRoi(_response); + }); + } + + protected processData_GetTopStrategiesByRoi(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 TopStrategiesByRoiViewModel; + 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_GetUserStrategies(agentName: string | null | undefined): Promise { let url_ = this.baseUrl + "/Data/GetUserStrategies?"; if (agentName !== undefined && agentName !== null) @@ -4554,6 +4589,17 @@ export interface StrategyPerformance { pnL?: number; } +export interface TopStrategiesByRoiViewModel { + topStrategiesByRoi?: StrategyRoiPerformance[] | null; +} + +export interface StrategyRoiPerformance { + strategyName?: string | null; + roi?: number; + pnL?: number; + volume?: number; +} + export interface UserStrategyDetailsViewModel { name?: string | null; state?: BotStatus; @@ -4585,9 +4631,9 @@ export interface PlatformSummaryViewModel { volumeChange24h?: number; openInterestChange24h?: number; positionCountChange24h?: number; - volumeByAsset?: { [key: string]: number; } | null; - positionCountByAsset?: { [key: string]: number; } | null; - positionCountByDirection?: { [key: string]: number; } | null; + volumeByAsset?: { [key in keyof typeof Ticker]?: number; } | null; + positionCountByAsset?: { [key in keyof typeof Ticker]?: number; } | null; + positionCountByDirection?: { [key in keyof typeof TradeDirection]?: number; } | null; lastUpdated?: Date; last24HourSnapshot?: Date; } diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index 5df6e7b..5de16f3 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -944,6 +944,17 @@ export interface StrategyPerformance { pnL?: number; } +export interface TopStrategiesByRoiViewModel { + topStrategiesByRoi?: StrategyRoiPerformance[] | null; +} + +export interface StrategyRoiPerformance { + strategyName?: string | null; + roi?: number; + pnL?: number; + volume?: number; +} + export interface UserStrategyDetailsViewModel { name?: string | null; state?: BotStatus; @@ -975,9 +986,9 @@ export interface PlatformSummaryViewModel { volumeChange24h?: number; openInterestChange24h?: number; positionCountChange24h?: number; - volumeByAsset?: { [key: string]: number; } | null; - positionCountByAsset?: { [key: string]: number; } | null; - positionCountByDirection?: { [key: string]: number; } | null; + volumeByAsset?: { [key in keyof typeof Ticker]?: number; } | null; + positionCountByAsset?: { [key in keyof typeof Ticker]?: number; } | null; + positionCountByDirection?: { [key in keyof typeof TradeDirection]?: number; } | null; lastUpdated?: Date; last24HourSnapshot?: Date; } diff --git a/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx b/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx index b7430e2..6561f97 100644 --- a/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx +++ b/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx @@ -1,12 +1,18 @@ import React, {useEffect, useState} from 'react' import useApiUrlStore from '../../app/store/apiStore' -import {DataClient, type PlatformSummaryViewModel, type TopStrategiesViewModel} from '../../generated/ManagingApi' +import { + DataClient, + type PlatformSummaryViewModel, + type TopStrategiesByRoiViewModel, + type TopStrategiesViewModel +} from '../../generated/ManagingApi' function PlatformSummary({ index }: { index: number }) { const { apiUrl } = useApiUrlStore() const [platformData, setPlatformData] = useState(null) const [topStrategies, setTopStrategies] = useState(null) + const [topStrategiesByRoi, setTopStrategiesByRoi] = useState(null) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) @@ -18,13 +24,15 @@ function PlatformSummary({ index }: { index: number }) { const client = new DataClient({}, apiUrl) // Fetch all platform data in parallel - const [platform, top] = await Promise.all([ + const [platform, top, topRoi] = await Promise.all([ client.data_GetPlatformSummary(), - client.data_GetTopStrategies() + client.data_GetTopStrategies(), + client.data_GetTopStrategiesByRoi() ]) setPlatformData(platform) setTopStrategies(top) + setTopStrategiesByRoi(topRoi) } catch (err) { setError('Failed to fetch platform data') console.error('Error fetching platform data:', err) @@ -164,17 +172,17 @@ function PlatformSummary({ index }: { index: number }) { - {/* Top 3 Rising (placeholder - using same data for now) */} + {/* Top 3 Rising (by ROI) */}
📈 -

Top 3 Rising

+

Top 3 by ROI

- {topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => ( + {topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
-
+
{strategy.strategyName?.charAt(0) || 'S'} @@ -183,49 +191,22 @@ function PlatformSummary({ index }: { index: number }) {
{strategy.strategyName || '[Strategy Name]'}
-
📧
+
+ Vol: {formatCurrency(strategy.volume || 0)} +
-
- 🔴 - 1,200 +
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}% +
+
= 0 ? 'text-green-400' : 'text-red-400'}`}> + {(strategy.pnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.pnL || 0)} +
)) || ( -
No rising strategies found
- )} -
-
- - {/* Top 3 Strategies */} -
-
- âš¡ -

Top 3 Strategies

-
-
- {topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => { - // Calculate a mock percentage for display - const percentage = Math.abs((strategy.pnL || 0) / 100 * 10).toFixed(0) - return ( -
-
-
- âš¡ -
-
-
- {strategy.strategyName || '[Strategy Name]'} -
-
-
-
- +{percentage}% -
-
- ) - }) || ( -
No strategies found
+
No ROI data available
)}