Add netpnl and initialBalance to backtests

This commit is contained in:
2025-10-16 17:19:22 +07:00
parent 661f91f537
commit 472c507801
9 changed files with 50 additions and 10 deletions

View File

@@ -301,7 +301,9 @@ public class BacktestController : BaseController
Fees = b.Fees, Fees = b.Fees,
SharpeRatio = b.SharpeRatio, SharpeRatio = b.SharpeRatio,
Score = b.Score, Score = b.Score,
ScoreMessage = b.ScoreMessage ScoreMessage = b.ScoreMessage,
InitialBalance = b.InitialBalance,
NetPnl = b.NetPnl
}), }),
TotalCount = totalCount, TotalCount = totalCount,
CurrentPage = page, CurrentPage = page,
@@ -435,7 +437,9 @@ public class BacktestController : BaseController
Fees = b.Fees, Fees = b.Fees,
SharpeRatio = b.SharpeRatio, SharpeRatio = b.SharpeRatio,
Score = b.Score, Score = b.Score,
ScoreMessage = b.ScoreMessage ScoreMessage = b.ScoreMessage,
InitialBalance = b.InitialBalance,
NetPnl = b.NetPnl
}), }),
TotalCount = totalCount, TotalCount = totalCount,
CurrentPage = page, CurrentPage = page,

View File

@@ -20,6 +20,8 @@ public class LightBacktestResponse
[Required] public double? SharpeRatio { get; set; } [Required] public double? SharpeRatio { get; set; }
[Required] public double Score { get; set; } [Required] public double Score { get; set; }
[Required] public string ScoreMessage { get; set; } = string.Empty; [Required] public string ScoreMessage { get; set; } = string.Empty;
[Required] public decimal InitialBalance { get; set; }
[Required] public decimal NetPnl { get; set; }
} }
public static class LightBacktestResponseMapper public static class LightBacktestResponseMapper
@@ -41,7 +43,9 @@ public static class LightBacktestResponseMapper
Fees = b.Fees, Fees = b.Fees,
SharpeRatio = (double?)b.Statistics?.SharpeRatio, SharpeRatio = (double?)b.Statistics?.SharpeRatio,
Score = b.Score, Score = b.Score,
ScoreMessage = b.ScoreMessage ScoreMessage = b.ScoreMessage,
InitialBalance = b.InitialBalance,
NetPnl = b.NetPnl
}; };
} }
} }

View File

@@ -166,6 +166,8 @@ public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
Metadata = metadata, Metadata = metadata,
StartDate = candles.FirstOrDefault()!.OpenTime, StartDate = candles.FirstOrDefault()!.OpenTime,
EndDate = candles.LastOrDefault()!.OpenTime, EndDate = candles.LastOrDefault()!.OpenTime,
InitialBalance = tradingBot.WalletBalances.FirstOrDefault().Value,
NetPnl = finalPnl - fees,
}; };
if (save && user != null) if (save && user != null)
@@ -201,7 +203,9 @@ public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
Fees = backtest.Fees, Fees = backtest.Fees,
SharpeRatio = (double?)backtest.Statistics?.SharpeRatio, SharpeRatio = (double?)backtest.Statistics?.SharpeRatio,
Score = backtest.Score, Score = backtest.Score,
ScoreMessage = backtest.ScoreMessage ScoreMessage = backtest.ScoreMessage,
InitialBalance = backtest.InitialBalance,
NetPnl = backtest.NetPnl
}; };
} }

View File

