Excellent! Let me create a comprehensive summary of all the changes made:

## Summary

I've successfully implemented all the requested features to add BacktestId support to the strategies/bot system:

### 1. **Added BacktestId Column to BotEntity** 
- **File**: `src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs:47`
- Added nullable `int? BacktestId` property with documentation
- This allows bots to reference the backtest they were created from

### 2. **Updated Bot Domain Model** 
- **File**: `src/Managing.Domain/Bots/Bot.cs:37`
- Added `BacktestId` property to the domain model
- Maintains consistency between entity and domain layers

### 3. **Updated TradingBotConfig and TradingBotConfigRequest** 
- **File**: `src/Managing.Domain/Bots/TradingBotConfig.cs:131`
  - Added `[Id(24)] public int? BacktestId` with Orleans serialization attribute
- **File**: `src/Managing.Domain/Bots/TradingBotConfigRequest.cs:119`
  - Added `BacktestId` property to the request model
- These changes ensure BacktestId flows through the entire bot creation pipeline

### 4. **Updated Data Mappers** 
- **File**: `src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs`
  - Updated `Map(Bot bot)` at line 833 to include BacktestId
  - Updated `Map(BotEntity entity)` at line 799 to include BacktestId
- Ensures proper mapping between entity and domain models

### 5. **Updated LiveTradingBotGrain** 
- **File**: `src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs`
  - Added BacktestId to bot creation at line 1078 (for saved bots)
  - Added BacktestId to bot creation at line 1145 (for running bots)
- The grain now properly persists BacktestId when saving bot statistics

### 6. **Created New Endpoint for Backtest Stats** 
- **File**: `src/Managing.Api/Controllers/BacktestController.cs:114`
- **New Endpoint**: `GET /Backtest/{id}/stats`
- Returns only statistical information without positions, signals, or candles:
  - Basic info: id, name, ticker, timeframe, tradingType, startDate, endDate
  - Performance: initialBalance, finalPnl, netPnl, growthPercentage, hodlPercentage, winRate
  - Risk metrics: sharpeRatio, maxDrawdown, maxDrawdownRecoveryTime
  - Other: fees, score, scoreMessage, positionCount

### 7. **Created Database Migration** 
- **Generated Migration**: `AddBacktestIdToBots`
- The migration adds a nullable integer column `BacktestId` to the `Bots` table
- Ready to be applied with `dotnet ef database update`

### 8. **Regenerated Frontend API Client** 
- Ran `dotnet build` in `src/Managing.Nswag`
- The `ManagingApi.ts` file has been regenerated with:
  - `backtestId` field in bot-related DTOs
  - New `/Backtest/{id}/stats` endpoint

## How It Works

### Starting a Bot from a Backtest:
1. Frontend sends `StartBotRequest` with `TradingBotConfigRequest` containing `backtestId`
2. `BotController` validates and prepares the request
3. `StartBotCommandHandler` creates the bot configuration with BacktestId
4. `LiveTradingBotGrain.CreateAsync()` receives the config and saves it to state
5. When the bot is saved via `SaveBotAsync()`, BacktestId is persisted to the database
6. The Bot entity now has a reference to its originating backtest

### Retrieving Backtest Stats:
1. Frontend calls `GET /Backtest/{id}/stats` with the backtest ID
2. Backend retrieves the full backtest from the database
3. Returns only the statistical summary (without heavy data like positions/signals/candles)
4. Frontend can display backtest performance metrics when viewing a bot

## Database Schema
```sql
ALTER TABLE "Bots" ADD COLUMN "BacktestId" integer NULL;
```

All changes follow the project's architecture patterns (Controller → Application → Repository) and maintain backward compatibility through nullable BacktestId fields.
This commit is contained in:
2026-01-09 18:24:08 +07:00
parent 1bb736ff70
commit 452c274073
10 changed files with 1901 additions and 0 deletions

View File

@@ -104,6 +104,52 @@ public class BacktestController : BaseController
return Ok(backtest); return Ok(backtest);
} }
/// <summary>
/// Retrieves only the statistical information for a specific backtest by ID.
/// This endpoint returns only the performance metrics without positions, signals, or candles.
/// Useful for displaying backtest stats when starting a bot from a backtest.
/// </summary>
/// <param name="id">The ID of the backtest to retrieve stats for.</param>
/// <returns>The backtest statistics without detailed position/signal data.</returns>
[HttpGet("{id}/stats")]
public async Task<ActionResult<object>> GetBacktestStats(int id)
{
var user = await GetUser();
var backtest = await _backtester.GetBacktestByIdForUserAsync(user, id.ToString());
if (backtest == null)
{
return NotFound($"Backtest with ID {id} not found or doesn't belong to the current user.");
}
// Return only the statistical information
var stats = new
{
id = backtest.Id,
name = backtest.Config.Name,
ticker = backtest.Config.Ticker,
timeframe = backtest.Config.Timeframe,
tradingType = backtest.Config.TradingType,
startDate = backtest.StartDate,
endDate = backtest.EndDate,
initialBalance = backtest.InitialBalance,
finalPnl = backtest.FinalPnl,
netPnl = backtest.NetPnl,
growthPercentage = backtest.GrowthPercentage,
hodlPercentage = backtest.HodlPercentage,
winRate = backtest.WinRate,
sharpeRatio = backtest.Statistics?.SharpeRatio ?? 0,
maxDrawdown = backtest.Statistics?.MaxDrawdown ?? 0,
maxDrawdownRecoveryTime = backtest.Statistics?.MaxDrawdownRecoveryTime ?? TimeSpan.Zero,
fees = backtest.Fees,
score = backtest.Score,
scoreMessage = backtest.ScoreMessage,
positionCount = backtest.PositionCount
};
return Ok(stats);
}
/// <summary> /// <summary>
/// Deletes a specific backtest by ID for the authenticated user. /// Deletes a specific backtest by ID for the authenticated user.
/// </summary> /// </summary>

