Update front and fix back

This commit is contained in:
2025-08-14 20:17:13 +07:00
parent 4a45d6c970
commit 8d37b04d3f
9 changed files with 419 additions and 130 deletions

View File

@@ -28,11 +28,11 @@ public static class PlatformSummaryExtensions
VolumeChange24h = abstractionsModel.VolumeChange24h, VolumeChange24h = abstractionsModel.VolumeChange24h,
OpenInterestChange24h = abstractionsModel.OpenInterestChange24h, OpenInterestChange24h = abstractionsModel.OpenInterestChange24h,
PositionCountChange24h = abstractionsModel.PositionCountChange24h, PositionCountChange24h = abstractionsModel.PositionCountChange24h,
VolumeByAsset = abstractionsModel.VolumeByAsset, VolumeByAsset = abstractionsModel.VolumeByAsset ?? new Dictionary<string, decimal>(),
PositionCountByAsset = abstractionsModel.PositionCountByAsset, PositionCountByAsset = abstractionsModel.PositionCountByAsset ?? new Dictionary<string, int>(),
PositionCountByDirection = abstractionsModel.PositionCountByDirection.ToDictionary( PositionCountByDirection = abstractionsModel.PositionCountByDirection?.ToDictionary(
kvp => kvp.Key.ToString(), kvp => kvp.Key.ToString(),
kvp => kvp.Value), kvp => kvp.Value) ?? new Dictionary<string, int>(),
LastUpdated = abstractionsModel.LastUpdated, LastUpdated = abstractionsModel.LastUpdated,
Last24HourSnapshot = abstractionsModel.Last24HourSnapshot Last24HourSnapshot = abstractionsModel.Last24HourSnapshot
}; };

View File

@@ -50,95 +50,95 @@ namespace Managing.Api.Models.Responses
/// <summary> /// <summary>
/// Total number of agents on the platform /// Total number of agents on the platform
/// </summary> /// </summary>
public int TotalAgents { get; set; } public required int TotalAgents { get; set; }
/// <summary> /// <summary>
/// Total number of active strategies across all agents /// Total number of active strategies across all agents
/// </summary> /// </summary>
public int TotalActiveStrategies { get; set; } public required int TotalActiveStrategies { get; set; }
/// <summary> /// <summary>
/// Total platform-wide profit and loss in USD /// Total platform-wide profit and loss in USD
/// </summary> /// </summary>
public decimal TotalPlatformPnL { get; set; } public required decimal TotalPlatformPnL { get; set; }
/// <summary> /// <summary>
/// Total volume traded across all agents in USD /// Total volume traded across all agents in USD
/// </summary> /// </summary>
public decimal TotalPlatformVolume { get; set; } public required decimal TotalPlatformVolume { get; set; }
/// <summary> /// <summary>
/// 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 required decimal TotalPlatformVolumeLast24h { get; set; }
/// <summary> /// <summary>
/// Total open interest across all positions in USD /// Total open interest across all positions in USD
/// </summary> /// </summary>
public decimal TotalOpenInterest { get; set; } public required decimal TotalOpenInterest { get; set; }
/// <summary> /// <summary>
/// Total number of open positions across all strategies /// Total number of open positions across all strategies
/// </summary> /// </summary>
public int TotalPositionCount { get; set; } public required int TotalPositionCount { get; set; }
// 24-hour changes // 24-hour changes
/// <summary> /// <summary>
/// Change in agent count over the last 24 hours /// Change in agent count over the last 24 hours
/// </summary> /// </summary>
public int AgentsChange24h { get; set; } public required int AgentsChange24h { get; set; }
/// <summary> /// <summary>
/// Change in strategy count over the last 24 hours /// Change in strategy count over the last 24 hours
/// </summary> /// </summary>
public int StrategiesChange24h { get; set; } public required int StrategiesChange24h { get; set; }
/// <summary> /// <summary>
/// Change in PnL over the last 24 hours /// Change in PnL over the last 24 hours
/// </summary> /// </summary>
public decimal PnLChange24h { get; set; } public required decimal PnLChange24h { get; set; }
/// <summary> /// <summary>
/// Change in volume over the last 24 hours /// Change in volume over the last 24 hours
/// </summary> /// </summary>
public decimal VolumeChange24h { get; set; } public required decimal VolumeChange24h { get; set; }
/// <summary> /// <summary>
/// Change in open interest over the last 24 hours /// Change in open interest over the last 24 hours
/// </summary> /// </summary>
public decimal OpenInterestChange24h { get; set; } public required decimal OpenInterestChange24h { get; set; }
/// <summary> /// <summary>
/// Change in position count over the last 24 hours /// Change in position count over the last 24 hours
/// </summary> /// </summary>
public int PositionCountChange24h { get; set; } public required int PositionCountChange24h { get; set; }
// Breakdowns // Breakdowns
/// <summary> /// <summary>
/// Volume breakdown by asset/ticker /// Volume breakdown by asset/ticker
/// </summary> /// </summary>
public Dictionary<string, decimal> VolumeByAsset { get; set; } = new(); public required Dictionary<string, decimal> VolumeByAsset { get; set; }
/// <summary> /// <summary>
/// Position count breakdown by asset/ticker /// Position count breakdown by asset/ticker
/// </summary> /// </summary>
public Dictionary<string, int> PositionCountByAsset { get; set; } = new(); public required Dictionary<string, int> PositionCountByAsset { get; set; }
/// <summary> /// <summary>
/// Position count breakdown by direction (Long/Short) /// Position count breakdown by direction (Long/Short)
/// </summary> /// </summary>
public Dictionary<string, int> PositionCountByDirection { get; set; } = new(); public required Dictionary<string, int> PositionCountByDirection { get; set; }
// Metadata // Metadata
/// <summary> /// <summary>
/// When the data was last updated /// When the data was last updated
/// </summary> /// </summary>
public DateTime LastUpdated { get; set; } public required DateTime LastUpdated { get; set; }
/// <summary> /// <summary>
/// When the last 24-hour snapshot was taken /// When the last 24-hour snapshot was taken
/// </summary> /// </summary>
public DateTime Last24HourSnapshot { get; set; } public required DateTime Last24HourSnapshot { get; set; }
} }
/// <summary> /// <summary>

View File

