import 'package:dio/dio.dart'; import '../../../core/network/api_client.dart'; import '../models/backtest_history_entry_model.dart'; import '../models/backtest_report_model.dart'; /// Backend endpoint contract: `FinlyticBackend/Controllers/SimulationController.cs` /// (`POST /api/v1/simulation/run`, body shape `BacktestRequestDto` in /// `FinlyticCore/Dtos/Simulation/SimulationDtos.cs`). class SimulationRepository { final ApiClient apiClient; const SimulationRepository({required this.apiClient}); Future runBacktest({ required String isin, required String symbol, required String strategyKey, required String timeframe, DateTime? startDateUtc, DateTime? endDateUtc, double startingCapital = 10000.0, double riskPerTradePercent = 1.0, bool includeFeesAndSlippage = true, bool simulateKnockOutDerivatives = false, double? targetLeverage, Map? strategyParameters, }) async { final now = DateTime.now().toUtc(); final start = (startDateUtc ?? now.subtract(const Duration(days: 180))).toUtc(); final end = (endDateUtc ?? now).toUtc(); final response = await apiClient.post('/api/v1/simulation/run', data: { 'isin': isin, 'symbol': symbol, 'strategyKey': strategyKey, 'timeframe': timeframe, 'startDateUtc': start.toIso8601String(), 'endDateUtc': end.toIso8601String(), 'startingCapital': startingCapital, 'riskPerTradePercent': riskPerTradePercent, 'includeFeesAndSlippage': includeFeesAndSlippage, 'simulateKnockOutDerivatives': simulateKnockOutDerivatives, if (targetLeverage != null) 'targetLeverage': targetLeverage, if (strategyParameters != null && strategyParameters.isNotEmpty) 'strategyParameters': strategyParameters, }); if (response.statusCode == 200 && response.data is Map) { return BacktestReportModel.fromJson(Map.from(response.data as Map)); } throw Exception('Simulation fehlgeschlagen: Status ${response.statusCode}'); } /// Lightweight history of past backtest runs for an asset (`GET /api/v1/simulation/history/{isin}`). Future> getBacktestHistory({ required String isin, String? strategyKey, int limit = 20, }) async { final response = await apiClient.get( '/api/v1/simulation/history/$isin', queryParameters: { if (strategyKey != null && strategyKey.isNotEmpty) 'strategyKey': strategyKey, 'limit': limit, }, ); if (response.statusCode == 200 && response.data is List) { return (response.data as List) .whereType() .map((e) => BacktestHistoryEntryModel.fromJson(Map.from(e))) .toList(); } throw Exception('Backtest-Verlauf konnte nicht geladen werden: Status ${response.statusCode}'); } /// Full report (trades + equity curve) for one specific past run (`GET /api/v1/simulation/history/run/{runId}`). Future getBacktestRunDetail(String runId) async { final response = await apiClient.get('/api/v1/simulation/history/run/$runId'); if (response.statusCode == 200 && response.data is Map) { return BacktestReportModel.fromJson(Map.from(response.data as Map)); } throw Exception('Backtest-Lauf konnte nicht geladen werden: Status ${response.statusCode}'); } /// Saved indicator-parameter profile for one (isin, strategyKey) pair /// (`GET /api/v1/simulation/parameters/{isin}/{strategyKey}`). Returns `null` when none was ever saved - /// an expected, legitimate empty state (the server responds 404), not an error to surface to the user. Future?> getStrategyParameters({ required String isin, required String strategyKey, }) async { try { final response = await apiClient.get('/api/v1/simulation/parameters/$isin/$strategyKey'); if (response.statusCode == 200 && response.data is Map) { final raw = Map.from(response.data as Map)['parameters']; if (raw is Map) { return raw.map((key, value) => MapEntry(key.toString(), (value as num).toDouble())); } } return null; } on DioException catch (e) { if (e.response?.statusCode == 404) return null; rethrow; } } /// Saves/updates a per-asset/per-strategy indicator-parameter profile (`POST /api/v1/simulation/parameters`). Future saveStrategyParameters({ required String isin, required String strategyKey, required Map parameters, }) async { final response = await apiClient.post('/api/v1/simulation/parameters', data: { 'isin': isin, 'strategyKey': strategyKey, 'parameters': parameters, }); if (response.statusCode != 200) { throw Exception('Parameter-Profil konnte nicht gespeichert werden: Status ${response.statusCode}'); } } }