feat(app): add evaluation history, bot control panel, simulation visualizer, and vendored signalr_core

This commit is contained in:
2026-08-24 21:37:43 +02:00
parent 676496b77d
commit 0894c40f07
113 changed files with 12413 additions and 3613 deletions
@@ -1,180 +0,0 @@
import 'package:equatable/equatable.dart';
import '../../trades/models/trade_model.dart';
class ExecutionPlanModel extends Equatable {
final double stopLoss;
final List<double> takeProfitTargets;
final double riskRewardRatio;
final double maxLeverage;
const ExecutionPlanModel({
this.stopLoss = 0.0,
this.takeProfitTargets = const [],
this.riskRewardRatio = 0.0,
this.maxLeverage = 1.0,
});
factory ExecutionPlanModel.fromJson(Map<String, dynamic> json) {
double parseDbl(dynamic v) => (v as num?)?.toDouble() ?? 0.0;
return ExecutionPlanModel(
stopLoss: parseDbl(json['stopLoss']),
takeProfitTargets: (json['takeProfitTargets'] as List<dynamic>? ?? []).map((e) => parseDbl(e)).toList(),
riskRewardRatio: parseDbl(json['riskRewardRatio']),
maxLeverage: parseDbl(json['maxLeverage']) == 0 ? 1.0 : parseDbl(json['maxLeverage']),
);
}
@override
List<Object?> get props => [stopLoss, takeProfitTargets, riskRewardRatio, maxLeverage];
}
class DetailedAnalysisModel extends Equatable {
final String technicalRationale;
final String fundamentalRationale;
final String riskWarning;
const DetailedAnalysisModel({
this.technicalRationale = '',
this.fundamentalRationale = '',
this.riskWarning = '',
});
factory DetailedAnalysisModel.fromJson(Map<String, dynamic> json) {
return DetailedAnalysisModel(
technicalRationale: json['technicalRationale']?.toString() ?? '',
fundamentalRationale: json['fundamentalRationale']?.toString() ?? '',
riskWarning: json['riskWarning']?.toString() ?? '',
);
}
@override
List<Object?> get props => [technicalRationale, fundamentalRationale, riskWarning];
}
class N8nAnalysisResponseDto extends Equatable {
final String aiDecision; // "Proceed", "Rejected", "Hold"
final String aiReasoning;
final int evalScore;
final String suggestedDirection; // "Long", "Short"
final String suggestedRisk;
final String suggestedTimeframe;
final ExecutionPlanModel? executionPlan;
final DetailedAnalysisModel? detailedAnalysis;
const N8nAnalysisResponseDto({
this.aiDecision = 'Rejected',
this.aiReasoning = '',
this.evalScore = 0,
this.suggestedDirection = 'Long',
this.suggestedRisk = 'Moderate',
this.suggestedTimeframe = '1D',
this.executionPlan,
this.detailedAnalysis,
});
factory N8nAnalysisResponseDto.fromJson(Map<String, dynamic> json) {
ExecutionPlanModel? execPlan;
if (json['executionPlan'] != null && json['executionPlan'] is Map<String, dynamic>) {
execPlan = ExecutionPlanModel.fromJson(json['executionPlan']);
}
DetailedAnalysisModel? detailAnalysis;
if (json['detailedAnalysis'] != null && json['detailedAnalysis'] is Map<String, dynamic>) {
detailAnalysis = DetailedAnalysisModel.fromJson(json['detailedAnalysis']);
}
return N8nAnalysisResponseDto(
aiDecision: json['aiDecision']?.toString() ?? 'Rejected',
aiReasoning: json['aiReasoning']?.toString() ?? '',
evalScore: (json['evalScore'] as num?)?.toInt() ?? 0,
suggestedDirection: json['suggestedDirection']?.toString() ?? 'Long',
suggestedRisk: json['suggestedRisk']?.toString() ?? 'Moderate',
suggestedTimeframe: json['suggestedTimeframe']?.toString() ?? '1D',
executionPlan: execPlan,
detailedAnalysis: detailAnalysis,
);
}
@override
List<Object?> get props => [
aiDecision,
aiReasoning,
evalScore,
suggestedDirection,
suggestedRisk,
suggestedTimeframe,
executionPlan,
detailedAnalysis,
];
}
class ManualAnalysisResponseDto extends Equatable {
final String analysisId;
final bool isTradeProposed;
final String status;
final String recommendation; // "RECOMMENDED", "NOT_RECOMMENDED"
final N8nAnalysisResponseDto? n8nResponse;
final TradeModel? proposal;
final String message;
const ManualAnalysisResponseDto({
required this.analysisId,
this.isTradeProposed = false,
this.status = 'Success',
this.recommendation = 'NOT_RECOMMENDED',
this.n8nResponse,
this.proposal,
this.message = '',
});
factory ManualAnalysisResponseDto.fromJson(Map<String, dynamic> json) {
N8nAnalysisResponseDto? n8n;
if (json['n8nResponse'] != null && json['n8nResponse'] is Map<String, dynamic>) {
n8n = N8nAnalysisResponseDto.fromJson(json['n8nResponse']);
}
TradeModel? prop;
if (json['proposal'] != null && json['proposal'] is Map<String, dynamic>) {
prop = TradeModel.fromJson(json['proposal']);
} else if (n8n != null) {
final exec = n8n.executionPlan;
final det = n8n.detailedAnalysis;
final isProceed = n8n.aiDecision.toLowerCase() == 'proceed';
final analysisIdStr = (json['analysisId'] ?? json['AnalysisId'])?.toString() ?? '';
final tradeIdStr = 'PROP-${analysisIdStr.length > 10 ? analysisIdStr.substring(0, 10).toUpperCase() : 'MANUAL'}';
prop = TradeModel(
id: tradeIdStr,
analysisId: analysisIdStr,
symbol: (json['symbol'] ?? json['Symbol'])?.toString() ?? '',
isin: (json['isin'] ?? json['Isin'])?.toString() ?? '',
status: isProceed ? 'Proposed' : 'Rejected',
signalType: n8n.suggestedDirection.toUpperCase() == 'SHORT' ? 'SELL' : 'BUY',
entryPrice: 0.0,
stopLoss: exec?.stopLoss ?? 0.0,
takeProfit: (exec?.takeProfitTargets.isNotEmpty ?? false) ? exec!.takeProfitTargets.first : 0.0,
reasoning: n8n.aiReasoning,
technicalRationale: det?.technicalRationale ?? '',
fundamentalRationale: det?.fundamentalRationale ?? '',
riskWarning: det?.riskWarning ?? '',
takeProfitTargets: exec?.takeProfitTargets ?? const [],
maxLeverage: exec?.maxLeverage ?? 1.0,
riskTolerance: n8n.suggestedRisk,
timeframe: n8n.suggestedTimeframe,
);
}
return ManualAnalysisResponseDto(
analysisId: (json['analysisId'] ?? json['AnalysisId'])?.toString() ?? '',
isTradeProposed: json['isTradeProposed'] == true || json['IsTradeProposed'] == true,
status: (json['status'] ?? json['Status'])?.toString() ?? 'Success',
recommendation: (json['recommendation'] ?? json['Recommendation'])?.toString() ?? 'NOT_RECOMMENDED',
n8nResponse: n8n,
proposal: prop,
message: (json['message'] ?? json['Message'])?.toString() ?? '',
);
}
@override
List<Object?> get props => [analysisId, isTradeProposed, status, recommendation, n8nResponse, proposal, message];
}