View File

@@ -1075,6 +1075,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
Volume = 0, Volume = 0,
Fees = 0, Fees = 0,
BotTradingBalance = _state.State.Config.BotTradingBalance, BotTradingBalance = _state.State.Config.BotTradingBalance,
BacktestId = _state.State.Config.BacktestId,
MasterBotUserId = _state.State.Config.MasterBotUserId MasterBotUserId = _state.State.Config.MasterBotUserId
}; };
} }
@@ -1141,6 +1142,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
LongPositionCount = longPositionCount, LongPositionCount = longPositionCount,
ShortPositionCount = shortPositionCount, ShortPositionCount = shortPositionCount,
BotTradingBalance = _state.State.Config.BotTradingBalance, BotTradingBalance = _state.State.Config.BotTradingBalance,
BacktestId = _state.State.Config.BacktestId,
MasterBotUserId = _state.State.Config.MasterBotUserId MasterBotUserId = _state.State.Config.MasterBotUserId
}; };
} }

View File

@@ -31,6 +31,11 @@ namespace Managing.Domain.Bots
public decimal BotTradingBalance { get; set; } public decimal BotTradingBalance { get; set; }
/// <summary>
/// The backtest ID associated with this bot (nullable for bots not created from backtests)
/// </summary>
public int? BacktestId { get; set; }
/// <summary> /// <summary>
/// The user ID of the master bot's owner when this bot is for copy trading /// The user ID of the master bot's owner when this bot is for copy trading
/// </summary> /// </summary>

View File

@@ -123,4 +123,10 @@ public class TradingBotConfig
/// </summary> /// </summary>
[Id(23)] [Id(23)]
public int? MasterBotUserId { get; set; } public int? MasterBotUserId { get; set; }
/// <summary>
/// The backtest ID associated with this bot configuration (nullable for bots not created from backtests)
/// </summary>
[Id(24)]
public int? BacktestId { get; set; }
} }

View File

@@ -112,4 +112,9 @@ public class TradingBotConfigRequest
public bool UseForDynamicStopLoss { get; set; } = true; public bool UseForDynamicStopLoss { get; set; } = true;
public TradingType TradingType { get; set; } public TradingType TradingType { get; set; }
/// <summary>
/// The backtest ID associated with this bot configuration (nullable for bots not created from backtests)
/// </summary>
public int? BacktestId { get; set; }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddBacktestIdToBots : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "BacktestId",
table: "Bots",
type: "integer",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BacktestId",
table: "Bots");
}
}
}

View File

@@ -305,6 +305,9 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<long>("AccumulatedRunTimeSeconds") b.Property<long>("AccumulatedRunTimeSeconds")
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<int?>("BacktestId")
.HasColumnType("integer");
b.Property<decimal>("BotTradingBalance") b.Property<decimal>("BotTradingBalance")
.HasColumnType("numeric"); .HasColumnType("numeric");

View File

@@ -41,6 +41,11 @@ public class BotEntity
public decimal BotTradingBalance { get; set; } public decimal BotTradingBalance { get; set; }
/// <summary>
/// The backtest ID associated with this bot (nullable for bots not created from backtests)
/// </summary>
public int? BacktestId { get; set; }
/// <summary> /// <summary>
/// The user ID of the master bot's owner when this bot is for copy trading /// The user ID of the master bot's owner when this bot is for copy trading
/// </summary> /// </summary>

View File

@@ -796,6 +796,7 @@ public static class PostgreSqlMappers
LongPositionCount = entity.LongPositionCount, LongPositionCount = entity.LongPositionCount,
ShortPositionCount = entity.ShortPositionCount, ShortPositionCount = entity.ShortPositionCount,
BotTradingBalance = entity.BotTradingBalance, BotTradingBalance = entity.BotTradingBalance,
BacktestId = entity.BacktestId,
MasterBotUserId = entity.MasterBotUserId, MasterBotUserId = entity.MasterBotUserId,
MasterBotUser = entity.MasterBotUser != null ? Map(entity.MasterBotUser) : null MasterBotUser = entity.MasterBotUser != null ? Map(entity.MasterBotUser) : null
}; };
@@ -830,6 +831,7 @@ public static class PostgreSqlMappers
LongPositionCount = bot.LongPositionCount, LongPositionCount = bot.LongPositionCount,
ShortPositionCount = bot.ShortPositionCount, ShortPositionCount = bot.ShortPositionCount,
BotTradingBalance = bot.BotTradingBalance, BotTradingBalance = bot.BotTradingBalance,
BacktestId = bot.BacktestId,
MasterBotUserId = bot.MasterBotUserId, MasterBotUserId = bot.MasterBotUserId,
UpdatedAt = DateTime.UtcNow UpdatedAt = DateTime.UtcNow
}; };