Fix global summary
This commit is contained in:
@@ -64,4 +64,6 @@ public class DailySnapshot
|
||||
[Id(5)] public decimal TotalOpenInterest { get; set; }
|
||||
|
||||
[Id(6)] public int TotalPositionCount { get; set; }
|
||||
|
||||
[Id(7)] public int TotalPlatformFees { get; set; }
|
||||
}
|
||||
@@ -2,8 +2,8 @@ using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Grains;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Orleans;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Trades;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -70,11 +70,12 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
TotalPnL = 0,
|
||||
TotalOpenInterest = 0,
|
||||
TotalPositionCount = 0,
|
||||
TotalPlatformFees = 0,
|
||||
};
|
||||
|
||||
_state.State.DailySnapshots.Add(initialSnapshot);
|
||||
_state.State.LastSnapshot = today;
|
||||
_state.State.LastUpdated = today;
|
||||
_state.State.LastSnapshot = initialSnapshot.Date;
|
||||
_state.State.LastUpdated = initialSnapshot.Date;
|
||||
_logger.LogInformation("Created initial empty daily snapshot for {Date}", today);
|
||||
}
|
||||
|
||||
@@ -99,55 +100,81 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
{
|
||||
_logger.LogInformation("Refreshing platform summary data");
|
||||
|
||||
var positions = await _tradingService.GetAllDatabasePositionsAsync();
|
||||
var strategies = await _botService.GetBotsAsync();
|
||||
|
||||
// Calculate totals
|
||||
var totalActiveStrategies = strategies.Count(s => s.Status == BotStatus.Running);
|
||||
// Calculate all metrics from positions in a single loop
|
||||
var totalVolume = 0m;
|
||||
var totalFees = 0m;
|
||||
var totalPnL = 0m;
|
||||
var totalOpenInterest = 0m;
|
||||
var totalPositionCount = 0;
|
||||
|
||||
// Calculate volume from strategies
|
||||
var totalVolume = strategies.Sum(s => s.Volume);
|
||||
// Clear state dictionaries at the start
|
||||
_state.State.VolumeByAsset.Clear();
|
||||
_state.State.PositionCountByAsset.Clear();
|
||||
_state.State.PositionCountByDirection.Clear();
|
||||
|
||||
// Calculate PnL directly from database positions (closed positions only)
|
||||
var totalPnL = await _tradingService.GetGlobalPnLFromPositionsAsync();
|
||||
|
||||
// Calculate real open interest and position count from actual positions
|
||||
var (totalOpenInterest, totalPositionCount) = await CalculatePositionMetricsAsync();
|
||||
|
||||
// Update state
|
||||
_state.State.TotalActiveStrategies = totalActiveStrategies;
|
||||
|
||||
// Only update volume if it hasn't been updated by events recently
|
||||
// This preserves real-time volume updates from position events
|
||||
if (!_state.State.VolumeUpdatedByEvents)
|
||||
foreach (var position in positions)
|
||||
{
|
||||
_state.State.TotalPlatformVolume = totalVolume;
|
||||
_logger.LogDebug("Updated volume from strategies: {Volume}", totalVolume);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Preserving event-updated volume: {Volume}", _state.State.TotalPlatformVolume);
|
||||
// Calculate volume using the dedicated method
|
||||
var positionVolume = GetVolumeForPosition(position);
|
||||
totalVolume += positionVolume;
|
||||
|
||||
// Add to open interest for active positions only
|
||||
if (!position.IsFinished())
|
||||
{
|
||||
totalOpenInterest += positionVolume;
|
||||
}
|
||||
|
||||
// Calculate fees and PnL for all positions
|
||||
totalFees += position.CalculateTotalFees();
|
||||
totalPnL += position.ProfitAndLoss?.Realized ?? 0;
|
||||
|
||||
// Count all positions
|
||||
totalPositionCount++;
|
||||
|
||||
// Calculate breakdown metrics and update state directly
|
||||
var ticker = position.Ticker;
|
||||
var direction = position.OriginDirection;
|
||||
|
||||
// Volume breakdown by asset - update state directly
|
||||
if (!_state.State.VolumeByAsset.ContainsKey(ticker))
|
||||
{
|
||||
_state.State.VolumeByAsset[ticker] = 0;
|
||||
}
|
||||
_state.State.VolumeByAsset[ticker] += positionVolume;
|
||||
|
||||
// Position count breakdown by asset - update state directly
|
||||
if (!_state.State.PositionCountByAsset.ContainsKey(ticker))
|
||||
{
|
||||
_state.State.PositionCountByAsset[ticker] = 0;
|
||||
}
|
||||
_state.State.PositionCountByAsset[ticker]++;
|
||||
|
||||
// Position count breakdown by direction - update state directly
|
||||
if (!_state.State.PositionCountByDirection.ContainsKey(direction))
|
||||
{
|
||||
_state.State.PositionCountByDirection[direction] = 0;
|
||||
}
|
||||
_state.State.PositionCountByDirection[direction]++;
|
||||
}
|
||||
|
||||
_state.State.TotalAgents = await _agentService.GetTotalAgentCount();
|
||||
_state.State.TotalPlatformVolume = totalVolume;
|
||||
_state.State.TotalPlatformFees = totalFees;
|
||||
_state.State.TotalPlatformPnL = totalPnL;
|
||||
_state.State.OpenInterest = totalOpenInterest;
|
||||
_state.State.TotalLifetimePositionCount = totalPositionCount;
|
||||
_state.State.LastUpdated = DateTime.UtcNow;
|
||||
_state.State.HasPendingChanges = false;
|
||||
|
||||
// Update volume breakdown by asset only if volume wasn't updated by events
|
||||
if (!_state.State.VolumeUpdatedByEvents)
|
||||
{
|
||||
UpdateVolumeBreakdown(strategies);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Preserving event-updated volume breakdown");
|
||||
}
|
||||
|
||||
// Update position count breakdown
|
||||
UpdatePositionCountBreakdown(strategies);
|
||||
_logger.LogDebug(
|
||||
"Updated position breakdown from positions: {AssetCount} assets, Long={LongPositions}, Short={ShortPositions}",
|
||||
_state.State.PositionCountByAsset.Count,
|
||||
_state.State.PositionCountByDirection.GetValueOrDefault(TradeDirection.Long, 0),
|
||||
_state.State.PositionCountByDirection.GetValueOrDefault(TradeDirection.Short, 0));
|
||||
|
||||
_state.State.LastUpdated = DateTime.UtcNow;
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
_logger.LogInformation("Platform summary data refreshed successfully");
|
||||
@@ -158,97 +185,39 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVolumeBreakdown(IEnumerable<Bot> strategies)
|
||||
/// <summary>
|
||||
/// Calculates the total volume for a position based on its status and filled trades
|
||||
/// </summary>
|
||||
/// <param name="position">The position to calculate volume for</param>
|
||||
/// <returns>The total volume for the position</returns>
|
||||
private decimal GetVolumeForPosition(Position position)
|
||||
{
|
||||
_state.State.VolumeByAsset.Clear();
|
||||
// Always include the opening trade volume
|
||||
var totalVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
|
||||
|
||||
// Group strategies by ticker and sum their volumes
|
||||
var volumeByAsset = strategies
|
||||
.Where(s => s.Volume > 0)
|
||||
.GroupBy(s => s.Ticker)
|
||||
.ToDictionary(g => g.Key, g => g.Sum(s => s.Volume));
|
||||
|
||||
foreach (var kvp in volumeByAsset)
|
||||
// For closed positions, add volume from filled closing trades
|
||||
if (position.IsFinished())
|
||||
{
|
||||
_state.State.VolumeByAsset[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Updated volume breakdown: {AssetCount} assets with total volume {TotalVolume}",
|
||||
volumeByAsset.Count, volumeByAsset.Values.Sum());
|
||||
}
|
||||
|
||||
private void UpdatePositionCountBreakdown(IEnumerable<Bot> 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)
|
||||
.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)
|
||||
// Add Stop Loss volume if filled
|
||||
if (position.StopLoss?.Status == TradeStatus.Filled)
|
||||
{
|
||||
_state.State.PositionCountByAsset[kvp.Key] = kvp.Value;
|
||||
totalVolume += position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
|
||||
}
|
||||
|
||||
_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}",
|
||||
positionsByAsset.Count, totalLongPositions, totalShortPositions);
|
||||
}
|
||||
else
|
||||
{
|
||||
_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
|
||||
// Get positions directly from database instead of exchange
|
||||
var allPositions = (await _tradingService.GetAllDatabasePositionsAsync()).ToList();
|
||||
var openPositions = allPositions?.Where(p => !p.IsFinished());
|
||||
var totalOpenPositionCount = allPositions.Count();
|
||||
|
||||
if (openPositions.Any())
|
||||
// Add Take Profit 1 volume if filled
|
||||
if (position.TakeProfit1?.Status == TradeStatus.Filled)
|
||||
{
|
||||
// Calculate open interest as the sum of leveraged position notional values
|
||||
// Open interest = sum of (position size * price * leverage) for all open positions
|
||||
var openInterest = openPositions
|
||||
.Sum(p => (p.Open.Price * p.Open.Quantity) * p.Open.Leverage);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Calculated position metrics: {PositionCount} positions, {OpenInterest} leveraged open interest",
|
||||
totalOpenPositionCount, openInterest);
|
||||
|
||||
return (openInterest, totalOpenPositionCount);
|
||||
totalVolume += position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
|
||||
}
|
||||
else
|
||||
|
||||
// Add Take Profit 2 volume if filled
|
||||
if (position.TakeProfit2?.Status == TradeStatus.Filled)
|
||||
{
|
||||
_logger.LogDebug("No open positions found for metrics calculation");
|
||||
return (0m, allPositions.Count());
|
||||
totalVolume += position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to calculate position metrics, returning zero values");
|
||||
return (0m, 0);
|
||||
}
|
||||
|
||||
return totalVolume;
|
||||
}
|
||||
|
||||
// Event handlers for immediate updates
|
||||
@@ -266,7 +235,6 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
}
|
||||
|
||||
_state.State.TotalActiveStrategies = newActiveCount;
|
||||
_state.State.HasPendingChanges = true;
|
||||
await _state.WriteStateAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -282,34 +250,34 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
_logger.LogInformation("Position closed: {PositionId} for {Ticker} with PnL: {PnL}",
|
||||
evt.PositionIdentifier, evt.Ticker, evt.RealizedPnL);
|
||||
|
||||
// Validate event data
|
||||
if (evt == null || evt.PositionIdentifier == Guid.Empty || evt.Ticker == Ticker.Unknown)
|
||||
{
|
||||
_logger.LogWarning("Invalid PositionClosedEvent received: {Event}", evt);
|
||||
return;
|
||||
}
|
||||
// // Validate event data
|
||||
// if (evt == null || evt.PositionIdentifier == Guid.Empty || evt.Ticker == Ticker.Unknown)
|
||||
// {
|
||||
// _logger.LogWarning("Invalid PositionClosedEvent received: {Event}", evt);
|
||||
// return;
|
||||
// }
|
||||
|
||||
_state.State.TotalPlatformVolume += evt.Volume;
|
||||
// _state.State.TotalPlatformVolume += evt.Volume;
|
||||
|
||||
// PnL is now calculated directly from database positions, not from events
|
||||
// This ensures accuracy and prevents double-counting issues
|
||||
// Refresh PnL from database to get the latest accurate value
|
||||
await RefreshPnLFromDatabaseAsync();
|
||||
// // PnL is now calculated directly from database positions, not from events
|
||||
// // This ensures accuracy and prevents double-counting issues
|
||||
// // Refresh PnL from database to get the latest accurate value
|
||||
// await RefreshPnLFromDatabaseAsync();
|
||||
|
||||
// Update volume by asset
|
||||
var asset = evt.Ticker;
|
||||
if (!_state.State.VolumeByAsset.ContainsKey(asset))
|
||||
{
|
||||
_state.State.VolumeByAsset[asset] = 0;
|
||||
}
|
||||
// // 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.VolumeByAsset[asset] += evt.Volume;
|
||||
|
||||
// Mark that volume has been updated by events
|
||||
_state.State.VolumeUpdatedByEvents = true;
|
||||
// // Mark that volume has been updated by events
|
||||
// _state.State.VolumeUpdatedByEvents = true;
|
||||
|
||||
// Update open interest (subtract the closed position's volume)
|
||||
_state.State.OpenInterest = Math.Max(0, _state.State.OpenInterest - evt.Volume);
|
||||
// // Update open interest (subtract the closed position's volume)
|
||||
// _state.State.OpenInterest = Math.Max(0, _state.State.OpenInterest - evt.Volume);
|
||||
|
||||
_state.State.HasPendingChanges = true;
|
||||
await _state.WriteStateAsync();
|
||||
@@ -327,54 +295,54 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
_logger.LogInformation("Position opened: {PositionIdentifier} for {Ticker} with volume: {Volume}",
|
||||
evt.PositionIdentifier, evt.Ticker, evt.Volume);
|
||||
|
||||
// Validate event data
|
||||
if (evt == null || evt.Ticker == Ticker.Unknown || evt.Volume <= 0)
|
||||
{
|
||||
_logger.LogWarning("Invalid PositionOpenEvent received: {Event}", evt);
|
||||
return;
|
||||
}
|
||||
// // Validate event data
|
||||
// if (evt == null || evt.Ticker == Ticker.Unknown || evt.Volume <= 0)
|
||||
// {
|
||||
// _logger.LogWarning("Invalid PositionOpenEvent received: {Event}", evt);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Update platform volume
|
||||
_state.State.TotalPlatformVolume += evt.Volume;
|
||||
// // Update platform volume
|
||||
// _state.State.TotalPlatformVolume += evt.Volume;
|
||||
|
||||
// Update volume by asset
|
||||
var asset = evt.Ticker;
|
||||
if (!_state.State.VolumeByAsset.ContainsKey(asset))
|
||||
{
|
||||
_state.State.VolumeByAsset[asset] = 0;
|
||||
}
|
||||
// // 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.VolumeByAsset[asset] += evt.Volume;
|
||||
|
||||
// Mark that volume has been updated by events
|
||||
_state.State.VolumeUpdatedByEvents = true;
|
||||
// // Mark that volume has been updated by events
|
||||
// _state.State.VolumeUpdatedByEvents = true;
|
||||
|
||||
// Update open interest and position count
|
||||
// Since this is called only when position is fully open on broker, we always increase counts
|
||||
_state.State.TotalLifetimePositionCount++;
|
||||
_state.State.OpenInterest += evt.Volume;
|
||||
// // Update open interest and position count
|
||||
// // Since this is called only when position is fully open on broker, we always increase counts
|
||||
// _state.State.TotalLifetimePositionCount++;
|
||||
// _state.State.OpenInterest += evt.Volume;
|
||||
|
||||
// Update position count by asset
|
||||
if (!_state.State.PositionCountByAsset.ContainsKey(asset))
|
||||
{
|
||||
_state.State.PositionCountByAsset[asset] = 0;
|
||||
}
|
||||
// // Update position count by asset
|
||||
// if (!_state.State.PositionCountByAsset.ContainsKey(asset))
|
||||
// {
|
||||
// _state.State.PositionCountByAsset[asset] = 0;
|
||||
// }
|
||||
|
||||
_state.State.PositionCountByAsset[asset]++;
|
||||
// _state.State.PositionCountByAsset[asset]++;
|
||||
|
||||
// Update position count by direction
|
||||
if (!_state.State.PositionCountByDirection.ContainsKey(evt.Direction))
|
||||
{
|
||||
_state.State.PositionCountByDirection[evt.Direction] = 0;
|
||||
}
|
||||
// // Update position count by direction
|
||||
// if (!_state.State.PositionCountByDirection.ContainsKey(evt.Direction))
|
||||
// {
|
||||
// _state.State.PositionCountByDirection[evt.Direction] = 0;
|
||||
// }
|
||||
|
||||
_state.State.PositionCountByDirection[evt.Direction]++;
|
||||
// _state.State.PositionCountByDirection[evt.Direction]++;
|
||||
|
||||
// Update fees if provided
|
||||
if (evt.Fee > 0)
|
||||
{
|
||||
_state.State.TotalPlatformFees += evt.Fee;
|
||||
}
|
||||
// // Update fees if provided
|
||||
// if (evt.Fee > 0)
|
||||
// {
|
||||
// _state.State.TotalPlatformFees += evt.Fee;
|
||||
// }
|
||||
|
||||
_state.State.HasPendingChanges = true;
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
Reference in New Issue
Block a user