Add test for platform summary calculation

This commit is contained in:
2025-11-14 17:21:39 +07:00
parent b6e4090f4e
commit d27df5de51
4 changed files with 987 additions and 283 deletions

View File

@@ -571,6 +571,17 @@ public static class TradingBox
decimal TotalFees,
decimal Collateral);
public record PlatformSummaryMetrics(
decimal TotalPlatformVolume,
decimal TotalPlatformFees,
decimal TotalPlatformPnL,
decimal NetPnL,
decimal OpenInterest,
int TotalLifetimePositionCount,
Dictionary<string, decimal> VolumeByAsset,
Dictionary<string, int> PositionCountByAsset,
Dictionary<TradeDirection, int> PositionCountByDirection);
public static AgentSummaryMetrics CalculateAgentSummaryMetrics(List<Position> positions)
{
var validPositions = positions?
@@ -595,6 +606,141 @@ public static class TradingBox
collateral);
}
/// <summary>
/// Calculates comprehensive platform summary metrics from a list of positions.
/// This includes volume, PnL, fees, open interest, and breakdowns by asset/direction.
/// </summary>
/// <param name="positions">List of all positions to analyze</param>
/// <param name="previousTotalVolume">Previous total volume to ensure cumulative volume never decreases</param>
/// <returns>PlatformSummaryMetrics with all calculated values</returns>
public static PlatformSummaryMetrics CalculatePlatformSummaryMetrics(List<Position> positions, decimal previousTotalVolume = 0m)
{
if (positions == null || !positions.Any())
{
return new PlatformSummaryMetrics(
TotalPlatformVolume: 0m,
TotalPlatformFees: 0m,
TotalPlatformPnL: 0m,
NetPnL: 0m,
OpenInterest: 0m,
TotalLifetimePositionCount: 0,
VolumeByAsset: new Dictionary<string, decimal>(),
PositionCountByAsset: new Dictionary<string, int>(),
PositionCountByDirection: new Dictionary<TradeDirection, int>()
);
}
// Initialize result variables
var volumeByAsset = new Dictionary<string, decimal>();
var positionCountByAsset = new Dictionary<string, int>();
var positionCountByDirection = new Dictionary<TradeDirection, int>();
decimal totalVolumeFromAllPositions = 0m;
decimal totalFees = 0m;
decimal totalPnL = 0m;
decimal totalOpenInterest = 0m;
int totalPositionCount = 0;
foreach (var position in positions)
{
if (!position.IsValidForMetrics()) continue;
// Calculate volume using the same logic as daily snapshots for consistency
// Opening volume is always counted (for positions opened on or before today)
var openVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
var closingVolume = 0m;
// Only include closing volume from trades that are filled
if (position.Status == PositionStatus.Finished || position.Status == PositionStatus.Flipped)
{
if (position.StopLoss?.Status == TradeStatus.Filled)
{
closingVolume += position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
}
if (position.TakeProfit1?.Status == TradeStatus.Filled)
{
closingVolume += position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
}
if (position.TakeProfit2?.Status == TradeStatus.Filled)
{
closingVolume += position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
}
}
var positionVolume = openVolume + closingVolume;
// Track total volume from ALL positions (this is the true cumulative volume)
totalVolumeFromAllPositions += positionVolume;
// Calculate breakdown metrics from ALL positions (for current state)
var ticker = position.Ticker.ToString();
var direction = position.OriginDirection;
// Volume breakdown by asset - update state directly
if (!volumeByAsset.ContainsKey(ticker))
{
volumeByAsset[ticker] = 0;
}
volumeByAsset[ticker] += positionVolume;
// Position count breakdown by asset - update state directly
if (!positionCountByAsset.ContainsKey(ticker))
{
positionCountByAsset[ticker] = 0;
}
positionCountByAsset[ticker]++;
// Calculate fees and PnL for all positions
totalFees += position.CalculateTotalFees();
totalPnL += position.ProfitAndLoss?.Realized ?? 0;
// Count all positions
totalPositionCount++;
// Position count breakdown by direction - only count open positions
if (position.IsOpen())
{
var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
totalOpenInterest += openingVolume;
if (!positionCountByDirection.ContainsKey(direction))
{
positionCountByDirection[direction] = 0;
}
positionCountByDirection[direction]++;
}
}
// ISSUE FIX: The correct cumulative volume should be calculated from ALL positions
// not just by adding new positions to the last snapshot, because:
// 1. Positions might be closed/updated which affects their volume
// 2. The daily snapshot might be outdated
// We should use the total from all positions, but never let it decrease
var correctCumulativeVolume = totalVolumeFromAllPositions;
// Ensure volume never decreases
if (correctCumulativeVolume < previousTotalVolume)
{
correctCumulativeVolume = previousTotalVolume;
}
var netPnL = totalPnL - totalFees;
return new PlatformSummaryMetrics(
TotalPlatformVolume: correctCumulativeVolume,
TotalPlatformFees: totalFees,
TotalPlatformPnL: totalPnL,
NetPnL: netPnL,
OpenInterest: totalOpenInterest,
TotalLifetimePositionCount: totalPositionCount,
VolumeByAsset: volumeByAsset,
PositionCountByAsset: positionCountByAsset,
PositionCountByDirection: positionCountByDirection
);
}
/// <summary>
/// Calculates the volume traded in the last 24 hours
/// </summary>