diff --git a/src/Managing.Application/Bots/Grains/AgentGrain.cs b/src/Managing.Application/Bots/Grains/AgentGrain.cs
index 9a5d80c3..1ff41004 100644
--- a/src/Managing.Application/Bots/Grains/AgentGrain.cs
+++ b/src/Managing.Application/Bots/Grains/AgentGrain.cs
@@ -361,7 +361,7 @@ public class AgentGrain : Grain, IAgentGrain
// Get or refresh cached balance data
var balanceData = await GetOrRefreshBalanceDataAsync(accountName);
- if (balanceData == null)
+ if (balanceData == null || balanceData.IsValid == false)
{
_logger.LogError("Failed to get balance data for account {AccountName}, user {UserId}",
accountName, this.GetPrimaryKeyLong());
@@ -645,7 +645,7 @@ public class AgentGrain : Grain, IAgentGrain
{
_logger.LogError(ex, "Error fetching balance data for account {AccountName}, user {UserId}",
accountName, this.GetPrimaryKeyLong());
- return null;
+ throw;
}
}
diff --git a/src/Managing.Application/Grains/PlatformSummaryGrain.cs b/src/Managing.Application/Grains/PlatformSummaryGrain.cs
index 2ea1849f..24be8d0a 100644
--- a/src/Managing.Application/Grains/PlatformSummaryGrain.cs
+++ b/src/Managing.Application/Grains/PlatformSummaryGrain.cs
@@ -64,6 +64,9 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_logger.LogInformation("Daily reminder scheduled - Next daily: {NextDaily}, Due time: {DueTime}",
nextDailyTime, timeUntilNextDay);
+ // Fix the first snapshot date if needed (should be day before first position)
+ await FixFirstSnapshotDateAsync();
+
// Wipe daily snapshots except for the first day
await WipeDailySnapshotsExceptFirstAsync();
@@ -76,10 +79,28 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
// Create initial empty daily snapshot if none exist
if (!_state.State.DailySnapshots.Any())
{
- var today = DateTime.UtcNow.Date.AddSeconds(1); // Today at 00:00:01 UTC
+ // Find the first position date to determine when to create the initial snapshot
+ var positions = await _tradingService.GetAllDatabasePositionsAsync();
+ DateTime initialSnapshotDate;
+
+ if (positions.Any())
+ {
+ // Create initial snapshot on the day BEFORE the first position
+ var firstPositionDate = positions.Min(p => p.Date).Date;
+ initialSnapshotDate = firstPositionDate.AddDays(-1);
+ _logger.LogInformation("First position found on {FirstPositionDate}, creating initial snapshot on {InitialSnapshotDate}",
+ firstPositionDate, initialSnapshotDate);
+ }
+ else
+ {
+ // No positions yet, create snapshot for today
+ initialSnapshotDate = DateTime.UtcNow.Date;
+ _logger.LogInformation("No positions found, creating initial snapshot for today: {InitialSnapshotDate}", initialSnapshotDate);
+ }
+
var initialSnapshot = new DailySnapshot
{
- Date = today,
+ Date = initialSnapshotDate,
TotalAgents = 0,
TotalStrategies = 0,
TotalVolume = 0,
@@ -94,7 +115,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_state.State.LastSnapshot = initialSnapshot.Date;
_state.State.LastUpdated = initialSnapshot.Date;
- _logger.LogInformation("Created initial empty daily snapshot for {Date}", today);
+ _logger.LogInformation("Created initial empty daily snapshot for {Date}", initialSnapshotDate);
}
await RefreshDataAsync();
@@ -138,6 +159,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
// Calculate all metrics from positions in a single loop
var newVolume = 0m; // Volume from positions after last snapshot
+ var totalVolumeFromAllPositions = 0m; // Total volume calculated from ALL positions (for comparison)
var totalFees = 0m;
var totalPnL = 0m;
var totalOpenInterest = 0m;
@@ -154,16 +176,25 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
// Calculate volume using the dedicated method
var positionVolume = TradingHelpers.GetVolumeForPosition(position);
+
+ // Track total volume from ALL positions for debugging
+ totalVolumeFromAllPositions += positionVolume;
// For cumulative volume: only add volume from positions created AFTER last snapshot
// For volume breakdown: include all positions
if (position.Date.Date > lastSnapshotDate)
{
newVolume += positionVolume;
- _logger.LogDebug(
+ _logger.LogInformation(
"Position {PositionId} created after last snapshot ({PositionDate} > {LastSnapshotDate}), adding volume: {Volume}",
position.Identifier, position.Date.Date, lastSnapshotDate, positionVolume);
}
+ else
+ {
+ _logger.LogDebug(
+ "Position {PositionId} created before/on last snapshot ({PositionDate} <= {LastSnapshotDate}), skipping volume: {Volume}",
+ position.Identifier, position.Date.Date, lastSnapshotDate, positionVolume);
+ }
// Calculate breakdown metrics from ALL positions (for current state)
var ticker = position.Ticker;
@@ -210,19 +241,30 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
// CUMULATIVE volume: baseline + new volume since last snapshot
var updatedCumulativeVolume = cumulativeVolume + newVolume;
- _logger.LogInformation("Volume calculation: Base={BaseVolume}, New={NewVolume}, Total={TotalVolume}",
- cumulativeVolume, newVolume, updatedCumulativeVolume);
+ _logger.LogWarning(
+ "Volume calculation DEBUG: TotalFromAllPositions={TotalFromAll}, LastSnapshotVolume={LastSnapshot}, NewVolumeSinceSnapshot={NewVolume}, CalculatedCumulative={Cumulative}, CurrentState={CurrentState}",
+ totalVolumeFromAllPositions, cumulativeVolume, newVolume, updatedCumulativeVolume,
+ _state.State.TotalPlatformVolume);
+
+ // 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 (updatedCumulativeVolume < _state.State.TotalPlatformVolume)
+ if (correctCumulativeVolume < _state.State.TotalPlatformVolume)
{
_logger.LogWarning(
- "Calculated cumulative volume ({Calculated}) is less than current volume ({Current}). Keeping current value.",
- updatedCumulativeVolume, _state.State.TotalPlatformVolume);
- updatedCumulativeVolume = _state.State.TotalPlatformVolume;
+ "Calculated volume ({Calculated}) is less than current volume ({Current}). Keeping current value to maintain cumulative nature.",
+ correctCumulativeVolume, _state.State.TotalPlatformVolume);
+ correctCumulativeVolume = _state.State.TotalPlatformVolume;
}
- _state.State.TotalPlatformVolume = updatedCumulativeVolume;
+ _state.State.TotalPlatformVolume = correctCumulativeVolume;
+
+ _logger.LogInformation("Final volume set to: {FinalVolume}", correctCumulativeVolume);
_state.State.TotalPlatformFees = totalFees;
_state.State.TotalPlatformPnL = totalPnL;
_state.State.NetPnL = totalPnL - totalFees; // Calculate NetPnL
@@ -368,6 +410,67 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
return timeSinceLastUpdate > TimeSpan.FromMinutes(5);
}
+ ///
+ /// Fixes the first snapshot date to ensure it's the day before the first position
+ ///
+ private async Task FixFirstSnapshotDateAsync()
+ {
+ try
+ {
+ if (!_state.State.DailySnapshots.Any())
+ {
+ _logger.LogInformation("No daily snapshots to fix");
+ return;
+ }
+
+ var positions = await _tradingService.GetAllDatabasePositionsAsync();
+ if (!positions.Any())
+ {
+ _logger.LogInformation("No positions found, cannot determine correct first snapshot date");
+ return;
+ }
+
+ var firstPositionDate = positions.Min(p => p.Date).Date;
+ var correctFirstSnapshotDate = firstPositionDate.AddDays(-1);
+ var currentFirstSnapshot = _state.State.DailySnapshots.OrderBy(s => s.Date).First();
+
+ _logger.LogInformation(
+ "First position date: {FirstPositionDate}, Current first snapshot: {CurrentFirstSnapshot}, Correct first snapshot should be: {CorrectFirstSnapshot}",
+ firstPositionDate, currentFirstSnapshot.Date, correctFirstSnapshotDate);
+
+ // If the first snapshot date is wrong, fix it
+ if (currentFirstSnapshot.Date.Date != correctFirstSnapshotDate.Date)
+ {
+ _logger.LogWarning(
+ "First snapshot date {CurrentDate} is incorrect. Should be {CorrectDate} (day before first position). Fixing...",
+ currentFirstSnapshot.Date, correctFirstSnapshotDate);
+
+ // Update the first snapshot date
+ currentFirstSnapshot.Date = correctFirstSnapshotDate;
+
+ // Reset all metrics to 0 for the first snapshot (it's before any activity)
+ currentFirstSnapshot.TotalVolume = 0;
+ currentFirstSnapshot.TotalPnL = 0;
+ currentFirstSnapshot.NetPnL = 0;
+ currentFirstSnapshot.TotalOpenInterest = 0;
+ currentFirstSnapshot.TotalLifetimePositionCount = 0;
+ currentFirstSnapshot.TotalPlatformFees = 0;
+
+ await _state.WriteStateAsync();
+
+ _logger.LogInformation("Fixed first snapshot date to {CorrectDate}", correctFirstSnapshotDate);
+ }
+ else
+ {
+ _logger.LogInformation("First snapshot date is correct");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error fixing first snapshot date");
+ }
+ }
+
///
/// Wipes all daily snapshots except for the first day
///
@@ -485,6 +588,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
///
/// Calculates a CUMULATIVE daily snapshot from positions up to a specific date
+ /// Based on SQL query logic: OpenVolume + ClosingVolume per position
///
/// All positions to analyze
/// The date to calculate the snapshot up to
@@ -492,127 +596,96 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
private async Task CalculateDailySnapshotFromPositionsAsync(List positions,
DateTime targetDate)
{
- var dayStart = targetDate;
- var dayEnd = targetDate.AddDays(1);
-
- // For CUMULATIVE daily snapshots, we need to consider ALL positions to calculate:
- // 1. TOTAL volume from all trades that occurred on or before this day
- // 2. Open interest from positions that were active during this day
- // 3. Cumulative position count up to this date
- // So we'll process all positions and include relevant data cumulatively
-
- // Calculate metrics for this specific day
+ // Calculate CUMULATIVE metrics: sum of ALL volume/PnL from positions opened on or before target date
var totalVolume = 0m;
var totalFees = 0m;
var totalPnL = 0m;
- var maxOpenInterest = 0m;
+ var currentOpenInterest = 0m;
var totalPositionCount = 0;
- // Calculate open interest at different points during the day to find the maximum
- var hourlyOpenInterest = new List();
-
- // Check open interest at each hour of the day (0-23)
- for (int hour = 0; hour < 24; hour++)
- {
- var hourDateTime = targetDate.AddHours(hour);
- var hourlyOI = 0m;
-
- foreach (var position in positions)
- {
- // Check if position was active at this hour
- var wasActiveAtThisHour = position.Date <= hourDateTime &&
- (position.IsOpen() ||
- (position.StopLoss.Status == TradeStatus.Filled &&
- position.StopLoss.Date > hourDateTime) ||
- (position.TakeProfit1.Status == TradeStatus.Filled &&
- position.TakeProfit1.Date > hourDateTime) ||
- (position.TakeProfit2 != null &&
- position.TakeProfit2.Status == TradeStatus.Filled &&
- position.TakeProfit2.Date > hourDateTime));
-
- if (wasActiveAtThisHour)
- {
- // For open interest, only count the opening volume (not closing trades)
- var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
- hourlyOI += openingVolume;
- }
- }
-
- hourlyOpenInterest.Add(hourlyOI);
- }
-
- // Find the maximum open interest during the day
- maxOpenInterest = hourlyOpenInterest.Max();
+ _logger.LogInformation("Calculating snapshot for {TargetDate} with {PositionCount} positions",
+ targetDate, positions.Count);
foreach (var position in positions)
{
- // Calculate CUMULATIVE volume up to this point in time
- // Include all positions that were opened on or before the target date
+ // Only include positions that were OPENED on or before the target date
+ if (position.Date.Date > targetDate)
+ {
+ _logger.LogDebug("Position {PositionId} opened after target date ({PositionDate} > {TargetDate}), skipping",
+ position.Identifier, position.Date.Date, targetDate);
+ continue;
+ }
+
+ // Count this position in the cumulative count
+ totalPositionCount++;
+
+ // Calculate volume for this position using SQL logic:
+ // OpenVolume = Price * Quantity * Leverage (from opening trade)
+ var openVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
+ var closingVolume = 0m;
+
+ // ClosingVolume = Sum of all filled closing trades (SL, TP1, TP2) that happened on or before target date
+ if (position.Status == PositionStatus.Finished || position.Status == PositionStatus.Flipped)
+ {
+ // Stop Loss volume (if filled and on or before target date)
+ if (position.StopLoss?.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate)
+ {
+ closingVolume += position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
+ }
+
+ // Take Profit 1 volume (if filled and on or before target date)
+ if (position.TakeProfit1?.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate)
+ {
+ closingVolume += position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
+ }
+
+ // Take Profit 2 volume (if filled and on or before target date)
+ if (position.TakeProfit2?.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate)
+ {
+ closingVolume += position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
+ }
+ }
+
+ // Total volume for this position = opening + closing
+ var positionVolume = openVolume + closingVolume;
+ totalVolume += positionVolume;
+
_logger.LogDebug(
- "Checking position {PositionId}: Position.Date={PositionDate}, TargetDate={TargetDate}, Position.Date.Date={PositionDateOnly}",
- position.Identifier, position.Date, targetDate, position.Date.Date);
+ "Position {PositionId} (opened {OpenDate}): OpenVolume={OpenVol}, ClosingVolume={CloseVol}, Total={Total}",
+ position.Identifier, position.Date.Date, openVolume, closingVolume, positionVolume);
- // Add opening volume if position was opened on or before this day
- // Use more flexible date comparison to handle timezone differences
- if (position.Date.Date <= targetDate)
+ // Calculate open interest (only for positions that are still open on target date)
+ if (position.IsOpen())
{
- var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
- totalVolume += openingVolume;
- _logger.LogDebug(
- "Position {PositionId} opened on/before {TargetDate}: Opening volume = {OpeningVolume}",
- position.Identifier, targetDate, openingVolume);
+ currentOpenInterest += openVolume;
}
-
- // Add closing volume if position was closed on or before this day
- if (position.IsValidForMetrics())
+ else if (position.Status == PositionStatus.Finished || position.Status == PositionStatus.Flipped)
{
- if (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate)
- {
- var closingVolume = position.StopLoss.Price * position.StopLoss.Quantity *
- position.StopLoss.Leverage;
- totalVolume += closingVolume;
- _logger.LogDebug(
- "Position {PositionId} closed on/before {TargetDate} via StopLoss: Closing volume = {ClosingVolume}",
- position.Identifier, targetDate, closingVolume);
- }
+ // Check if position was still open on the target date (closed after target date)
+ var closedAfterTargetDate =
+ (position.StopLoss?.Status == TradeStatus.Filled && position.StopLoss.Date.Date > targetDate) ||
+ (position.TakeProfit1?.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date > targetDate) ||
+ (position.TakeProfit2?.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date > targetDate);
- if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate)
+ if (closedAfterTargetDate)
{
- var closingVolume = position.TakeProfit1.Price * position.TakeProfit1.Quantity *
- position.TakeProfit1.Leverage;
- totalVolume += closingVolume;
- _logger.LogDebug(
- "Position {PositionId} closed on/before {TargetDate} via TakeProfit1: Closing volume = {ClosingVolume}",
- position.Identifier, targetDate, closingVolume);
- }
-
- if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
- position.TakeProfit2.Date.Date <= targetDate)
- {
- var closingVolume = position.TakeProfit2.Price * position.TakeProfit2.Quantity *
- position.TakeProfit2.Leverage;
- totalVolume += closingVolume;
+ currentOpenInterest += openVolume;
}
}
- // Calculate CUMULATIVE fees and PnL for positions closed on or before this day
- var wasClosedOnOrBeforeThisDay = position.IsValidForMetrics() && (
- (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate) ||
- (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate) ||
- (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
- position.TakeProfit2.Date.Date <= targetDate)
- );
-
- if (wasClosedOnOrBeforeThisDay)
+ // Calculate fees and PnL for FINISHED positions (only if closed on or before target date)
+ if (position.Status == PositionStatus.Finished || position.Status == PositionStatus.Flipped)
{
- totalFees += position.CalculateTotalFees();
- totalPnL += position.ProfitAndLoss?.Realized ?? 0;
- }
+ var wasClosedByTargetDate =
+ (position.StopLoss?.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate) ||
+ (position.TakeProfit1?.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate) ||
+ (position.TakeProfit2?.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate);
- // Count positions that were created on or before this day (CUMULATIVE position count)
- if (position.Date.Date <= targetDate)
- {
- totalPositionCount++;
+ if (wasClosedByTargetDate)
+ {
+ totalFees += position.CalculateTotalFees();
+ totalPnL += position.ProfitAndLoss?.Realized ?? 0;
+ }
}
}
@@ -621,8 +694,8 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
var totalStrategies = _state.State.TotalActiveStrategies;
_logger.LogInformation(
- "Calculated CUMULATIVE daily snapshot for {TargetDate}: CumVolume={TotalVolume}, MaxOpenInterest={MaxOpenInterest}, CumPositionCount={TotalPositionCount}",
- targetDate, totalVolume, maxOpenInterest, totalPositionCount);
+ "Calculated CUMULATIVE snapshot for {TargetDate}: CumVolume={TotalVolume}, OpenInterest={OpenInterest}, CumPositionCount={TotalPositionCount}, Fees={Fees}, PnL={PnL}",
+ targetDate, totalVolume, currentOpenInterest, totalPositionCount, totalFees, totalPnL);
return new DailySnapshot
{
@@ -632,7 +705,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
TotalVolume = totalVolume,
TotalPnL = totalPnL,
NetPnL = totalPnL - totalFees,
- TotalOpenInterest = maxOpenInterest,
+ TotalOpenInterest = currentOpenInterest,
TotalLifetimePositionCount = totalPositionCount,
TotalPlatformFees = (int)totalFees,
};
diff --git a/src/Managing.Infrastructure.Web3/Services/ChainService.cs b/src/Managing.Infrastructure.Web3/Services/ChainService.cs
index b205e729..bb16e73a 100644
--- a/src/Managing.Infrastructure.Web3/Services/ChainService.cs
+++ b/src/Managing.Infrastructure.Web3/Services/ChainService.cs
@@ -26,7 +26,7 @@ public static class ChainService
var chains = new List()
{
GetArbitrum(),
- GetEthereum(),
+ // GetEthereum(),
//GetArbitrumGoerli(),
//GetGoerli()
};
diff --git a/src/Managing.Infrastructure.Web3/Services/TokenService.cs b/src/Managing.Infrastructure.Web3/Services/TokenService.cs
index 89c356fc..16ce898f 100644
--- a/src/Managing.Infrastructure.Web3/Services/TokenService.cs
+++ b/src/Managing.Infrastructure.Web3/Services/TokenService.cs
@@ -80,7 +80,8 @@ public static class TokenService
Ticker.AAVE,
Ticker.XRP,
Ticker.PENDLE,
- Ticker.BNB
+ Ticker.BNB,
+ Ticker.USDC
};
}
}
\ No newline at end of file