Add save only for bundle backtest

This commit is contained in:
2025-10-21 16:38:51 +07:00
parent d144ae73ca
commit af08462e59
7 changed files with 120 additions and 45 deletions

View File

@@ -455,21 +455,15 @@ namespace Managing.Application.Backtests
return (backtests, totalCount);
}
// Bundle backtest methods
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
{
_backtestRepository.InsertBundleBacktestRequestForUser(user, bundleRequest);
// Trigger the BundleBacktestGrain to process this request
TriggerBundleBacktestGrain(bundleRequest.RequestId);
}
public async Task InsertBundleBacktestRequestForUserAsync(User user, BundleBacktestRequest bundleRequest)
public async Task InsertBundleBacktestRequestForUserAsync(User user, BundleBacktestRequest bundleRequest, bool saveAsTemplate = false)
{
await _backtestRepository.InsertBundleBacktestRequestForUserAsync(user, bundleRequest);
// Trigger the BundleBacktestGrain to process this request
await TriggerBundleBacktestGrainAsync(bundleRequest.RequestId);
if (!saveAsTemplate)
{
// Trigger the BundleBacktestGrain to process this request
await TriggerBundleBacktestGrainAsync(bundleRequest.RequestId);
}
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)

View File

@@ -3,7 +3,6 @@ using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Application.Orleans;
using Managing.Domain.Candles;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -174,10 +173,33 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
{
if (!position.IsValidForMetrics()) continue;
// Calculate volume using the dedicated method
var positionVolume = TradingHelpers.GetVolumeForPosition(position);
// 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 for debugging
// Track total volume from ALL positions (this is the true cumulative volume)
totalVolumeFromAllPositions += positionVolume;
// For cumulative volume: only add volume from positions created AFTER last snapshot
@@ -377,10 +399,18 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
{
_logger.LogInformation("Taking daily snapshot");
// Add daily snapshot
// Before taking today's snapshot, fill any missing snapshots from previous days
// This ensures we don't have gaps in the historical data
await FillMissingDailySnapshotsAsync();
// Refresh data to get the latest metrics
await RefreshDataAsync();
// Add daily snapshot for today
var today = DateTime.UtcNow.Date;
var dailySnapshot = new DailySnapshot
{
Date = DateTime.UtcNow.Date,
Date = today,
TotalAgents = _state.State.TotalAgents,
TotalStrategies = _state.State.TotalActiveStrategies,
TotalVolume = _state.State.TotalPlatformVolume,
@@ -388,20 +418,33 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
NetPnL = _state.State.NetPnL,
TotalOpenInterest = _state.State.OpenInterest,
TotalLifetimePositionCount = _state.State.TotalLifetimePositionCount,
TotalPlatformFees = (int)_state.State.TotalPlatformFees,
};
_state.State.DailySnapshots.Add(dailySnapshot);
// Only add if we don't already have a snapshot for today
if (!_state.State.DailySnapshots.Any(s => s.Date.Date == today))
{
_state.State.DailySnapshots.Add(dailySnapshot);
_logger.LogInformation("Created daily snapshot for {Date}: Volume={Volume}, PnL={PnL}, Positions={Positions}",
today, dailySnapshot.TotalVolume, dailySnapshot.TotalPnL, dailySnapshot.TotalLifetimePositionCount);
}
else
{
_logger.LogWarning("Daily snapshot for {Date} already exists, skipping", today);
}
// Keep only last 60 days
var cutoff = DateTime.UtcNow.AddDays(-60);
_state.State.DailySnapshots.RemoveAll(s => s.Date < cutoff);
_state.State.LastSnapshot = DateTime.UtcNow;
_state.State.LastSnapshot = today;
// Reset the volume updated by events flag daily to allow periodic refresh from strategies
_state.State.VolumeUpdatedByEvents = false;
await _state.WriteStateAsync();
_logger.LogInformation("Daily snapshot complete. Total snapshots: {Count}", _state.State.DailySnapshots.Count);
}
private bool IsDataStale()
@@ -516,27 +559,31 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
// Get all positions to calculate missing snapshots
var positions = await _tradingService.GetAllDatabasePositionsAsync();
if (!positions.Any())
{
_logger.LogInformation("No positions found, skipping gap filling");
return;
}
// Find the date range we need to cover
var earliestPositionDate = positions.Min(p => p.Date).Date;
var latestPositionDate = positions.Max(p => p.Date).Date;
var today = DateTime.UtcNow.Date;
// Determine the start date for gap filling
var startDate = _state.State.DailySnapshots.Any()
? _state.State.DailySnapshots.Max(s => s.Date).AddDays(1)
: earliestPositionDate;
DateTime startDate;
if (_state.State.DailySnapshots.Any())
{
startDate = _state.State.DailySnapshots.Max(s => s.Date).Date.AddDays(1);
}
else if (positions.Any())
{
// Start from the first position date
startDate = positions.Min(p => p.Date).Date;
}
else
{
// No positions and no snapshots - start from today
_logger.LogInformation("No positions and no snapshots found, starting from today");
startDate = today;
}
// Don't go beyond today
var endDate = today > latestPositionDate ? today : latestPositionDate;
// IMPORTANT: Fill snapshots up to TODAY (not just up to latest position date)
// This ensures we have daily snapshots even for days with no trading activity
var endDate = today;
_logger.LogInformation("Gap filling from {StartDate} to {EndDate}", startDate, endDate);
_logger.LogInformation("Gap filling from {StartDate} to {EndDate} (today)", startDate, endDate);
var missingDates = new List<DateTime>();
for (var date = startDate; date <= endDate; date = date.AddDays(1))
@@ -558,9 +605,8 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
// Calculate and add missing snapshots
foreach (var missingDate in missingDates)
{
var snapshot =
await CalculateDailySnapshotFromPositionsAsync(positions.Where(p => p.IsValidForMetrics()).ToList(),
missingDate);
var validPositions = positions.Where(p => p.IsValidForMetrics()).ToList();
var snapshot = await CalculateDailySnapshotFromPositionsAsync(validPositions, missingDate);
_state.State.DailySnapshots.Add(snapshot);
_logger.LogInformation(
@@ -596,7 +642,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
private async Task<DailySnapshot> CalculateDailySnapshotFromPositionsAsync(List<Position> positions,
DateTime targetDate)
{
// Calculate CUMULATIVE metrics: sum of ALL volume/PnL from positions opened on or before target date
// Calculate CUMULATIVE metrics: sum of ALL volume/PnL from positions with activity on or before target date
var totalVolume = 0m;
var totalFees = 0m;
var totalPnL = 0m;
@@ -625,28 +671,55 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
var closingVolume = 0m;
// ClosingVolume = Sum of all filled closing trades (SL, TP1, TP2) that happened on or before target date
// IMPORTANT: Only count closing volume from trades that were filled on or BEFORE the 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;
_logger.LogDebug("Position {PositionId}: Including SL closing volume {Volume} (filled on {Date})",
position.Identifier, position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage, position.StopLoss.Date.Date);
}
else if (position.StopLoss?.Status == TradeStatus.Filled)
{
_logger.LogDebug("Position {PositionId}: Excluding SL closing volume (filled on {Date} > target {TargetDate})",
position.Identifier, position.StopLoss.Date.Date, targetDate);
}
// 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;
_logger.LogDebug("Position {PositionId}: Including TP1 closing volume {Volume} (filled on {Date})",
position.Identifier, position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage, position.TakeProfit1.Date.Date);
}
else if (position.TakeProfit1?.Status == TradeStatus.Filled)
{
_logger.LogDebug("Position {PositionId}: Excluding TP1 closing volume (filled on {Date} > target {TargetDate})",
position.Identifier, position.TakeProfit1.Date.Date, targetDate);
}
// 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;
_logger.LogDebug("Position {PositionId}: Including TP2 closing volume {Volume} (filled on {Date})",
position.Identifier, position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage, position.TakeProfit2.Date.Date);
}
else if (position.TakeProfit2?.Status == TradeStatus.Filled)
{
_logger.LogDebug("Position {PositionId}: Excluding TP2 closing volume (filled on {Date} > target {TargetDate})",
position.Identifier, position.TakeProfit2.Date.Date, targetDate);
}
}
// For positions that are still open, no closing volume yet
else if (position.IsOpen())
{
_logger.LogDebug("Position {PositionId}: Still open, no closing volume", position.Identifier);
}
// Total volume for this position = opening + closing
// Total volume for this position = opening + closing (only what happened by target date)
var positionVolume = openVolume + closingVolume;
totalVolume += positionVolume;