@@ -172,7 +172,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
// Fallback: Get the first account for the user // Fallback: Get the first account for the user
if (_state.State.User == null) if (_state.State.User == null)
{ {
throw new InvalidOperationException($"Bot '{config.Name}' (ID: {_state.State.Identifier}) has no user information. Cannot determine fallback account."); throw new InvalidOperationException(
$"Bot '{config.Name}' (ID: {_state.State.Identifier}) has no user information. Cannot determine fallback account.");
} }
var firstAccount = await ServiceScopeHelpers.WithScopedService<IAccountService, Account>( var firstAccount = await ServiceScopeHelpers.WithScopedService<IAccountService, Account>(
@@ -183,8 +184,10 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
var account = userAccounts.FirstOrDefault(); var account = userAccounts.FirstOrDefault();
if (account == null) if (account == null)
{ {
throw new InvalidOperationException($"User '{_state.State.User.Name}' has no accounts configured."); throw new InvalidOperationException(
$"User '{_state.State.User.Name}' has no accounts configured.");
} }
return account; return account;
}); });
@@ -193,7 +196,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
_state.State.Config = config; _state.State.Config = config;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
_logger.LogInformation("Bot '{BotName}' (ID: {BotId}) using fallback account '{AccountName}' for user '{UserName}'", _logger.LogInformation(
"Bot '{BotName}' (ID: {BotId}) using fallback account '{AccountName}' for user '{UserName}'",
config.Name, _state.State.Identifier, firstAccount.Name, _state.State.User.Name); config.Name, _state.State.Identifier, firstAccount.Name, _state.State.User.Name);
} }
@@ -461,6 +465,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{ {
_logger.LogError(ex, "Error during coordinated balance check for bot {BotId}", _logger.LogError(ex, "Error during coordinated balance check for bot {BotId}",
_state.State.Identifier); _state.State.Identifier);
SentrySdk.CaptureException(ex);
// Continue execution to avoid stopping the bot due to coordination errors // Continue execution to avoid stopping the bot due to coordination errors
} }
} }
@@ -1046,7 +1051,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
{ {
if (_state.State.User == null) if (_state.State.User == null)
{ {
throw new InvalidOperationException($"Bot '{_state.State.Config?.Name}' (ID: {_state.State.Identifier}) has no user information."); throw new InvalidOperationException(
$"Bot '{_state.State.Config?.Name}' (ID: {_state.State.Identifier}) has no user information.");
} }
return Task.FromResult(_state.State.User); return Task.FromResult(_state.State.User);

View File

@@ -55,6 +55,8 @@ public class Backtest
public Guid RequestId { get; set; } public Guid RequestId { get; set; }
public object? Metadata { get; set; } public object? Metadata { get; set; }
public string ScoreMessage { get; set; } = string.Empty; public string ScoreMessage { get; set; } = string.Empty;
[Required] public decimal InitialBalance { get; set; }
[Required] public decimal NetPnl { get; set; }
/// <summary> /// <summary>
/// Creates a new TradingBotConfig based on this backtest's configuration for starting a live bot. /// Creates a new TradingBotConfig based on this backtest's configuration for starting a live bot.

View File

@@ -25,4 +25,6 @@ public class LightBacktest
[Id(12)] public string ScoreMessage { get; set; } = string.Empty; [Id(12)] public string ScoreMessage { get; set; } = string.Empty;
[Id(13)] public object Metadata { get; set; } [Id(13)] public object Metadata { get; set; }
[Id(14)] public string Ticker { get; set; } = string.Empty; [Id(14)] public string Ticker { get; set; } = string.Empty;
[Id(15)] public decimal InitialBalance { get; set; }
[Id(16)] public decimal NetPnl { get; set; }
} }

View File

@@ -189,6 +189,9 @@ namespace Managing.Infrastructure.Databases.Migrations
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
b.Property<decimal>("InitialBalance")
.HasColumnType("decimal(18,8)");
b.Property<decimal>("MaxDrawdown") b.Property<decimal>("MaxDrawdown")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("decimal(18,8)") .HasColumnType("decimal(18,8)")
@@ -211,6 +214,9 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255) .HasMaxLength(255)
.HasColumnType("character varying(255)"); .HasColumnType("character varying(255)");
b.Property<decimal>("NetPnl")
.HasColumnType("decimal(18,8)");
b.Property<string>("PositionsJson") b.Property<string>("PositionsJson")
.IsRequired() .IsRequired()
.HasColumnType("jsonb"); .HasColumnType("jsonb");

View File

@@ -120,4 +120,12 @@ public class BacktestEntity
[Required] [Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal InitialBalance { get; set; }
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal NetPnl { get; set; }
} }

View File

@@ -313,7 +313,9 @@ public static class PostgreSqlMappers
Score = entity.Score, Score = entity.Score,
ScoreMessage = entity.ScoreMessage, ScoreMessage = entity.ScoreMessage,
RequestId = entity.RequestId, RequestId = entity.RequestId,
Metadata = entity.Metadata Metadata = entity.Metadata,
InitialBalance = entity.InitialBalance,
NetPnl = entity.NetPnl
}; };
return backtest; return backtest;
@@ -353,7 +355,9 @@ public static class PostgreSqlMappers
ScoreMessage = backtest.ScoreMessage ?? string.Empty, ScoreMessage = backtest.ScoreMessage ?? string.Empty,
Metadata = backtest.Metadata?.ToString(), Metadata = backtest.Metadata?.ToString(),
CreatedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow UpdatedAt = DateTime.UtcNow,
InitialBalance = backtest.InitialBalance,
NetPnl = backtest.NetPnl
}; };
} }