feat(app): add evaluation history, bot control panel, simulation visualizer, and vendored signalr_core

This commit is contained in:
2026-08-24 21:37:43 +02:00
parent 676496b77d
commit 0894c40f07
113 changed files with 12413 additions and 3613 deletions
@@ -0,0 +1,87 @@
import 'package:equatable/equatable.dart';
/// Typed counterpart of the backend `BacktestHistoryEntryDto`
/// (see `FinlyticCore/Dtos/Simulation/SimulationDtos.cs`) - one lightweight
/// row of a past backtest run, without the full trade list/equity curve
/// (fetch those via `SimulationRepository.getBacktestRunDetail` when the
/// user drills into a specific entry).
class BacktestHistoryEntryModel extends Equatable {
final String runId;
final String isin;
final String symbol;
final String strategyKey;
final String timeframe;
final DateTime startDateUtc;
final DateTime endDateUtc;
final int totalTrades;
final double winRatePercent;
final double profitFactor;
final double maxDrawdownPercent;
final double totalReturnPercent;
final double sharpeRatio;
final DateTime createdAtUtc;
const BacktestHistoryEntryModel({
required this.runId,
required this.isin,
required this.symbol,
required this.strategyKey,
required this.timeframe,
required this.startDateUtc,
required this.endDateUtc,
required this.totalTrades,
required this.winRatePercent,
required this.profitFactor,
required this.maxDrawdownPercent,
required this.totalReturnPercent,
required this.sharpeRatio,
required this.createdAtUtc,
});
factory BacktestHistoryEntryModel.fromJson(Map<String, dynamic> json) {
double parseDbl(dynamic val) {
if (val == null) return 0.0;
if (val is num) return val.toDouble();
return double.tryParse(val.toString()) ?? 0.0;
}
DateTime parseDate(dynamic val) {
return DateTime.tryParse(val?.toString() ?? '')?.toUtc() ?? DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
}
return BacktestHistoryEntryModel(
runId: json['runId']?.toString() ?? '',
isin: json['isin']?.toString() ?? '',
symbol: json['symbol']?.toString() ?? '',
strategyKey: json['strategyKey']?.toString() ?? '',
timeframe: json['timeframe']?.toString() ?? '',
startDateUtc: parseDate(json['startDateUtc']),
endDateUtc: parseDate(json['endDateUtc']),
totalTrades: (json['totalTrades'] as num?)?.toInt() ?? 0,
winRatePercent: parseDbl(json['winRatePercent']),
profitFactor: parseDbl(json['profitFactor']),
maxDrawdownPercent: parseDbl(json['maxDrawdownPercent']),
totalReturnPercent: parseDbl(json['totalReturnPercent']),
sharpeRatio: parseDbl(json['sharpeRatio']),
createdAtUtc: parseDate(json['createdAtUtc']),
);
}
@override
List<Object?> get props => [
runId,
isin,
symbol,
strategyKey,
timeframe,
startDateUtc,
endDateUtc,
totalTrades,
winRatePercent,
profitFactor,
maxDrawdownPercent,
totalReturnPercent,
sharpeRatio,
createdAtUtc,
];
}
@@ -0,0 +1,128 @@
import 'package:equatable/equatable.dart';
/// Typed counterpart of the backend `EquityPointDto`
/// (see `FinlyticCore/Dtos/Simulation/SimulationDtos.cs`).
class EquityPointModel extends Equatable {
final DateTime timestampUtc;
final double portfolioValue;
final double drawdownPercent;
const EquityPointModel({
required this.timestampUtc,
required this.portfolioValue,
required this.drawdownPercent,
});
factory EquityPointModel.fromJson(Map<String, dynamic> json) {
double parseDbl(dynamic val) {
if (val == null) return 0.0;
if (val is num) return val.toDouble();
return double.tryParse(val.toString()) ?? 0.0;
}
return EquityPointModel(
timestampUtc: DateTime.tryParse(json['timestampUtc']?.toString() ?? '') ?? DateTime.now(),
portfolioValue: parseDbl(json['portfolioValue']),
drawdownPercent: parseDbl(json['drawdownPercent']),
);
}
@override
List<Object?> get props => [timestampUtc, portfolioValue, drawdownPercent];
}
/// Typed counterpart of the backend `BacktestReportDto`
/// (see `FinlyticCore/Dtos/Simulation/SimulationDtos.cs`).
///
/// `equityCurve` is intentionally a plain (possibly empty) list rather than
/// a fallback with a synthetic starting point: an empty list means "no
/// equity curve data returned" and MUST be rendered as an explicit empty
/// state, never as an invented chart (Rules.md §4).
class BacktestReportModel extends Equatable {
final String runId;
final String isin;
final String symbol;
final String strategyKey;
final String timeframe;
final int totalTrades;
final int winningTrades;
final int losingTrades;
final double winRatePercent;
final double profitFactor;
final double maxDrawdownPercent;
final double totalReturnPercent;
final double expectancyEur;
final double sharpeRatio;
final List<EquityPointModel> equityCurve;
const BacktestReportModel({
this.runId = '',
this.isin = '',
this.symbol = '',
this.strategyKey = '',
this.timeframe = '',
this.totalTrades = 0,
this.winningTrades = 0,
this.losingTrades = 0,
this.winRatePercent = 0.0,
this.profitFactor = 0.0,
this.maxDrawdownPercent = 0.0,
this.totalReturnPercent = 0.0,
this.expectancyEur = 0.0,
this.sharpeRatio = 0.0,
this.equityCurve = const [],
});
factory BacktestReportModel.fromJson(Map<String, dynamic> json) {
double parseDbl(dynamic val) {
if (val == null) return 0.0;
if (val is num) return val.toDouble();
return double.tryParse(val.toString()) ?? 0.0;
}
final rawCurve = json['equityCurve'];
final curve = rawCurve is List
? rawCurve
.whereType<Map>()
.map((e) => EquityPointModel.fromJson(Map<String, dynamic>.from(e)))
.toList()
: const <EquityPointModel>[];
return BacktestReportModel(
runId: json['runId']?.toString() ?? '',
isin: json['isin']?.toString() ?? '',
symbol: json['symbol']?.toString() ?? '',
strategyKey: json['strategyKey']?.toString() ?? '',
timeframe: json['timeframe']?.toString() ?? '',
totalTrades: (json['totalTrades'] as num?)?.toInt() ?? 0,
winningTrades: (json['winningTrades'] as num?)?.toInt() ?? 0,
losingTrades: (json['losingTrades'] as num?)?.toInt() ?? 0,
winRatePercent: parseDbl(json['winRatePercent']),
profitFactor: parseDbl(json['profitFactor']),
maxDrawdownPercent: parseDbl(json['maxDrawdownPercent']),
totalReturnPercent: parseDbl(json['totalReturnPercent']),
expectancyEur: parseDbl(json['expectancyEur']),
sharpeRatio: parseDbl(json['sharpeRatio']),
equityCurve: curve,
);
}
@override
List<Object?> get props => [
runId,
isin,
symbol,
strategyKey,
timeframe,
totalTrades,
winningTrades,
losingTrades,
winRatePercent,
profitFactor,
maxDrawdownPercent,
totalReturnPercent,
expectancyEur,
sharpeRatio,
equityCurve,
];
}