@@ -50,69 +50,125 @@ public interface IPlatformSummaryGrain : IGrainWithStringKey
/// <summary> /// <summary>
/// Base class for platform metrics events /// Base class for platform metrics events
/// </summary> /// </summary>
[GenerateSerializer]
public abstract class PlatformMetricsEvent public abstract class PlatformMetricsEvent
{ {
[Id(0)]
public DateTime Timestamp { get; set; } = DateTime.UtcNow; public DateTime Timestamp { get; set; } = DateTime.UtcNow;
} }
/// <summary> /// <summary>
/// Event fired when a new strategy is deployed /// Event fired when a new strategy is deployed
/// </summary> /// </summary>
[GenerateSerializer]
public class StrategyDeployedEvent : PlatformMetricsEvent public class StrategyDeployedEvent : PlatformMetricsEvent
{ {
[Id(1)]
public Guid StrategyId { get; set; } public Guid StrategyId { get; set; }
[Id(2)]
public string AgentName { get; set; } = string.Empty; public string AgentName { get; set; } = string.Empty;
[Id(3)]
public string StrategyName { get; set; } = string.Empty; public string StrategyName { get; set; } = string.Empty;
[Id(4)]
public decimal InitialVolume { get; set; } public decimal InitialVolume { get; set; }
[Id(5)]
public decimal InitialPnL { get; set; } public decimal InitialPnL { get; set; }
} }
/// <summary> /// <summary>
/// Event fired when a strategy is stopped /// Event fired when a strategy is stopped
/// </summary> /// </summary>
[GenerateSerializer]
public class StrategyStoppedEvent : PlatformMetricsEvent public class StrategyStoppedEvent : PlatformMetricsEvent
{ {
[Id(1)]
public Guid StrategyId { get; set; } public Guid StrategyId { get; set; }
[Id(2)]
public string AgentName { get; set; } = string.Empty; public string AgentName { get; set; } = string.Empty;
[Id(3)]
public string StrategyName { get; set; } = string.Empty; public string StrategyName { get; set; } = string.Empty;
} }
/// <summary> /// <summary>
/// Event fired when a new position is opened /// Event fired when a new position is opened
/// </summary> /// </summary>
[GenerateSerializer]
public class PositionOpenedEvent : PlatformMetricsEvent public class PositionOpenedEvent : PlatformMetricsEvent
{ {
[Id(1)]
public Guid PositionId { get; set; } public Guid PositionId { get; set; }
[Id(2)]
public Guid StrategyId { get; set; } public Guid StrategyId { get; set; }
[Id(3)]
public string Ticker { get; set; } = string.Empty; public string Ticker { get; set; } = string.Empty;
[Id(4)]
public decimal Size { get; set; } public decimal Size { get; set; }
[Id(5)]
public decimal NotionalValue { get; set; } public decimal NotionalValue { get; set; }
[Id(6)]
public TradeDirection Direction { get; set; } public TradeDirection Direction { get; set; }
} }
/// <summary> /// <summary>
/// Event fired when a position is closed /// Event fired when a position is closed
/// </summary> /// </summary>
[GenerateSerializer]
public class PositionClosedEvent : PlatformMetricsEvent public class PositionClosedEvent : PlatformMetricsEvent
{ {
[Id(1)]
public Guid PositionId { get; set; } public Guid PositionId { get; set; }
[Id(2)]
public Guid StrategyId { get; set; } public Guid StrategyId { get; set; }
[Id(3)]
public string Ticker { get; set; } = string.Empty; public string Ticker { get; set; } = string.Empty;
[Id(4)]
public decimal RealizedPnL { get; set; } public decimal RealizedPnL { get; set; }
[Id(5)]
public decimal Volume { get; set; } public decimal Volume { get; set; }
} }
/// <summary> /// <summary>
/// Event fired when a trade is executed /// Event fired when a trade is executed
/// </summary> /// </summary>
[GenerateSerializer]
public class TradeExecutedEvent : PlatformMetricsEvent public class TradeExecutedEvent : PlatformMetricsEvent
{ {
[Id(1)]
public Guid TradeId { get; set; } public Guid TradeId { get; set; }
[Id(2)]
public Guid PositionId { get; set; } public Guid PositionId { get; set; }
[Id(3)]
public Guid StrategyId { get; set; } public Guid StrategyId { get; set; }
[Id(4)]
public string Ticker { get; set; } = string.Empty; public string Ticker { get; set; } = string.Empty;
[Id(5)]
public decimal Volume { get; set; } public decimal Volume { get; set; }
[Id(6)]
public decimal PnL { get; set; } public decimal PnL { get; set; }
[Id(7)]
public decimal Fee { get; set; } public decimal Fee { get; set; }
[Id(8)]
public TradeDirection Direction { get; set; } public TradeDirection Direction { get; set; }
} }

View File

