Update plateform summary

This commit is contained in:
2025-08-15 06:54:09 +07:00
parent e6c3ec139a
commit 0a4a4e1398
7 changed files with 202 additions and 103 deletions

View File

@@ -340,16 +340,6 @@ public class DataController : ControllerBase
[HttpGet("GetTopStrategies")]
public async Task<ActionResult<TopStrategiesViewModel>> GetTopStrategies()
{
const string cacheKey = "TopStrategies";
// Check if the top strategies are already cached
var cachedStrategies = _cacheService.GetValue<TopStrategiesViewModel>(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);
}
/// <summary>
/// Retrieves the top 3 performing strategies based on ROI percentage.
/// </summary>
/// <returns>A <see cref="TopStrategiesByRoiViewModel"/> containing the top performing strategies by ROI.</returns>
[HttpGet("GetTopStrategiesByRoi")]
public async Task<ActionResult<TopStrategiesByRoiViewModel>> 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);
}
/// <summary>
/// Retrieves list of the active strategies for a user with detailed information
/// </summary>

View File

@@ -1,4 +1,5 @@
using Managing.Api.Models.Responses;
using Managing.Common;
using AbstractionsPlatformSummaryViewModel = Managing.Application.Abstractions.Models.PlatformSummaryViewModel;
namespace Managing.Api.Extensions;
@@ -28,11 +29,11 @@ public static class PlatformSummaryExtensions
VolumeChange24h = abstractionsModel.VolumeChange24h,
OpenInterestChange24h = abstractionsModel.OpenInterestChange24h,
PositionCountChange24h = abstractionsModel.PositionCountChange24h,
VolumeByAsset = abstractionsModel.VolumeByAsset ?? new Dictionary<string, decimal>(),
PositionCountByAsset = abstractionsModel.PositionCountByAsset ?? new Dictionary<string, int>(),
VolumeByAsset = abstractionsModel.VolumeByAsset ?? new Dictionary<Enums.Ticker, decimal>(),
PositionCountByAsset = abstractionsModel.PositionCountByAsset ?? new Dictionary<Enums.Ticker, int>(),
PositionCountByDirection = abstractionsModel.PositionCountByDirection?.ToDictionary(
kvp => kvp.Key.ToString(),
kvp => kvp.Value) ?? new Dictionary<string, int>(),
kvp => kvp.Key,
kvp => kvp.Value) ?? new Dictionary<Enums.TradeDirection, int>(),
LastUpdated = abstractionsModel.LastUpdated,
Last24HourSnapshot = abstractionsModel.Last24HourSnapshot
};

View File

@@ -1,3 +1,5 @@
using Managing.Common;
namespace Managing.Api.Models.Responses
{
/// <summary>
@@ -117,17 +119,17 @@ namespace Managing.Api.Models.Responses
/// <summary>
/// Volume breakdown by asset/ticker
/// </summary>
public required Dictionary<string, decimal> VolumeByAsset { get; set; }
public required Dictionary<Enums.Ticker, decimal> VolumeByAsset { get; set; }
/// <summary>
/// Position count breakdown by asset/ticker
/// </summary>
public required Dictionary<string, int> PositionCountByAsset { get; set; }
public required Dictionary<Enums.Ticker, int> PositionCountByAsset { get; set; }
/// <summary>
/// Position count breakdown by direction (Long/Short)
/// </summary>
public required Dictionary<string, int> PositionCountByDirection { get; set; }
public required Dictionary<Enums.TradeDirection, int> PositionCountByDirection { get; set; }
// Metadata
/// <summary>

View File

@@ -16,6 +16,32 @@ namespace Managing.Api.Models.Responses
public decimal PnL { get; set; }
}
/// <summary>
/// Represents a high-performing strategy with its name and ROI value
/// </summary>
public class StrategyRoiPerformance
{
/// <summary>
/// Name of the strategy bot
/// </summary>
public string StrategyName { get; set; }
/// <summary>
/// Return on Investment percentage of the strategy
/// </summary>
public decimal Roi { get; set; }
/// <summary>
/// Profit and Loss value of the strategy
/// </summary>
public decimal PnL { get; set; }
/// <summary>
/// Volume traded by the strategy
/// </summary>
public decimal Volume { get; set; }
}
/// <summary>
/// View model containing the top performing strategies by ROI
/// </summary>
@@ -26,4 +52,15 @@ namespace Managing.Api.Models.Responses
/// </summary>
public List<StrategyPerformance> TopStrategies { get; set; } = new List<StrategyPerformance>();
}
/// <summary>
/// View model containing the top performing strategies by ROI
/// </summary>
public class TopStrategiesByRoiViewModel
{
/// <summary>
/// List of the top performing strategies by ROI
/// </summary>
public List<StrategyRoiPerformance> TopStrategiesByRoi { get; set; } = new List<StrategyRoiPerformance>();
}
}

