feat(app): add evaluation history, bot control panel, simulation visualizer, and vendored signalr_core
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
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}');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user