From 8d37b04d3fb5639de48e8df0bd5fd36a04c0ba24 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Thu, 14 Aug 2025 20:17:13 +0700 Subject: [PATCH] Update front and fix back --- .../Extensions/PlatformSummaryExtensions.cs | 8 +- .../Models/Responses/AgentSummaryViewModel.cs | 36 ++-- .../Grains/IPlatformSummaryGrain.cs | 56 ++++++ .../Grains/PlatformSummaryGrainState.cs | 64 +++++++ .../Models/PlatformSummaryViewModel.cs | 36 ++-- .../Grains/PlatformSummaryGrain.cs | 173 +++++++++--------- .../src/generated/ManagingApi.ts | 13 ++ .../src/generated/ManagingApiTypes.ts | 13 ++ .../pages/dashboardPage/platformSummary.tsx | 150 ++++++++++++++- 9 files changed, 419 insertions(+), 130 deletions(-) diff --git a/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs b/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs index 060fc19..8e5009a 100644 --- a/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs +++ b/src/Managing.Api/Extensions/PlatformSummaryExtensions.cs @@ -28,11 +28,11 @@ public static class PlatformSummaryExtensions VolumeChange24h = abstractionsModel.VolumeChange24h, OpenInterestChange24h = abstractionsModel.OpenInterestChange24h, PositionCountChange24h = abstractionsModel.PositionCountChange24h, - VolumeByAsset = abstractionsModel.VolumeByAsset, - PositionCountByAsset = abstractionsModel.PositionCountByAsset, - PositionCountByDirection = abstractionsModel.PositionCountByDirection.ToDictionary( + VolumeByAsset = abstractionsModel.VolumeByAsset ?? new Dictionary(), + PositionCountByAsset = abstractionsModel.PositionCountByAsset ?? new Dictionary(), + PositionCountByDirection = abstractionsModel.PositionCountByDirection?.ToDictionary( kvp => kvp.Key.ToString(), - kvp => kvp.Value), + kvp => kvp.Value) ?? new Dictionary(), LastUpdated = abstractionsModel.LastUpdated, Last24HourSnapshot = abstractionsModel.Last24HourSnapshot }; diff --git a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs index 96dd8ed..27f9db5 100644 --- a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs +++ b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs @@ -50,95 +50,95 @@ namespace Managing.Api.Models.Responses /// /// Total number of agents on the platform /// - public int TotalAgents { get; set; } + public required int TotalAgents { get; set; } /// /// Total number of active strategies across all agents /// - public int TotalActiveStrategies { get; set; } + public required int TotalActiveStrategies { get; set; } /// /// Total platform-wide profit and loss in USD /// - public decimal TotalPlatformPnL { get; set; } + public required decimal TotalPlatformPnL { get; set; } /// /// Total volume traded across all agents in USD /// - public decimal TotalPlatformVolume { get; set; } + public required decimal TotalPlatformVolume { get; set; } /// /// Total volume traded across all agents in the last 24 hours in USD /// - public decimal TotalPlatformVolumeLast24h { get; set; } + public required decimal TotalPlatformVolumeLast24h { get; set; } /// /// Total open interest across all positions in USD /// - public decimal TotalOpenInterest { get; set; } + public required decimal TotalOpenInterest { get; set; } /// /// Total number of open positions across all strategies /// - public int TotalPositionCount { get; set; } + public required int TotalPositionCount { get; set; } // 24-hour changes /// /// Change in agent count over the last 24 hours /// - public int AgentsChange24h { get; set; } + public required int AgentsChange24h { get; set; } /// /// Change in strategy count over the last 24 hours /// - public int StrategiesChange24h { get; set; } + public required int StrategiesChange24h { get; set; } /// /// Change in PnL over the last 24 hours /// - public decimal PnLChange24h { get; set; } + public required decimal PnLChange24h { get; set; } /// /// Change in volume over the last 24 hours /// - public decimal VolumeChange24h { get; set; } + public required decimal VolumeChange24h { get; set; } /// /// Change in open interest over the last 24 hours /// - public decimal OpenInterestChange24h { get; set; } + public required decimal OpenInterestChange24h { get; set; } /// /// Change in position count over the last 24 hours /// - public int PositionCountChange24h { get; set; } + public required int PositionCountChange24h { get; set; } // Breakdowns /// /// Volume breakdown by asset/ticker /// - public Dictionary VolumeByAsset { get; set; } = new(); + public required Dictionary VolumeByAsset { get; set; } /// /// Position count breakdown by asset/ticker /// - public Dictionary PositionCountByAsset { get; set; } = new(); + public required Dictionary PositionCountByAsset { get; set; } /// /// Position count breakdown by direction (Long/Short) /// - public Dictionary PositionCountByDirection { get; set; } = new(); + public required Dictionary PositionCountByDirection { get; set; } // Metadata /// /// When the data was last updated /// - public DateTime LastUpdated { get; set; } + public required DateTime LastUpdated { get; set; } /// /// When the last 24-hour snapshot was taken /// - public DateTime Last24HourSnapshot { get; set; } + public required DateTime Last24HourSnapshot { get; set; } } /// diff --git a/src/Managing.Application.Abstractions/Grains/IPlatformSummaryGrain.cs b/src/Managing.Application.Abstractions/Grains/IPlatformSummaryGrain.cs index 0162e41..9fc935b 100644 --- a/src/Managing.Application.Abstractions/Grains/IPlatformSummaryGrain.cs +++ b/src/Managing.Application.Abstractions/Grains/IPlatformSummaryGrain.cs @@ -50,69 +50,125 @@ public interface IPlatformSummaryGrain : IGrainWithStringKey /// /// Base class for platform metrics events /// +[GenerateSerializer] public abstract class PlatformMetricsEvent { + [Id(0)] public DateTime Timestamp { get; set; } = DateTime.UtcNow; } /// /// Event fired when a new strategy is deployed /// +[GenerateSerializer] public class StrategyDeployedEvent : PlatformMetricsEvent { + [Id(1)] public Guid StrategyId { get; set; } + + [Id(2)] public string AgentName { get; set; } = string.Empty; + + [Id(3)] public string StrategyName { get; set; } = string.Empty; + + [Id(4)] public decimal InitialVolume { get; set; } + + [Id(5)] public decimal InitialPnL { get; set; } } /// /// Event fired when a strategy is stopped /// +[GenerateSerializer] public class StrategyStoppedEvent : PlatformMetricsEvent { + [Id(1)] public Guid StrategyId { get; set; } + + [Id(2)] public string AgentName { get; set; } = string.Empty; + + [Id(3)] public string StrategyName { get; set; } = string.Empty; } /// /// Event fired when a new position is opened /// +[GenerateSerializer] public class PositionOpenedEvent : PlatformMetricsEvent { + [Id(1)] public Guid PositionId { get; set; } + + [Id(2)] public Guid StrategyId { get; set; } + + [Id(3)] public string Ticker { get; set; } = string.Empty; + + [Id(4)] public decimal Size { get; set; } + + [Id(5)] public decimal NotionalValue { get; set; } + + [Id(6)] public TradeDirection Direction { get; set; } } /// /// Event fired when a position is closed /// +[GenerateSerializer] public class PositionClosedEvent : PlatformMetricsEvent { + [Id(1)] public Guid PositionId { get; set; } + + [Id(2)] public Guid StrategyId { get; set; } + + [Id(3)] public string Ticker { get; set; } = string.Empty; + + [Id(4)] public decimal RealizedPnL { get; set; } + + [Id(5)] public decimal Volume { get; set; } } /// /// Event fired when a trade is executed /// +[GenerateSerializer] public class TradeExecutedEvent : PlatformMetricsEvent { + [Id(1)] public Guid TradeId { get; set; } + + [Id(2)] public Guid PositionId { get; set; } + + [Id(3)] public Guid StrategyId { get; set; } + + [Id(4)] public string Ticker { get; set; } = string.Empty; + + [Id(5)] public decimal Volume { get; set; } + + [Id(6)] public decimal PnL { get; set; } + + [Id(7)] public decimal Fee { get; set; } + + [Id(8)] public TradeDirection Direction { get; set; } } diff --git a/src/Managing.Application.Abstractions/Grains/PlatformSummaryGrainState.cs b/src/Managing.Application.Abstractions/Grains/PlatformSummaryGrainState.cs index e733364..990ca99 100644 --- a/src/Managing.Application.Abstractions/Grains/PlatformSummaryGrainState.cs +++ b/src/Managing.Application.Abstractions/Grains/PlatformSummaryGrainState.cs @@ -1,3 +1,4 @@ +using Orleans; using static Managing.Common.Enums; namespace Managing.Application.Abstractions.Grains; @@ -5,64 +6,127 @@ namespace Managing.Application.Abstractions.Grains; /// /// State model for Platform Summary Grain /// +[GenerateSerializer] public class PlatformSummaryGrainState { + [Id(0)] public DateTime LastUpdated { get; set; } + + [Id(1)] public DateTime LastSnapshot { get; set; } + + [Id(2)] public bool HasPendingChanges { get; set; } // Current metrics + [Id(3)] public int TotalAgents { get; set; } + + [Id(4)] public int TotalActiveStrategies { get; set; } + + [Id(5)] public decimal TotalPlatformPnL { get; set; } + + [Id(6)] public decimal TotalPlatformVolume { get; set; } + + [Id(7)] public decimal TotalOpenInterest { get; set; } + + [Id(8)] public int TotalPositionCount { get; set; } // 24-hour ago values (for comparison) + [Id(9)] public int TotalAgents24hAgo { get; set; } + + [Id(10)] public int TotalActiveStrategies24hAgo { get; set; } + + [Id(11)] public decimal TotalPlatformPnL24hAgo { get; set; } + + [Id(12)] public decimal TotalPlatformVolume24hAgo { get; set; } + + [Id(13)] public decimal TotalOpenInterest24hAgo { get; set; } + + [Id(14)] public int TotalPositionCount24hAgo { get; set; } // Historical snapshots + [Id(15)] public List HourlySnapshots { get; set; } = new(); + + [Id(16)] public List DailySnapshots { get; set; } = new(); // Volume breakdown by asset + [Id(17)] public Dictionary VolumeByAsset { get; set; } = new(); // Position count breakdown + [Id(18)] public Dictionary PositionCountByAsset { get; set; } = new(); + + [Id(19)] public Dictionary PositionCountByDirection { get; set; } = new(); } /// /// Hourly snapshot of platform metrics /// +[GenerateSerializer] public class HourlySnapshot { + [Id(0)] public DateTime Timestamp { get; set; } + + [Id(1)] public int TotalAgents { get; set; } + + [Id(2)] public int TotalStrategies { get; set; } + + [Id(3)] public decimal TotalVolume { get; set; } + + [Id(4)] public decimal TotalPnL { get; set; } + + [Id(5)] public decimal TotalOpenInterest { get; set; } + + [Id(6)] public int TotalPositionCount { get; set; } } /// /// Daily snapshot of platform metrics /// +[GenerateSerializer] public class DailySnapshot { + [Id(0)] public DateTime Date { get; set; } + + [Id(1)] public int TotalAgents { get; set; } + + [Id(2)] public int TotalStrategies { get; set; } + + [Id(3)] public decimal TotalVolume { get; set; } + + [Id(4)] public decimal TotalPnL { get; set; } + + [Id(5)] public decimal TotalOpenInterest { get; set; } + + [Id(6)] public int TotalPositionCount { get; set; } } diff --git a/src/Managing.Application.Abstractions/Models/PlatformSummaryViewModel.cs b/src/Managing.Application.Abstractions/Models/PlatformSummaryViewModel.cs index a10268c..86bfe60 100644 --- a/src/Managing.Application.Abstractions/Models/PlatformSummaryViewModel.cs +++ b/src/Managing.Application.Abstractions/Models/PlatformSummaryViewModel.cs @@ -10,93 +10,93 @@ public class PlatformSummaryViewModel /// /// Total number of agents on the platform /// - public int TotalAgents { get; set; } + public required int TotalAgents { get; set; } /// /// Total number of active strategies across all agents /// - public int TotalActiveStrategies { get; set; } + public required int TotalActiveStrategies { get; set; } /// /// Total platform-wide profit and loss in USD /// - public decimal TotalPlatformPnL { get; set; } + public required decimal TotalPlatformPnL { get; set; } /// /// Total volume traded across all agents in USD /// - public decimal TotalPlatformVolume { get; set; } + public required decimal TotalPlatformVolume { get; set; } /// /// Total volume traded across all agents in the last 24 hours in USD /// - public decimal TotalPlatformVolumeLast24h { get; set; } + public required decimal TotalPlatformVolumeLast24h { get; set; } /// /// Total open interest across all positions in USD /// - public decimal TotalOpenInterest { get; set; } + public required decimal TotalOpenInterest { get; set; } /// /// Total number of open positions across all strategies /// - public int TotalPositionCount { get; set; } + public required int TotalPositionCount { get; set; } // 24-hour changes /// /// Change in agent count over the last 24 hours /// - public int AgentsChange24h { get; set; } + public required int AgentsChange24h { get; set; } /// /// Change in strategy count over the last 24 hours /// - public int StrategiesChange24h { get; set; } + public required int StrategiesChange24h { get; set; } /// /// Change in PnL over the last 24 hours /// - public decimal PnLChange24h { get; set; } + public required decimal PnLChange24h { get; set; } /// /// Change in volume over the last 24 hours /// - public decimal VolumeChange24h { get; set; } + public required decimal VolumeChange24h { get; set; } /// /// Change in open interest over the last 24 hours /// - public decimal OpenInterestChange24h { get; set; } + public required decimal OpenInterestChange24h { get; set; } /// /// Change in position count over the last 24 hours /// - public int PositionCountChange24h { get; set; } + public required int PositionCountChange24h { get; set; } // Breakdowns /// /// Volume breakdown by asset/ticker /// - public Dictionary VolumeByAsset { get; set; } = new(); + public required Dictionary VolumeByAsset { get; set; } /// /// Position count breakdown by asset/ticker /// - public Dictionary PositionCountByAsset { get; set; } = new(); + public required Dictionary PositionCountByAsset { get; set; } /// /// Position count breakdown by direction (Long/Short) /// - public Dictionary PositionCountByDirection { get; set; } = new(); + public required Dictionary PositionCountByDirection { get; set; } // Metadata /// /// When the data was last updated /// - public DateTime LastUpdated { get; set; } + public required DateTime LastUpdated { get; set; } /// /// When the last 24-hour snapshot was taken /// - public DateTime Last24HourSnapshot { get; set; } + public required DateTime Last24HourSnapshot { get; set; } } diff --git a/src/Managing.Application/Grains/PlatformSummaryGrain.cs b/src/Managing.Application/Grains/PlatformSummaryGrain.cs index 3d805ee..e2d517d 100644 --- a/src/Managing.Application/Grains/PlatformSummaryGrain.cs +++ b/src/Managing.Application/Grains/PlatformSummaryGrain.cs @@ -18,10 +18,10 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable private readonly IAgentService _agentService; private readonly ITradingService _tradingService; private readonly ILogger _logger; - + private const string _hourlySnapshotReminder = "HourlySnapshot"; private const string _dailySnapshotReminder = "DailySnapshot"; - + public PlatformSummaryGrain( [PersistentState("platform-summary-state", "platform-summary-store")] IPersistentState state, @@ -36,28 +36,28 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable _tradingService = tradingService; _logger = logger; } - + public override async Task OnActivateAsync(CancellationToken cancellationToken) { _logger.LogInformation("Platform Summary Grain activated"); - + // Set up reminders for periodic snapshots - await this.RegisterOrUpdateReminder(_hourlySnapshotReminder, + await this.RegisterOrUpdateReminder(_hourlySnapshotReminder, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); - + var now = DateTime.UtcNow; var nextMidnight = now.Date.AddDays(1); var timeUntilMidnight = nextMidnight - now; - await this.RegisterOrUpdateReminder(_dailySnapshotReminder, + await this.RegisterOrUpdateReminder(_dailySnapshotReminder, timeUntilMidnight, TimeSpan.FromDays(1)); - + // Initial data load if state is empty if (_state.State.LastUpdated == default) { await RefreshDataAsync(); } } - + public async Task GetPlatformSummaryAsync() { // If data is stale or has pending changes, refresh it @@ -65,36 +65,36 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable { await RefreshDataAsync(); } - + return MapToViewModel(_state.State); } - + public async Task RefreshDataAsync() { try { _logger.LogInformation("Refreshing platform summary data"); - + // Get all data in parallel for better performance var agentsTask = _agentService.GetAllAgentSummaries(); var strategiesTask = _botService.GetBotsAsync(); - + await Task.WhenAll(agentsTask, strategiesTask); - + var agents = await agentsTask; var strategies = await strategiesTask; - + // Calculate totals var totalAgents = agents.Count(); var totalActiveStrategies = strategies.Count(s => s.Status == BotStatus.Running); - + // Calculate volume and PnL from strategies var totalVolume = strategies.Sum(s => s.Volume); var totalPnL = strategies.Sum(s => s.Pnl); - + // Calculate real open interest and position count from actual positions var (totalOpenInterest, totalPositionCount) = await CalculatePositionMetricsAsync(); - + // Update state _state.State.TotalAgents = totalAgents; _state.State.TotalActiveStrategies = totalActiveStrategies; @@ -104,15 +104,15 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable _state.State.TotalPositionCount = totalPositionCount; _state.State.LastUpdated = DateTime.UtcNow; _state.State.HasPendingChanges = false; - + // Update volume breakdown by asset await UpdateVolumeBreakdownAsync(strategies); - + // Update position count breakdown await UpdatePositionCountBreakdownAsync(strategies); - + await _state.WriteStateAsync(); - + _logger.LogInformation("Platform summary data refreshed successfully"); } catch (Exception ex) @@ -120,55 +120,56 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable _logger.LogError(ex, "Error refreshing platform summary data"); } } - + private async Task UpdateVolumeBreakdownAsync(IEnumerable strategies) { _state.State.VolumeByAsset.Clear(); - + // Group strategies by ticker and sum their volumes var volumeByAsset = strategies .Where(s => s.Volume > 0) .GroupBy(s => s.Ticker.ToString()) .ToDictionary(g => g.Key, g => g.Sum(s => s.Volume)); - + foreach (var kvp in volumeByAsset) { _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()); } - + private async Task UpdatePositionCountBreakdownAsync(IEnumerable strategies) { _state.State.PositionCountByAsset.Clear(); _state.State.PositionCountByDirection.Clear(); - + // Use position counts directly from bot statistics var activeStrategies = strategies.Where(s => s.Status != BotStatus.Saved).ToList(); - + if (activeStrategies.Any()) { // Group by asset and sum position counts per asset var positionsByAsset = activeStrategies .GroupBy(s => s.Ticker.ToString()) .ToDictionary(g => g.Key, g => g.Sum(b => b.LongPositionCount + b.ShortPositionCount)); - + // Sum long and short position counts across all bots var totalLongPositions = activeStrategies.Sum(s => s.LongPositionCount); var totalShortPositions = activeStrategies.Sum(s => s.ShortPositionCount); - + // Update state foreach (var kvp in positionsByAsset) { _state.State.PositionCountByAsset[kvp.Key] = kvp.Value; } - + _state.State.PositionCountByDirection[TradeDirection.Long] = totalLongPositions; _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); } else @@ -176,27 +177,27 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable _logger.LogDebug("No active strategies found for position breakdown"); } } - + private async Task<(decimal totalOpenInterest, int totalPositionCount)> CalculatePositionMetricsAsync() { try { // Get all open positions from all accounts var openPositions = await _tradingService.GetBrokerPositions(null); - + if (openPositions?.Any() == true) { var positionCount = openPositions.Count(); - + // Calculate open interest as the sum of position notional values // Open interest = sum of (position size * price) for all open positions var openInterest = openPositions .Where(p => p.Open?.Price > 0 && p.Open?.Quantity > 0) .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); - + return (openInterest, positionCount); } else @@ -211,102 +212,104 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable return (0m, 0); } } - + public Task GetTotalVolumeAsync() { return Task.FromResult(_state.State.TotalPlatformVolume); } - + public Task GetTotalPnLAsync() { return Task.FromResult(_state.State.TotalPlatformPnL); } - - public Task GetTotalOpenInterestAsync() + + public Task GetTotalOpenInterest() { return Task.FromResult(_state.State.TotalOpenInterest); } - + public Task GetTotalPositionCountAsync() { return Task.FromResult(_state.State.TotalPositionCount); } - + // Event handlers for immediate updates public async Task OnStrategyDeployedAsync(StrategyDeployedEvent evt) { _logger.LogInformation("Strategy deployed: {StrategyId} - {StrategyName}", evt.StrategyId, evt.StrategyName); - + _state.State.TotalActiveStrategies++; _state.State.HasPendingChanges = true; await _state.WriteStateAsync(); } - + public async Task OnStrategyStoppedAsync(StrategyStoppedEvent evt) { _logger.LogInformation("Strategy stopped: {StrategyId} - {StrategyName}", evt.StrategyId, evt.StrategyName); - + _state.State.TotalActiveStrategies--; _state.State.HasPendingChanges = true; await _state.WriteStateAsync(); } - + public async Task OnPositionOpenedAsync(PositionOpenedEvent evt) { _logger.LogInformation("Position opened: {PositionId} for {Ticker}", evt.PositionId, evt.Ticker); - + _state.State.TotalPositionCount++; _state.State.TotalOpenInterest += evt.NotionalValue; _state.State.HasPendingChanges = true; await _state.WriteStateAsync(); } - + 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); - + _state.State.TotalPositionCount--; _state.State.TotalPlatformVolume += evt.Volume; _state.State.TotalPlatformPnL += evt.RealizedPnL; - + // Update volume by asset var asset = evt.Ticker; if (!_state.State.VolumeByAsset.ContainsKey(asset)) { _state.State.VolumeByAsset[asset] = 0; } + _state.State.VolumeByAsset[asset] += evt.Volume; - + _state.State.HasPendingChanges = true; await _state.WriteStateAsync(); } - + 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); - + _state.State.TotalPlatformVolume += evt.Volume; _state.State.TotalPlatformPnL += evt.PnL; - + // Update volume by asset var asset = evt.Ticker; if (!_state.State.VolumeByAsset.ContainsKey(asset)) { _state.State.VolumeByAsset[asset] = 0; } + _state.State.VolumeByAsset[asset] += evt.Volume; - + _state.State.HasPendingChanges = true; await _state.WriteStateAsync(); } - + // Reminder handlers for periodic snapshots public async Task ReceiveReminder(string reminderName, TickStatus status) { _logger.LogInformation("Reminder received: {ReminderName}", reminderName); - + switch (reminderName) { case _hourlySnapshotReminder: @@ -317,11 +320,11 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable break; } } - + private async Task TakeHourlySnapshotAsync() { _logger.LogInformation("Taking hourly snapshot"); - + var snapshot = new HourlySnapshot { Timestamp = DateTime.UtcNow, @@ -332,20 +335,20 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable TotalOpenInterest = _state.State.TotalOpenInterest, TotalPositionCount = _state.State.TotalPositionCount }; - + _state.State.HourlySnapshots.Add(snapshot); - + // Keep only last 24 hours var cutoff = DateTime.UtcNow.AddHours(-24); _state.State.HourlySnapshots.RemoveAll(s => s.Timestamp < cutoff); - + await _state.WriteStateAsync(); } - + private async Task TakeDailySnapshotAsync() { _logger.LogInformation("Taking daily snapshot"); - + // Store 24-hour ago values for comparison _state.State.TotalAgents24hAgo = _state.State.TotalAgents; _state.State.TotalActiveStrategies24hAgo = _state.State.TotalActiveStrategies; @@ -353,7 +356,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable _state.State.TotalPlatformVolume24hAgo = _state.State.TotalPlatformVolume; _state.State.TotalOpenInterest24hAgo = _state.State.TotalOpenInterest; _state.State.TotalPositionCount24hAgo = _state.State.TotalPositionCount; - + // Add daily snapshot var dailySnapshot = new DailySnapshot { @@ -365,24 +368,24 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable TotalOpenInterest = _state.State.TotalOpenInterest, TotalPositionCount = _state.State.TotalPositionCount }; - + _state.State.DailySnapshots.Add(dailySnapshot); - + // Keep only last 30 days var cutoff = DateTime.UtcNow.AddDays(-30); _state.State.DailySnapshots.RemoveAll(s => s.Date < cutoff); - + _state.State.LastSnapshot = DateTime.UtcNow; - + await _state.WriteStateAsync(); } - + private bool IsDataStale() { var timeSinceLastUpdate = DateTime.UtcNow - _state.State.LastUpdated; return timeSinceLastUpdate > TimeSpan.FromMinutes(5); } - + private PlatformSummaryViewModel MapToViewModel(PlatformSummaryGrainState state) { return new PlatformSummaryViewModel @@ -394,7 +397,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable TotalPlatformVolumeLast24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo, TotalOpenInterest = state.TotalOpenInterest, TotalPositionCount = state.TotalPositionCount, - + // 24-hour changes AgentsChange24h = state.TotalAgents - state.TotalAgents24hAgo, StrategiesChange24h = state.TotalActiveStrategies - state.TotalActiveStrategies24hAgo, @@ -402,15 +405,15 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable VolumeChange24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo, OpenInterestChange24h = state.TotalOpenInterest - state.TotalOpenInterest24hAgo, PositionCountChange24h = state.TotalPositionCount - state.TotalPositionCount24hAgo, - + // Breakdowns - VolumeByAsset = state.VolumeByAsset, - PositionCountByAsset = state.PositionCountByAsset, - PositionCountByDirection = state.PositionCountByDirection, - + VolumeByAsset = state.VolumeByAsset ?? new Dictionary(), + PositionCountByAsset = state.PositionCountByAsset ?? new Dictionary(), + PositionCountByDirection = state.PositionCountByDirection ?? new Dictionary(), + // Metadata LastUpdated = state.LastUpdated, Last24HourSnapshot = state.LastSnapshot }; } -} +} \ 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 7d1f6e7..a218c56 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -4577,6 +4577,19 @@ export interface PlatformSummaryViewModel { totalPlatformPnL?: number; totalPlatformVolume?: 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 { diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index 9126d8f..5df6e7b 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -967,6 +967,19 @@ export interface PlatformSummaryViewModel { totalPlatformPnL?: number; totalPlatformVolume?: 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 { diff --git a/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx b/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx index 2d60031..e5d8486 100644 --- a/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx +++ b/src/Managing.WebApp/src/pages/dashboardPage/platformSummary.tsx @@ -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) { return (
@@ -121,8 +127,11 @@ function PlatformSummary({ index }: { index: number }) {
{formatCurrency(platformData?.totalPlatformVolume || 0)}
-
- +{formatCurrency(platformData?.totalPlatformVolumeLast24h || 0)} Today +
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {(platformData?.volumeChange24h || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.volumeChange24h || 0)} Today + + ({formatPercentageChange(platformData?.totalPlatformVolume || 0, platformData?.volumeChange24h || 0)}) +
{/* Simple chart placeholder - you can replace with actual chart */}
@@ -230,19 +239,25 @@ function PlatformSummary({ index }: { index: number }) {
{/* Platform Summary Stats */} -
+

Total Agents

-
+
{formatNumber(platformData?.totalAgents || 0)}
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {formatChangeIndicator(platformData?.agentsChange24h || 0)} +

Active Strategies

-
+
{formatNumber(platformData?.totalActiveStrategies || 0)}
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {formatChangeIndicator(platformData?.strategiesChange24h || 0)} +
@@ -250,6 +265,131 @@ function PlatformSummary({ index }: { index: number }) {
= 0 ? 'text-green-500' : 'text-red-500'}`}> {(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)}
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {(platformData?.pnLChange24h || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.pnLChange24h || 0)} Today +
+
+ +
+

Open Interest

+
+ {formatCurrency(platformData?.totalOpenInterest || 0)} +
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {(platformData?.openInterestChange24h || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.openInterestChange24h || 0)} Today +
+
+
+ + {/* Position Metrics */} +
+
+

Total Positions

+
+ {formatNumber(platformData?.totalPositionCount || 0)} +
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {formatChangeIndicator(platformData?.positionCountChange24h || 0)} +
+
+ +
+

Long Positions

+
+ {formatNumber(platformData?.positionCountByDirection?.Long || 0)} +
+
+ {platformData?.totalPositionCount ? + ((platformData.positionCountByDirection?.Long || 0) / platformData.totalPositionCount * 100).toFixed(1) : 0}% of total +
+
+ +
+

Short Positions

+
+ {formatNumber(platformData?.positionCountByDirection?.Short || 0)} +
+
+ {platformData?.totalPositionCount ? + ((platformData.positionCountByDirection?.Short || 0) / platformData.totalPositionCount * 100).toFixed(1) : 0}% of total +
+
+
+ + {/* Volume and Positions by Asset */} +
+ {/* Volume by Asset */} +
+

Volume by Asset

+
+ {platformData?.volumeByAsset && Object.keys(platformData.volumeByAsset).length > 0 ? ( + Object.entries(platformData.volumeByAsset) + .sort(([,a], [,b]) => b - a) + .slice(0, 10) + .map(([asset, volume]) => ( +
+
+
+ + {asset.substring(0, 2)} + +
+ {asset} +
+
+
+ {formatCurrency(volume)} +
+
+
+ )) + ) : ( +
No volume data available
+ )} +
+
+ + {/* Positions by Asset */} +
+

Positions by Asset

+
+ {platformData?.positionCountByAsset && Object.keys(platformData.positionCountByAsset).length > 0 ? ( + Object.entries(platformData.positionCountByAsset) + .sort(([,a], [,b]) => b - a) + .slice(0, 10) + .map(([asset, count]) => ( +
+
+
+ + {asset.substring(0, 2)} + +
+ {asset} +
+
+
+ {formatNumber(count)} positions +
+
+ {platformData?.totalPositionCount ? + (count / platformData.totalPositionCount * 100).toFixed(1) : 0}% of total +
+
+
+ )) + ) : ( +
No position data available
+ )} +
+
+
+ + {/* Data Freshness Indicator */} +
+
+ Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'} + 24h snapshot: {platformData?.last24HourSnapshot ? new Date(platformData.last24HourSnapshot).toLocaleString() : 'Unknown'}