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 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 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 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 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((e) => EquityPointModel.fromJson(Map.from(e))) .toList() : const []; 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 get props => [ runId, isin, symbol, strategyKey, timeframe, totalTrades, winningTrades, losingTrades, winRatePercent, profitFactor, maxDrawdownPercent, totalReturnPercent, expectancyEur, sharpeRatio, equityCurve, ]; }