Files
Finlytic/FinlyticApp/lib/features/simulation/repositories/simulation_repository.dart
T

128 lines
4.9 KiB
Dart

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<BacktestReportModel> 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<String, double>? 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<String, dynamic>.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<List<BacktestHistoryEntryModel>> 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>()
.map((e) => BacktestHistoryEntryModel.fromJson(Map<String, dynamic>.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<BacktestReportModel> 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<String, dynamic>.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<Map<String, double>?> 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<String, dynamic>.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<void> saveStrategyParameters({
required String isin,
required String strategyKey,
required Map<String, double> 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}');
}
}
}