Files
Finlytic/FinlyticApp/lib/features/simulation/models/backtest_report_model.dart
T

129 lines
4.0 KiB
Dart

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,
];
}