View File

@@ -1904,6 +1904,41 @@ export class DataClient extends AuthorizedApiBase {
return Promise.resolve<TopStrategiesViewModel>(null as any);
}
data_GetTopStrategiesByRoi(): Promise<TopStrategiesByRoiViewModel> {
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<TopStrategiesByRoiViewModel> {
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<TopStrategiesByRoiViewModel>(null as any);
}
data_GetUserStrategies(agentName: string | null | undefined): Promise<UserStrategyDetailsViewModel[]> {
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;
}

View File

@@ -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;
}

View File

@@ -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<PlatformSummaryViewModel | null>(null)
const [topStrategies, setTopStrategies] = useState<TopStrategiesViewModel | null>(null)
const [topStrategiesByRoi, setTopStrategiesByRoi] = useState<TopStrategiesByRoiViewModel | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(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 }) {
</div>
</div>
{/* Top 3 Rising (placeholder - using same data for now) */}
{/* Top 3 Rising (by ROI) */}
<div className="bg-base-200 rounded-lg p-6">
<div className="flex items-center gap-2 mb-4">
<span className="text-2xl">📈</span>
<h3 className="text-lg font-semibold text-gray-400">Top 3 Rising</h3>
<h3 className="text-lg font-semibold text-gray-400">Top 3 by ROI</h3>
</div>
<div className="space-y-3">
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
<div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<span className="text-xs font-bold text-white">
{strategy.strategyName?.charAt(0) || 'S'}
</span>
@@ -183,49 +191,22 @@ function PlatformSummary({ index }: { index: number }) {
<div className="text-sm text-white font-medium">
{strategy.strategyName || '[Strategy Name]'}
</div>
<div className="text-xs text-gray-400">📧</div>
<div className="text-xs text-gray-400">
Vol: {formatCurrency(strategy.volume || 0)}
</div>
</div>
<div className="flex items-center gap-1">
<span className="text-red-500">🔴</span>
<span className="text-sm text-white">1,200</span>
</div>
<div className="text-right">
<div className={`text-sm font-bold ${(strategy.roi || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
{(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}%
</div>
<div className={`text-xs ${(strategy.pnL || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{(strategy.pnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.pnL || 0)}
</div>
</div>
</div>
)) || (
<div className="text-gray-500 text-sm">No rising strategies found</div>
)}
</div>
</div>
{/* Top 3 Strategies */}
<div className="bg-base-200 rounded-lg p-6">
<div className="flex items-center gap-2 mb-4">
<span className="text-2xl"></span>
<h3 className="text-lg font-semibold text-gray-400">Top 3 Strategies</h3>
</div>
<div className="space-y-3">
{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 (
<div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
<span className="text-xs font-bold text-black"></span>
</div>
<div>
<div className="text-sm text-white font-medium">
{strategy.strategyName || '[Strategy Name]'}
</div>
</div>
</div>
<div className="text-green-500 text-sm font-bold">
+{percentage}%
</div>
</div>
)
}) || (
<div className="text-gray-500 text-sm">No strategies found</div>
<div className="text-gray-500 text-sm">No ROI data available</div>
)}
</div>
</div>