Update front and fix back
This commit is contained in:
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user