Update plateform summary
This commit is contained in:
@@ -459,19 +459,11 @@ public class DataController : ControllerBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a summary of platform activity across all agents (platform-level data only)
|
/// Retrieves a summary of platform activity across all agents (platform-level data only)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
|
|
||||||
/// <returns>A summary of platform activity without individual agent details</returns>
|
/// <returns>A summary of platform activity without individual agent details</returns>
|
||||||
[HttpGet("GetPlatformSummary")]
|
[HttpGet("GetPlatformSummary")]
|
||||||
public async Task<ActionResult<PlatformSummaryViewModel>> GetPlatformSummary(string timeFilter = "Total")
|
public async Task<ActionResult<PlatformSummaryViewModel>> GetPlatformSummary()
|
||||||
{
|
{
|
||||||
// Validate time filter
|
const string cacheKey = "PlatformSummary";
|
||||||
var validTimeFilters = new[] { "24H", "3D", "1W", "1M", "1Y", "Total" };
|
|
||||||
if (!validTimeFilters.Contains(timeFilter))
|
|
||||||
{
|
|
||||||
timeFilter = "Total"; // Default to Total if invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
string cacheKey = $"PlatformSummary_{timeFilter}";
|
|
||||||
|
|
||||||
// Check if the platform summary is already cached
|
// Check if the platform summary is already cached
|
||||||
var cachedSummary = _cacheService.GetValue<PlatformSummaryViewModel>(cacheKey);
|
var cachedSummary = _cacheService.GetValue<PlatformSummaryViewModel>(cacheKey);
|
||||||
@@ -481,15 +473,14 @@ public class DataController : ControllerBase
|
|||||||
return Ok(cachedSummary);
|
return Ok(cachedSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all agents and their strategies
|
// Get all agents and their strategies (without time filter)
|
||||||
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter));
|
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand());
|
||||||
|
|
||||||
// Create the platform summary
|
// Create the platform summary
|
||||||
var summary = new PlatformSummaryViewModel
|
var summary = new PlatformSummaryViewModel
|
||||||
{
|
{
|
||||||
TotalAgents = agentsWithStrategies.Count,
|
TotalAgents = agentsWithStrategies.Count,
|
||||||
TotalActiveStrategies = agentsWithStrategies.Values.Sum(list => list.Count),
|
TotalActiveStrategies = agentsWithStrategies.Values.Sum(list => list.Count)
|
||||||
TimeFilter = timeFilter
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate total platform metrics
|
// Calculate total platform metrics
|
||||||
|
|||||||
@@ -71,11 +71,6 @@ namespace Managing.Api.Models.Responses
|
|||||||
/// Total volume traded across all agents in the last 24 hours in USD
|
/// Total volume traded across all agents in the last 24 hours in USD
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal TotalPlatformVolumeLast24h { get; set; }
|
public decimal TotalPlatformVolumeLast24h { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time filter applied to the data
|
|
||||||
/// </summary>
|
|
||||||
public string TimeFilter { get; set; } = "Total";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1980,10 +1980,8 @@ export class DataClient extends AuthorizedApiBase {
|
|||||||
return Promise.resolve<UserStrategyDetailsViewModel>(null as any);
|
return Promise.resolve<UserStrategyDetailsViewModel>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
data_GetPlatformSummary(timeFilter: string | null | undefined): Promise<PlatformSummaryViewModel> {
|
data_GetPlatformSummary(): Promise<PlatformSummaryViewModel> {
|
||||||
let url_ = this.baseUrl + "/Data/GetPlatformSummary?";
|
let url_ = this.baseUrl + "/Data/GetPlatformSummary";
|
||||||
if (timeFilter !== undefined && timeFilter !== null)
|
|
||||||
url_ += "timeFilter=" + encodeURIComponent("" + timeFilter) + "&";
|
|
||||||
url_ = url_.replace(/[?&]$/, "");
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
let options_: RequestInit = {
|
let options_: RequestInit = {
|
||||||
@@ -4579,7 +4577,6 @@ export interface PlatformSummaryViewModel {
|
|||||||
totalPlatformPnL?: number;
|
totalPlatformPnL?: number;
|
||||||
totalPlatformVolume?: number;
|
totalPlatformVolume?: number;
|
||||||
totalPlatformVolumeLast24h?: number;
|
totalPlatformVolumeLast24h?: number;
|
||||||
timeFilter?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginatedAgentIndexResponse {
|
export interface PaginatedAgentIndexResponse {
|
||||||
|
|||||||
@@ -967,7 +967,6 @@ export interface PlatformSummaryViewModel {
|
|||||||
totalPlatformPnL?: number;
|
totalPlatformPnL?: number;
|
||||||
totalPlatformVolume?: number;
|
totalPlatformVolume?: number;
|
||||||
totalPlatformVolumeLast24h?: number;
|
totalPlatformVolumeLast24h?: number;
|
||||||
timeFilter?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginatedAgentIndexResponse {
|
export interface PaginatedAgentIndexResponse {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import BestAgents from './analytics/bestAgents'
|
|||||||
import AgentSearch from './agentSearch'
|
import AgentSearch from './agentSearch'
|
||||||
import AgentIndex from './agentIndex'
|
import AgentIndex from './agentIndex'
|
||||||
import AgentStrategy from './agentStrategy'
|
import AgentStrategy from './agentStrategy'
|
||||||
|
import PlatformSummary from './platformSummary'
|
||||||
|
|
||||||
const tabs: ITabsType = [
|
const tabs: ITabsType = [
|
||||||
{
|
{
|
||||||
@@ -41,6 +42,11 @@ const tabs: ITabsType = [
|
|||||||
index: 6,
|
index: 6,
|
||||||
label: 'Agent Strategy',
|
label: 'Agent Strategy',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Component: PlatformSummary,
|
||||||
|
index: 7,
|
||||||
|
label: 'Platform Summary',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
|
|||||||
259
src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx
Normal file
259
src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import React, {useEffect, useState} from 'react'
|
||||||
|
import useApiUrlStore from '../../app/store/apiStore'
|
||||||
|
import {
|
||||||
|
DataClient,
|
||||||
|
type PlatformSummaryViewModel,
|
||||||
|
type StrategiesStatisticsViewModel,
|
||||||
|
type TopStrategiesViewModel
|
||||||
|
} from '../../generated/ManagingApi'
|
||||||
|
|
||||||
|
function PlatformSummary({ index }: { index: number }) {
|
||||||
|
const { apiUrl } = useApiUrlStore()
|
||||||
|
const [platformData, setPlatformData] = useState<PlatformSummaryViewModel | null>(null)
|
||||||
|
const [strategiesStats, setStrategiesStats] = useState<StrategiesStatisticsViewModel | null>(null)
|
||||||
|
const [topStrategies, setTopStrategies] = useState<TopStrategiesViewModel | null>(null)
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const fetchPlatformData = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = new DataClient({}, apiUrl)
|
||||||
|
|
||||||
|
// Fetch all platform data in parallel
|
||||||
|
const [platform, strategies, top] = await Promise.all([
|
||||||
|
client.data_GetPlatformSummary(),
|
||||||
|
client.data_GetStrategiesStatistics(),
|
||||||
|
client.data_GetTopStrategies()
|
||||||
|
])
|
||||||
|
|
||||||
|
setPlatformData(platform)
|
||||||
|
setStrategiesStats(strategies)
|
||||||
|
setTopStrategies(top)
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to fetch platform data')
|
||||||
|
console.error('Error fetching platform data:', err)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPlatformData()
|
||||||
|
|
||||||
|
// Set up refresh interval (every 30 seconds)
|
||||||
|
const interval = setInterval(fetchPlatformData, 30000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [apiUrl])
|
||||||
|
|
||||||
|
const formatCurrency = (value: number) => {
|
||||||
|
if (value >= 1000000) {
|
||||||
|
return `$${(value / 1000000).toFixed(1)}M`
|
||||||
|
} else if (value >= 1000) {
|
||||||
|
return `$${(value / 1000).toFixed(1)}K`
|
||||||
|
}
|
||||||
|
return `$${value.toFixed(0)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatNumber = (value: number) => {
|
||||||
|
if (value >= 1000) {
|
||||||
|
return value.toLocaleString()
|
||||||
|
}
|
||||||
|
return value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatChangeIndicator = (change: number) => {
|
||||||
|
if (change > 0) {
|
||||||
|
return (
|
||||||
|
<span className="text-green-500">
|
||||||
|
+{change} Today
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else if (change < 0) {
|
||||||
|
return (
|
||||||
|
<span className="text-red-500">
|
||||||
|
{change} Today
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span className="text-gray-500">
|
||||||
|
No change
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-96">
|
||||||
|
<div className="loading loading-spinner loading-lg"></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="alert alert-error">
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 bg-base-100 min-h-screen">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-4xl font-bold text-white mb-2">
|
||||||
|
{formatNumber(strategiesStats?.totalStrategiesRunning || 0)} Strategies Deployed
|
||||||
|
</h1>
|
||||||
|
<div className="text-lg">
|
||||||
|
{strategiesStats && formatChangeIndicator(strategiesStats.changeInLast24Hours || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Stats Grid */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 mb-8">
|
||||||
|
{/* Total Volume Traded */}
|
||||||
|
<div className="bg-base-200 rounded-lg p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Volume Traded</h3>
|
||||||
|
<div className="text-3xl font-bold text-white mb-1">
|
||||||
|
{formatCurrency(platformData?.totalPlatformVolume || 0)}
|
||||||
|
</div>
|
||||||
|
<div className="text-green-500">
|
||||||
|
+{formatCurrency(platformData?.totalPlatformVolumeLast24h || 0)} Today
|
||||||
|
</div>
|
||||||
|
{/* Simple chart placeholder - you can replace with actual chart */}
|
||||||
|
<div className="mt-4 h-16 flex items-end">
|
||||||
|
<div className="w-full h-8 bg-green-500 rounded opacity-20"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top 3 Most Profitable */}
|
||||||
|
<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 Most Profitable</h3>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{topStrategies?.topStrategies?.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">
|
||||||
|
<span className="text-xs font-bold text-white">
|
||||||
|
{strategy.strategyName?.charAt(0) || 'S'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-white font-medium">
|
||||||
|
{strategy.strategyName || '[Strategy Name]'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400">📧</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`text-sm font-bold ${strategy.pnL && strategy.pnL >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
||||||
|
{strategy.pnL && strategy.pnL >= 0 ? '+' : ''}{formatCurrency(strategy.pnL || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)) || (
|
||||||
|
<div className="text-gray-500 text-sm">No profitable strategies found</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top 3 Rising (placeholder - using same data for now) */}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{topStrategies?.topStrategies?.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">
|
||||||
|
<span className="text-xs font-bold text-white">
|
||||||
|
{strategy.strategyName?.charAt(0) || 'S'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm text-white font-medium">
|
||||||
|
{strategy.strategyName || '[Strategy Name]'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400">📧</div>
|
||||||
|
</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>
|
||||||
|
)) || (
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Platform Summary Stats */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div className="bg-base-200 rounded-lg p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Agents</h3>
|
||||||
|
<div className="text-3xl font-bold text-white">
|
||||||
|
{formatNumber(platformData?.totalAgents || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-base-200 rounded-lg p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-400 mb-2">Active Strategies</h3>
|
||||||
|
<div className="text-3xl font-bold text-white">
|
||||||
|
{formatNumber(platformData?.totalActiveStrategies || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-base-200 rounded-lg p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Platform PnL</h3>
|
||||||
|
<div className={`text-3xl font-bold ${(platformData?.totalPlatformPnL || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
||||||
|
{(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlatformSummary
|
||||||
Reference in New Issue
Block a user