@@ -1,3 +1,4 @@
using Orleans;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Grains; namespace Managing.Application.Abstractions.Grains;
@@ -5,64 +6,127 @@ namespace Managing.Application.Abstractions.Grains;
/// <summary> /// <summary>
/// State model for Platform Summary Grain /// State model for Platform Summary Grain
/// </summary> /// </summary>
[GenerateSerializer]
public class PlatformSummaryGrainState public class PlatformSummaryGrainState
{ {
[Id(0)]
public DateTime LastUpdated { get; set; } public DateTime LastUpdated { get; set; }
[Id(1)]
public DateTime LastSnapshot { get; set; } public DateTime LastSnapshot { get; set; }
[Id(2)]
public bool HasPendingChanges { get; set; } public bool HasPendingChanges { get; set; }
// Current metrics // Current metrics
[Id(3)]
public int TotalAgents { get; set; } public int TotalAgents { get; set; }
[Id(4)]
public int TotalActiveStrategies { get; set; } public int TotalActiveStrategies { get; set; }
[Id(5)]
public decimal TotalPlatformPnL { get; set; } public decimal TotalPlatformPnL { get; set; }
[Id(6)]
public decimal TotalPlatformVolume { get; set; } public decimal TotalPlatformVolume { get; set; }
[Id(7)]
public decimal TotalOpenInterest { get; set; } public decimal TotalOpenInterest { get; set; }
[Id(8)]
public int TotalPositionCount { get; set; } public int TotalPositionCount { get; set; }
// 24-hour ago values (for comparison) // 24-hour ago values (for comparison)
[Id(9)]
public int TotalAgents24hAgo { get; set; } public int TotalAgents24hAgo { get; set; }
[Id(10)]
public int TotalActiveStrategies24hAgo { get; set; } public int TotalActiveStrategies24hAgo { get; set; }
[Id(11)]
public decimal TotalPlatformPnL24hAgo { get; set; } public decimal TotalPlatformPnL24hAgo { get; set; }
[Id(12)]
public decimal TotalPlatformVolume24hAgo { get; set; } public decimal TotalPlatformVolume24hAgo { get; set; }
[Id(13)]
public decimal TotalOpenInterest24hAgo { get; set; } public decimal TotalOpenInterest24hAgo { get; set; }
[Id(14)]
public int TotalPositionCount24hAgo { get; set; } public int TotalPositionCount24hAgo { get; set; }
// Historical snapshots // Historical snapshots
[Id(15)]
public List<HourlySnapshot> HourlySnapshots { get; set; } = new(); public List<HourlySnapshot> HourlySnapshots { get; set; } = new();
[Id(16)]
public List<DailySnapshot> DailySnapshots { get; set; } = new(); public List<DailySnapshot> DailySnapshots { get; set; } = new();
// Volume breakdown by asset // Volume breakdown by asset
[Id(17)]
public Dictionary<string, decimal> VolumeByAsset { get; set; } = new(); public Dictionary<string, decimal> VolumeByAsset { get; set; } = new();
// Position count breakdown // Position count breakdown
[Id(18)]
public Dictionary<string, int> PositionCountByAsset { get; set; } = new(); public Dictionary<string, int> PositionCountByAsset { get; set; } = new();
[Id(19)]
public Dictionary<TradeDirection, int> PositionCountByDirection { get; set; } = new(); public Dictionary<TradeDirection, int> PositionCountByDirection { get; set; } = new();
} }
/// <summary> /// <summary>
/// Hourly snapshot of platform metrics /// Hourly snapshot of platform metrics
/// </summary> /// </summary>
[GenerateSerializer]
public class HourlySnapshot public class HourlySnapshot
{ {
[Id(0)]
public DateTime Timestamp { get; set; } public DateTime Timestamp { get; set; }
[Id(1)]
public int TotalAgents { get; set; } public int TotalAgents { get; set; }
[Id(2)]
public int TotalStrategies { get; set; } public int TotalStrategies { get; set; }
[Id(3)]
public decimal TotalVolume { get; set; } public decimal TotalVolume { get; set; }
[Id(4)]
public decimal TotalPnL { get; set; } public decimal TotalPnL { get; set; }
[Id(5)]
public decimal TotalOpenInterest { get; set; } public decimal TotalOpenInterest { get; set; }
[Id(6)]
public int TotalPositionCount { get; set; } public int TotalPositionCount { get; set; }
} }
/// <summary> /// <summary>
/// Daily snapshot of platform metrics /// Daily snapshot of platform metrics
/// </summary> /// </summary>
[GenerateSerializer]
public class DailySnapshot public class DailySnapshot
{ {
[Id(0)]
public DateTime Date { get; set; } public DateTime Date { get; set; }
[Id(1)]
public int TotalAgents { get; set; } public int TotalAgents { get; set; }
[Id(2)]
public int TotalStrategies { get; set; } public int TotalStrategies { get; set; }
[Id(3)]
public decimal TotalVolume { get; set; } public decimal TotalVolume { get; set; }
[Id(4)]
public decimal TotalPnL { get; set; } public decimal TotalPnL { get; set; }
[Id(5)]
public decimal TotalOpenInterest { get; set; } public decimal TotalOpenInterest { get; set; }
[Id(6)]
public int TotalPositionCount { get; set; } public int TotalPositionCount { get; set; }
} }

View File

@@ -10,93 +10,93 @@ public class PlatformSummaryViewModel
/// <summary> /// <summary>
/// Total number of agents on the platform /// Total number of agents on the platform
/// </summary> /// </summary>
public int TotalAgents { get; set; } public required int TotalAgents { get; set; }
/// <summary> /// <summary>
/// Total number of active strategies across all agents /// Total number of active strategies across all agents
/// </summary> /// </summary>
public int TotalActiveStrategies { get; set; } public required int TotalActiveStrategies { get; set; }
/// <summary> /// <summary>
/// Total platform-wide profit and loss in USD /// Total platform-wide profit and loss in USD
/// </summary> /// </summary>
public decimal TotalPlatformPnL { get; set; } public required decimal TotalPlatformPnL { get; set; }
/// <summary> /// <summary>
/// Total volume traded across all agents in USD /// Total volume traded across all agents in USD
/// </summary> /// </summary>
public decimal TotalPlatformVolume { get; set; } public required decimal TotalPlatformVolume { get; set; }
/// <summary> /// <summary>
/// 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 required decimal TotalPlatformVolumeLast24h { get; set; }
/// <summary> /// <summary>
/// Total open interest across all positions in USD /// Total open interest across all positions in USD
/// </summary> /// </summary>
public decimal TotalOpenInterest { get; set; } public required decimal TotalOpenInterest { get; set; }
/// <summary> /// <summary>
/// Total number of open positions across all strategies /// Total number of open positions across all strategies
/// </summary> /// </summary>
public int TotalPositionCount { get; set; } public required int TotalPositionCount { get; set; }
// 24-hour changes // 24-hour changes
/// <summary> /// <summary>
/// Change in agent count over the last 24 hours /// Change in agent count over the last 24 hours
/// </summary> /// </summary>
public int AgentsChange24h { get; set; } public required int AgentsChange24h { get; set; }
/// <summary> /// <summary>
/// Change in strategy count over the last 24 hours /// Change in strategy count over the last 24 hours
/// </summary> /// </summary>
public int StrategiesChange24h { get; set; } public required int StrategiesChange24h { get; set; }
/// <summary> /// <summary>
/// Change in PnL over the last 24 hours /// Change in PnL over the last 24 hours
/// </summary> /// </summary>
public decimal PnLChange24h { get; set; } public required decimal PnLChange24h { get; set; }
/// <summary> /// <summary>
/// Change in volume over the last 24 hours /// Change in volume over the last 24 hours
/// </summary> /// </summary>
public decimal VolumeChange24h { get; set; } public required decimal VolumeChange24h { get; set; }
/// <summary> /// <summary>
/// Change in open interest over the last 24 hours /// Change in open interest over the last 24 hours
/// </summary> /// </summary>
public decimal OpenInterestChange24h { get; set; } public required decimal OpenInterestChange24h { get; set; }
/// <summary> /// <summary>
/// Change in position count over the last 24 hours /// Change in position count over the last 24 hours
/// </summary> /// </summary>
public int PositionCountChange24h { get; set; } public required int PositionCountChange24h { get; set; }
// Breakdowns // Breakdowns
/// <summary> /// <summary>
/// Volume breakdown by asset/ticker /// Volume breakdown by asset/ticker
/// </summary> /// </summary>
public Dictionary<string, decimal> VolumeByAsset { get; set; } = new(); public required Dictionary<string, decimal> VolumeByAsset { get; set; }
/// <summary> /// <summary>
/// Position count breakdown by asset/ticker /// Position count breakdown by asset/ticker
/// </summary> /// </summary>
public Dictionary<string, int> PositionCountByAsset { get; set; } = new(); public required Dictionary<string, int> PositionCountByAsset { get; set; }
/// <summary> /// <summary>
/// Position count breakdown by direction (Long/Short) /// Position count breakdown by direction (Long/Short)
/// </summary> /// </summary>
public Dictionary<TradeDirection, int> PositionCountByDirection { get; set; } = new(); public required Dictionary<TradeDirection, int> PositionCountByDirection { get; set; }
// Metadata // Metadata
/// <summary> /// <summary>
/// When the data was last updated /// When the data was last updated
/// </summary> /// </summary>
public DateTime LastUpdated { get; set; } public required DateTime LastUpdated { get; set; }
/// <summary> /// <summary>
/// When the last 24-hour snapshot was taken /// When the last 24-hour snapshot was taken
/// </summary> /// </summary>
public DateTime Last24HourSnapshot { get; set; } public required DateTime Last24HourSnapshot { get; set; }
} }

View File

@@ -18,10 +18,10 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
private readonly IAgentService _agentService; private readonly IAgentService _agentService;
private readonly ITradingService _tradingService; private readonly ITradingService _tradingService;
private readonly ILogger<PlatformSummaryGrain> _logger; private readonly ILogger<PlatformSummaryGrain> _logger;
private const string _hourlySnapshotReminder = "HourlySnapshot"; private const string _hourlySnapshotReminder = "HourlySnapshot";
private const string _dailySnapshotReminder = "DailySnapshot"; private const string _dailySnapshotReminder = "DailySnapshot";
public PlatformSummaryGrain( public PlatformSummaryGrain(
[PersistentState("platform-summary-state", "platform-summary-store")] [PersistentState("platform-summary-state", "platform-summary-store")]
IPersistentState<PlatformSummaryGrainState> state, IPersistentState<PlatformSummaryGrainState> state,
@@ -36,28 +36,28 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_tradingService = tradingService; _tradingService = tradingService;
_logger = logger; _logger = logger;
} }
public override async Task OnActivateAsync(CancellationToken cancellationToken) public override async Task OnActivateAsync(CancellationToken cancellationToken)
{ {
_logger.LogInformation("Platform Summary Grain activated"); _logger.LogInformation("Platform Summary Grain activated");
// Set up reminders for periodic snapshots // Set up reminders for periodic snapshots
await this.RegisterOrUpdateReminder(_hourlySnapshotReminder, await this.RegisterOrUpdateReminder(_hourlySnapshotReminder,
TimeSpan.FromHours(1), TimeSpan.FromHours(1)); TimeSpan.FromHours(1), TimeSpan.FromHours(1));
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var nextMidnight = now.Date.AddDays(1); var nextMidnight = now.Date.AddDays(1);
var timeUntilMidnight = nextMidnight - now; var timeUntilMidnight = nextMidnight - now;
await this.RegisterOrUpdateReminder(_dailySnapshotReminder, await this.RegisterOrUpdateReminder(_dailySnapshotReminder,
timeUntilMidnight, TimeSpan.FromDays(1)); timeUntilMidnight, TimeSpan.FromDays(1));
// Initial data load if state is empty // Initial data load if state is empty
if (_state.State.LastUpdated == default) if (_state.State.LastUpdated == default)
{ {
await RefreshDataAsync(); await RefreshDataAsync();
} }
} }
public async Task<PlatformSummaryViewModel> GetPlatformSummaryAsync() public async Task<PlatformSummaryViewModel> GetPlatformSummaryAsync()
{ {
// If data is stale or has pending changes, refresh it // If data is stale or has pending changes, refresh it
@@ -65,36 +65,36 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
{ {
await RefreshDataAsync(); await RefreshDataAsync();
} }
return MapToViewModel(_state.State); return MapToViewModel(_state.State);
} }
public async Task RefreshDataAsync() public async Task RefreshDataAsync()
{ {
try try
{ {
_logger.LogInformation("Refreshing platform summary data"); _logger.LogInformation("Refreshing platform summary data");
// Get all data in parallel for better performance // Get all data in parallel for better performance
var agentsTask = _agentService.GetAllAgentSummaries(); var agentsTask = _agentService.GetAllAgentSummaries();
var strategiesTask = _botService.GetBotsAsync(); var strategiesTask = _botService.GetBotsAsync();
await Task.WhenAll(agentsTask, strategiesTask); await Task.WhenAll(agentsTask, strategiesTask);
var agents = await agentsTask; var agents = await agentsTask;
var strategies = await strategiesTask; var strategies = await strategiesTask;
// Calculate totals // Calculate totals
var totalAgents = agents.Count(); var totalAgents = agents.Count();
var totalActiveStrategies = strategies.Count(s => s.Status == BotStatus.Running); var totalActiveStrategies = strategies.Count(s => s.Status == BotStatus.Running);
// Calculate volume and PnL from strategies // Calculate volume and PnL from strategies
var totalVolume = strategies.Sum(s => s.Volume); var totalVolume = strategies.Sum(s => s.Volume);
var totalPnL = strategies.Sum(s => s.Pnl); var totalPnL = strategies.Sum(s => s.Pnl);
// Calculate real open interest and position count from actual positions // Calculate real open interest and position count from actual positions
var (totalOpenInterest, totalPositionCount) = await CalculatePositionMetricsAsync(); var (totalOpenInterest, totalPositionCount) = await CalculatePositionMetricsAsync();
// Update state // Update state
_state.State.TotalAgents = totalAgents; _state.State.TotalAgents = totalAgents;
_state.State.TotalActiveStrategies = totalActiveStrategies; _state.State.TotalActiveStrategies = totalActiveStrategies;
@@ -104,15 +104,15 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_state.State.TotalPositionCount = totalPositionCount; _state.State.TotalPositionCount = totalPositionCount;
_state.State.LastUpdated = DateTime.UtcNow; _state.State.LastUpdated = DateTime.UtcNow;
_state.State.HasPendingChanges = false; _state.State.HasPendingChanges = false;
// Update volume breakdown by asset // Update volume breakdown by asset
await UpdateVolumeBreakdownAsync(strategies); await UpdateVolumeBreakdownAsync(strategies);
// Update position count breakdown // Update position count breakdown
await UpdatePositionCountBreakdownAsync(strategies); await UpdatePositionCountBreakdownAsync(strategies);
await _state.WriteStateAsync(); await _state.WriteStateAsync();
_logger.LogInformation("Platform summary data refreshed successfully"); _logger.LogInformation("Platform summary data refreshed successfully");
} }
catch (Exception ex) catch (Exception ex)
@@ -120,55 +120,56 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_logger.LogError(ex, "Error refreshing platform summary data"); _logger.LogError(ex, "Error refreshing platform summary data");
} }
} }
private async Task UpdateVolumeBreakdownAsync(IEnumerable<Bot> strategies) private async Task UpdateVolumeBreakdownAsync(IEnumerable<Bot> strategies)
{ {
_state.State.VolumeByAsset.Clear(); _state.State.VolumeByAsset.Clear();
// Group strategies by ticker and sum their volumes // Group strategies by ticker and sum their volumes
var volumeByAsset = strategies var volumeByAsset = strategies
.Where(s => s.Volume > 0) .Where(s => s.Volume > 0)
.GroupBy(s => s.Ticker.ToString()) .GroupBy(s => s.Ticker.ToString())
.ToDictionary(g => g.Key, g => g.Sum(s => s.Volume)); .ToDictionary(g => g.Key, g => g.Sum(s => s.Volume));
foreach (var kvp in volumeByAsset) foreach (var kvp in volumeByAsset)
{ {
_state.State.VolumeByAsset[kvp.Key] = kvp.Value; _state.State.VolumeByAsset[kvp.Key] = kvp.Value;
} }
_logger.LogDebug("Updated volume breakdown: {AssetCount} assets with total volume {TotalVolume}", _logger.LogDebug("Updated volume breakdown: {AssetCount} assets with total volume {TotalVolume}",
volumeByAsset.Count, volumeByAsset.Values.Sum()); volumeByAsset.Count, volumeByAsset.Values.Sum());
} }
private async Task UpdatePositionCountBreakdownAsync(IEnumerable<Bot> strategies) private async Task UpdatePositionCountBreakdownAsync(IEnumerable<Bot> strategies)
{ {
_state.State.PositionCountByAsset.Clear(); _state.State.PositionCountByAsset.Clear();
_state.State.PositionCountByDirection.Clear(); _state.State.PositionCountByDirection.Clear();
// Use position counts directly from bot statistics // Use position counts directly from bot statistics
var activeStrategies = strategies.Where(s => s.Status != BotStatus.Saved).ToList(); var activeStrategies = strategies.Where(s => s.Status != BotStatus.Saved).ToList();
if (activeStrategies.Any()) if (activeStrategies.Any())
{ {
// Group by asset and sum position counts per asset // Group by asset and sum position counts per asset
var positionsByAsset = activeStrategies var positionsByAsset = activeStrategies
.GroupBy(s => s.Ticker.ToString()) .GroupBy(s => s.Ticker.ToString())
.ToDictionary(g => g.Key, g => g.Sum(b => b.LongPositionCount + b.ShortPositionCount)); .ToDictionary(g => g.Key, g => g.Sum(b => b.LongPositionCount + b.ShortPositionCount));
// Sum long and short position counts across all bots // Sum long and short position counts across all bots
var totalLongPositions = activeStrategies.Sum(s => s.LongPositionCount); var totalLongPositions = activeStrategies.Sum(s => s.LongPositionCount);
var totalShortPositions = activeStrategies.Sum(s => s.ShortPositionCount); var totalShortPositions = activeStrategies.Sum(s => s.ShortPositionCount);
// Update state // Update state
foreach (var kvp in positionsByAsset) foreach (var kvp in positionsByAsset)
{ {
_state.State.PositionCountByAsset[kvp.Key] = kvp.Value; _state.State.PositionCountByAsset[kvp.Key] = kvp.Value;
} }
_state.State.PositionCountByDirection[TradeDirection.Long] = totalLongPositions; _state.State.PositionCountByDirection[TradeDirection.Long] = totalLongPositions;
_state.State.PositionCountByDirection[TradeDirection.Short] = totalShortPositions; _state.State.PositionCountByDirection[TradeDirection.Short] = totalShortPositions;
_logger.LogDebug("Updated position breakdown from bot statistics: {AssetCount} assets, Long={LongPositions}, Short={ShortPositions}", _logger.LogDebug(
"Updated position breakdown from bot statistics: {AssetCount} assets, Long={LongPositions}, Short={ShortPositions}",
positionsByAsset.Count, totalLongPositions, totalShortPositions); positionsByAsset.Count, totalLongPositions, totalShortPositions);
} }
else else
@@ -176,27 +177,27 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_logger.LogDebug("No active strategies found for position breakdown"); _logger.LogDebug("No active strategies found for position breakdown");
} }
} }
private async Task<(decimal totalOpenInterest, int totalPositionCount)> CalculatePositionMetricsAsync() private async Task<(decimal totalOpenInterest, int totalPositionCount)> CalculatePositionMetricsAsync()
{ {
try try
{ {
// Get all open positions from all accounts // Get all open positions from all accounts
var openPositions = await _tradingService.GetBrokerPositions(null); var openPositions = await _tradingService.GetBrokerPositions(null);
if (openPositions?.Any() == true) if (openPositions?.Any() == true)
{ {
var positionCount = openPositions.Count(); var positionCount = openPositions.Count();
// Calculate open interest as the sum of position notional values // Calculate open interest as the sum of position notional values
// Open interest = sum of (position size * price) for all open positions // Open interest = sum of (position size * price) for all open positions
var openInterest = openPositions var openInterest = openPositions
.Where(p => p.Open?.Price > 0 && p.Open?.Quantity > 0) .Where(p => p.Open?.Price > 0 && p.Open?.Quantity > 0)
.Sum(p => p.Open.Price * p.Open.Quantity); .Sum(p => p.Open.Price * p.Open.Quantity);
_logger.LogDebug("Calculated position metrics: {PositionCount} positions, {OpenInterest} open interest", _logger.LogDebug("Calculated position metrics: {PositionCount} positions, {OpenInterest} open interest",
positionCount, openInterest); positionCount, openInterest);
return (openInterest, positionCount); return (openInterest, positionCount);
} }
else else
@@ -211,102 +212,104 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
return (0m, 0); return (0m, 0);
} }
} }
public Task<decimal> GetTotalVolumeAsync() public Task<decimal> GetTotalVolumeAsync()
{ {
return Task.FromResult(_state.State.TotalPlatformVolume); return Task.FromResult(_state.State.TotalPlatformVolume);
} }
public Task<decimal> GetTotalPnLAsync() public Task<decimal> GetTotalPnLAsync()
{ {
return Task.FromResult(_state.State.TotalPlatformPnL); return Task.FromResult(_state.State.TotalPlatformPnL);
} }
public Task<decimal> GetTotalOpenInterestAsync() public Task<decimal> GetTotalOpenInterest()
{ {
return Task.FromResult(_state.State.TotalOpenInterest); return Task.FromResult(_state.State.TotalOpenInterest);
} }
public Task<int> GetTotalPositionCountAsync() public Task<int> GetTotalPositionCountAsync()
{ {
return Task.FromResult(_state.State.TotalPositionCount); return Task.FromResult(_state.State.TotalPositionCount);
} }
// Event handlers for immediate updates // Event handlers for immediate updates
public async Task OnStrategyDeployedAsync(StrategyDeployedEvent evt) public async Task OnStrategyDeployedAsync(StrategyDeployedEvent evt)
{ {
_logger.LogInformation("Strategy deployed: {StrategyId} - {StrategyName}", evt.StrategyId, evt.StrategyName); _logger.LogInformation("Strategy deployed: {StrategyId} - {StrategyName}", evt.StrategyId, evt.StrategyName);
_state.State.TotalActiveStrategies++; _state.State.TotalActiveStrategies++;
_state.State.HasPendingChanges = true; _state.State.HasPendingChanges = true;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
public async Task OnStrategyStoppedAsync(StrategyStoppedEvent evt) public async Task OnStrategyStoppedAsync(StrategyStoppedEvent evt)
{ {
_logger.LogInformation("Strategy stopped: {StrategyId} - {StrategyName}", evt.StrategyId, evt.StrategyName); _logger.LogInformation("Strategy stopped: {StrategyId} - {StrategyName}", evt.StrategyId, evt.StrategyName);
_state.State.TotalActiveStrategies--; _state.State.TotalActiveStrategies--;
_state.State.HasPendingChanges = true; _state.State.HasPendingChanges = true;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
public async Task OnPositionOpenedAsync(PositionOpenedEvent evt) public async Task OnPositionOpenedAsync(PositionOpenedEvent evt)
{ {
_logger.LogInformation("Position opened: {PositionId} for {Ticker}", evt.PositionId, evt.Ticker); _logger.LogInformation("Position opened: {PositionId} for {Ticker}", evt.PositionId, evt.Ticker);
_state.State.TotalPositionCount++; _state.State.TotalPositionCount++;
_state.State.TotalOpenInterest += evt.NotionalValue; _state.State.TotalOpenInterest += evt.NotionalValue;
_state.State.HasPendingChanges = true; _state.State.HasPendingChanges = true;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
public async Task OnPositionClosedAsync(PositionClosedEvent evt) public async Task OnPositionClosedAsync(PositionClosedEvent evt)
{ {
_logger.LogInformation("Position closed: {PositionId} for {Ticker} with PnL: {PnL}", _logger.LogInformation("Position closed: {PositionId} for {Ticker} with PnL: {PnL}",
evt.PositionId, evt.Ticker, evt.RealizedPnL); evt.PositionId, evt.Ticker, evt.RealizedPnL);
_state.State.TotalPositionCount--; _state.State.TotalPositionCount--;
_state.State.TotalPlatformVolume += evt.Volume; _state.State.TotalPlatformVolume += evt.Volume;
_state.State.TotalPlatformPnL += evt.RealizedPnL; _state.State.TotalPlatformPnL += evt.RealizedPnL;
// Update volume by asset // Update volume by asset
var asset = evt.Ticker; var asset = evt.Ticker;
if (!_state.State.VolumeByAsset.ContainsKey(asset)) if (!_state.State.VolumeByAsset.ContainsKey(asset))
{ {
_state.State.VolumeByAsset[asset] = 0; _state.State.VolumeByAsset[asset] = 0;
} }
_state.State.VolumeByAsset[asset] += evt.Volume; _state.State.VolumeByAsset[asset] += evt.Volume;
_state.State.HasPendingChanges = true; _state.State.HasPendingChanges = true;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
public async Task OnTradeExecutedAsync(TradeExecutedEvent evt) public async Task OnTradeExecutedAsync(TradeExecutedEvent evt)
{ {
_logger.LogInformation("Trade executed: {TradeId} for {Ticker} with volume: {Volume}", _logger.LogInformation("Trade executed: {TradeId} for {Ticker} with volume: {Volume}",
evt.TradeId, evt.Ticker, evt.Volume); evt.TradeId, evt.Ticker, evt.Volume);
_state.State.TotalPlatformVolume += evt.Volume; _state.State.TotalPlatformVolume += evt.Volume;
_state.State.TotalPlatformPnL += evt.PnL; _state.State.TotalPlatformPnL += evt.PnL;
// Update volume by asset // Update volume by asset
var asset = evt.Ticker; var asset = evt.Ticker;
if (!_state.State.VolumeByAsset.ContainsKey(asset)) if (!_state.State.VolumeByAsset.ContainsKey(asset))
{ {
_state.State.VolumeByAsset[asset] = 0; _state.State.VolumeByAsset[asset] = 0;
} }
_state.State.VolumeByAsset[asset] += evt.Volume; _state.State.VolumeByAsset[asset] += evt.Volume;
_state.State.HasPendingChanges = true; _state.State.HasPendingChanges = true;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
// Reminder handlers for periodic snapshots // Reminder handlers for periodic snapshots
public async Task ReceiveReminder(string reminderName, TickStatus status) public async Task ReceiveReminder(string reminderName, TickStatus status)
{ {
_logger.LogInformation("Reminder received: {ReminderName}", reminderName); _logger.LogInformation("Reminder received: {ReminderName}", reminderName);
switch (reminderName) switch (reminderName)
{ {
case _hourlySnapshotReminder: case _hourlySnapshotReminder:
@@ -317,11 +320,11 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
break; break;
} }
} }
private async Task TakeHourlySnapshotAsync() private async Task TakeHourlySnapshotAsync()
{ {
_logger.LogInformation("Taking hourly snapshot"); _logger.LogInformation("Taking hourly snapshot");
var snapshot = new HourlySnapshot var snapshot = new HourlySnapshot
{ {
Timestamp = DateTime.UtcNow, Timestamp = DateTime.UtcNow,
@@ -332,20 +335,20 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
TotalOpenInterest = _state.State.TotalOpenInterest, TotalOpenInterest = _state.State.TotalOpenInterest,
TotalPositionCount = _state.State.TotalPositionCount TotalPositionCount = _state.State.TotalPositionCount
}; };
_state.State.HourlySnapshots.Add(snapshot); _state.State.HourlySnapshots.Add(snapshot);
// Keep only last 24 hours // Keep only last 24 hours
var cutoff = DateTime.UtcNow.AddHours(-24); var cutoff = DateTime.UtcNow.AddHours(-24);
_state.State.HourlySnapshots.RemoveAll(s => s.Timestamp < cutoff); _state.State.HourlySnapshots.RemoveAll(s => s.Timestamp < cutoff);
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
private async Task TakeDailySnapshotAsync() private async Task TakeDailySnapshotAsync()
{ {
_logger.LogInformation("Taking daily snapshot"); _logger.LogInformation("Taking daily snapshot");
// Store 24-hour ago values for comparison // Store 24-hour ago values for comparison
_state.State.TotalAgents24hAgo = _state.State.TotalAgents; _state.State.TotalAgents24hAgo = _state.State.TotalAgents;
_state.State.TotalActiveStrategies24hAgo = _state.State.TotalActiveStrategies; _state.State.TotalActiveStrategies24hAgo = _state.State.TotalActiveStrategies;
@@ -353,7 +356,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_state.State.TotalPlatformVolume24hAgo = _state.State.TotalPlatformVolume; _state.State.TotalPlatformVolume24hAgo = _state.State.TotalPlatformVolume;
_state.State.TotalOpenInterest24hAgo = _state.State.TotalOpenInterest; _state.State.TotalOpenInterest24hAgo = _state.State.TotalOpenInterest;
_state.State.TotalPositionCount24hAgo = _state.State.TotalPositionCount; _state.State.TotalPositionCount24hAgo = _state.State.TotalPositionCount;
// Add daily snapshot // Add daily snapshot
var dailySnapshot = new DailySnapshot var dailySnapshot = new DailySnapshot
{ {
@@ -365,24 +368,24 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
TotalOpenInterest = _state.State.TotalOpenInterest, TotalOpenInterest = _state.State.TotalOpenInterest,
TotalPositionCount = _state.State.TotalPositionCount TotalPositionCount = _state.State.TotalPositionCount
}; };
_state.State.DailySnapshots.Add(dailySnapshot); _state.State.DailySnapshots.Add(dailySnapshot);
// Keep only last 30 days // Keep only last 30 days
var cutoff = DateTime.UtcNow.AddDays(-30); var cutoff = DateTime.UtcNow.AddDays(-30);
_state.State.DailySnapshots.RemoveAll(s => s.Date < cutoff); _state.State.DailySnapshots.RemoveAll(s => s.Date < cutoff);
_state.State.LastSnapshot = DateTime.UtcNow; _state.State.LastSnapshot = DateTime.UtcNow;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
private bool IsDataStale() private bool IsDataStale()
{ {
var timeSinceLastUpdate = DateTime.UtcNow - _state.State.LastUpdated; var timeSinceLastUpdate = DateTime.UtcNow - _state.State.LastUpdated;
return timeSinceLastUpdate > TimeSpan.FromMinutes(5); return timeSinceLastUpdate > TimeSpan.FromMinutes(5);
} }
private PlatformSummaryViewModel MapToViewModel(PlatformSummaryGrainState state) private PlatformSummaryViewModel MapToViewModel(PlatformSummaryGrainState state)
{ {
return new PlatformSummaryViewModel return new PlatformSummaryViewModel
@@ -394,7 +397,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
TotalPlatformVolumeLast24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo, TotalPlatformVolumeLast24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo,
TotalOpenInterest = state.TotalOpenInterest, TotalOpenInterest = state.TotalOpenInterest,
TotalPositionCount = state.TotalPositionCount, TotalPositionCount = state.TotalPositionCount,
// 24-hour changes // 24-hour changes
AgentsChange24h = state.TotalAgents - state.TotalAgents24hAgo, AgentsChange24h = state.TotalAgents - state.TotalAgents24hAgo,
StrategiesChange24h = state.TotalActiveStrategies - state.TotalActiveStrategies24hAgo, StrategiesChange24h = state.TotalActiveStrategies - state.TotalActiveStrategies24hAgo,
@@ -402,15 +405,15 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
VolumeChange24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo, VolumeChange24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo,
OpenInterestChange24h = state.TotalOpenInterest - state.TotalOpenInterest24hAgo, OpenInterestChange24h = state.TotalOpenInterest - state.TotalOpenInterest24hAgo,
PositionCountChange24h = state.TotalPositionCount - state.TotalPositionCount24hAgo, PositionCountChange24h = state.TotalPositionCount - state.TotalPositionCount24hAgo,
// Breakdowns // Breakdowns
VolumeByAsset = state.VolumeByAsset, VolumeByAsset = state.VolumeByAsset ?? new Dictionary<string, decimal>(),
PositionCountByAsset = state.PositionCountByAsset, PositionCountByAsset = state.PositionCountByAsset ?? new Dictionary<string, int>(),
PositionCountByDirection = state.PositionCountByDirection, PositionCountByDirection = state.PositionCountByDirection ?? new Dictionary<TradeDirection, int>(),
// Metadata // Metadata
LastUpdated = state.LastUpdated, LastUpdated = state.LastUpdated,
Last24HourSnapshot = state.LastSnapshot Last24HourSnapshot = state.LastSnapshot
}; };
} }
} }

View File

@@ -4577,6 +4577,19 @@ export interface PlatformSummaryViewModel {
totalPlatformPnL?: number; totalPlatformPnL?: number;
totalPlatformVolume?: number; totalPlatformVolume?: number;
totalPlatformVolumeLast24h?: number; totalPlatformVolumeLast24h?: number;
totalOpenInterest?: number;
totalPositionCount?: number;
agentsChange24h?: number;
strategiesChange24h?: number;
pnLChange24h?: number;
volumeChange24h?: number;
openInterestChange24h?: number;
positionCountChange24h?: number;
volumeByAsset?: { [key: string]: number; } | null;
positionCountByAsset?: { [key: string]: number; } | null;
positionCountByDirection?: { [key: string]: number; } | null;
lastUpdated?: Date;
last24HourSnapshot?: Date;
} }
export interface PaginatedAgentIndexResponse { export interface PaginatedAgentIndexResponse {

View File

@@ -967,6 +967,19 @@ export interface PlatformSummaryViewModel {
totalPlatformPnL?: number; totalPlatformPnL?: number;
totalPlatformVolume?: number; totalPlatformVolume?: number;
totalPlatformVolumeLast24h?: number; totalPlatformVolumeLast24h?: number;
totalOpenInterest?: number;
totalPositionCount?: number;
agentsChange24h?: number;
strategiesChange24h?: number;
pnLChange24h?: number;
volumeChange24h?: number;
openInterestChange24h?: number;
positionCountChange24h?: number;
volumeByAsset?: { [key: string]: number; } | null;
positionCountByAsset?: { [key: string]: number; } | null;
positionCountByDirection?: { [key: string]: number; } | null;
lastUpdated?: Date;
last24HourSnapshot?: Date;
} }
export interface PaginatedAgentIndexResponse { export interface PaginatedAgentIndexResponse {

View File

@@ -85,6 +85,12 @@ function PlatformSummary({ index }: { index: number }) {
) )
} }
const formatPercentageChange = (current: number, change: number) => {
if (current === 0) return '0%'
const percentage = (change / (current - change)) * 100
return `${percentage >= 0 ? '+' : ''}${percentage.toFixed(1)}%`
}
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex justify-center items-center min-h-96"> <div className="flex justify-center items-center min-h-96">
@@ -121,8 +127,11 @@ function PlatformSummary({ index }: { index: number }) {
<div className="text-3xl font-bold text-white mb-1"> <div className="text-3xl font-bold text-white mb-1">
{formatCurrency(platformData?.totalPlatformVolume || 0)} {formatCurrency(platformData?.totalPlatformVolume || 0)}
</div> </div>
<div className="text-green-500"> <div className={`text-sm ${(platformData?.volumeChange24h || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
+{formatCurrency(platformData?.totalPlatformVolumeLast24h || 0)} Today {(platformData?.volumeChange24h || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.volumeChange24h || 0)} Today
<span className="ml-2 text-gray-400">
({formatPercentageChange(platformData?.totalPlatformVolume || 0, platformData?.volumeChange24h || 0)})
</span>
</div> </div>
{/* Simple chart placeholder - you can replace with actual chart */} {/* Simple chart placeholder - you can replace with actual chart */}
<div className="mt-4 h-16 flex items-end"> <div className="mt-4 h-16 flex items-end">
@@ -230,19 +239,25 @@ function PlatformSummary({ index }: { index: number }) {
</div> </div>
{/* Platform Summary Stats */} {/* Platform Summary Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="bg-base-200 rounded-lg p-6"> <div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Agents</h3> <h3 className="text-lg font-semibold text-gray-400 mb-2">Total Agents</h3>
<div className="text-3xl font-bold text-white"> <div className="text-3xl font-bold text-white mb-1">
{formatNumber(platformData?.totalAgents || 0)} {formatNumber(platformData?.totalAgents || 0)}
</div> </div>
<div className={`text-sm ${(platformData?.agentsChange24h || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
{formatChangeIndicator(platformData?.agentsChange24h || 0)}
</div>
</div> </div>
<div className="bg-base-200 rounded-lg p-6"> <div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Active Strategies</h3> <h3 className="text-lg font-semibold text-gray-400 mb-2">Active Strategies</h3>
<div className="text-3xl font-bold text-white"> <div className="text-3xl font-bold text-white mb-1">
{formatNumber(platformData?.totalActiveStrategies || 0)} {formatNumber(platformData?.totalActiveStrategies || 0)}
</div> </div>
<div className={`text-sm ${(platformData?.strategiesChange24h || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
{formatChangeIndicator(platformData?.strategiesChange24h || 0)}
</div>
</div> </div>
<div className="bg-base-200 rounded-lg p-6"> <div className="bg-base-200 rounded-lg p-6">
@@ -250,6 +265,131 @@ function PlatformSummary({ index }: { index: number }) {
<div className={`text-3xl font-bold ${(platformData?.totalPlatformPnL || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}> <div className={`text-3xl font-bold ${(platformData?.totalPlatformPnL || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
{(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)} {(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)}
</div> </div>
<div className={`text-sm ${(platformData?.pnLChange24h || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
{(platformData?.pnLChange24h || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.pnLChange24h || 0)} Today
</div>
</div>
<div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Open Interest</h3>
<div className="text-3xl font-bold text-white mb-1">
{formatCurrency(platformData?.totalOpenInterest || 0)}
</div>
<div className={`text-sm ${(platformData?.openInterestChange24h || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
{(platformData?.openInterestChange24h || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.openInterestChange24h || 0)} Today
</div>
</div>
</div>
{/* Position Metrics */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Positions</h3>
<div className="text-3xl font-bold text-white mb-1">
{formatNumber(platformData?.totalPositionCount || 0)}
</div>
<div className={`text-sm ${(platformData?.positionCountChange24h || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
{formatChangeIndicator(platformData?.positionCountChange24h || 0)}
</div>
</div>
<div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Long Positions</h3>
<div className="text-3xl font-bold text-green-400 mb-1">
{formatNumber(platformData?.positionCountByDirection?.Long || 0)}
</div>
<div className="text-sm text-gray-400">
{platformData?.totalPositionCount ?
((platformData.positionCountByDirection?.Long || 0) / platformData.totalPositionCount * 100).toFixed(1) : 0}% of total
</div>
</div>
<div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-2">Short Positions</h3>
<div className="text-3xl font-bold text-red-400 mb-1">
{formatNumber(platformData?.positionCountByDirection?.Short || 0)}
</div>
<div className="text-sm text-gray-400">
{platformData?.totalPositionCount ?
((platformData.positionCountByDirection?.Short || 0) / platformData.totalPositionCount * 100).toFixed(1) : 0}% of total
</div>
</div>
</div>
{/* Volume and Positions by Asset */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Volume by Asset */}
<div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-4">Volume by Asset</h3>
<div className="space-y-3 max-h-80 overflow-y-auto">
{platformData?.volumeByAsset && Object.keys(platformData.volumeByAsset).length > 0 ? (
Object.entries(platformData.volumeByAsset)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
.map(([asset, volume]) => (
<div key={asset} className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<span className="text-xs font-bold text-white">
{asset.substring(0, 2)}
</span>
</div>
<span className="text-white font-medium">{asset}</span>
</div>
<div className="text-right">
<div className="text-white font-semibold">
{formatCurrency(volume)}
</div>
</div>
</div>
))
) : (
<div className="text-gray-500 text-sm">No volume data available</div>
)}
</div>
</div>
{/* Positions by Asset */}
<div className="bg-base-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-gray-400 mb-4">Positions by Asset</h3>
<div className="space-y-3 max-h-80 overflow-y-auto">
{platformData?.positionCountByAsset && Object.keys(platformData.positionCountByAsset).length > 0 ? (
Object.entries(platformData.positionCountByAsset)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
.map(([asset, count]) => (
<div key={asset} className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
<span className="text-xs font-bold text-white">
{asset.substring(0, 2)}
</span>
</div>
<span className="text-white font-medium">{asset}</span>
</div>
<div className="text-right">
<div className="text-white font-semibold">
{formatNumber(count)} positions
</div>
<div className="text-xs text-gray-400">
{platformData?.totalPositionCount ?
(count / platformData.totalPositionCount * 100).toFixed(1) : 0}% of total
</div>
</div>
</div>
))
) : (
<div className="text-gray-500 text-sm">No position data available</div>
)}
</div>
</div>
</div>
{/* Data Freshness Indicator */}
<div className="bg-base-200 rounded-lg p-4">
<div className="flex items-center justify-between text-sm text-gray-400">
<span>Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'}</span>
<span>24h snapshot: {platformData?.last24HourSnapshot ? new Date(platformData.last24HourSnapshot).toLocaleString() : 'Unknown'}</span>
</div> </div>
</div> </div>
</div> </div>