Files

289 lines
11 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import '../../../core/theme/app_theme.dart';
import '../models/trade_model.dart';
import 'trade_calculation_card.dart';
/// Migrated onto `ActiveTradeDto`. The old "KI-Analysen, Bewertungen &
/// Begründungen" expansion (reasoning/technicalRationale/
/// fundamentalRationale/riskWarning, plus the hourly AI-Guardian check-in
/// timeline) has no backend equivalent anymore — none of those fields exist
/// on `ActiveTradeDto`, so the section was removed rather than shown empty
/// (Rules.md §4). This does leave the detail view noticeably thinner than
/// before: today it can only show the mechanical trade state (prices, exit
/// plan, fills), not any narrative "why" behind the trade.
class TradeDetailContent extends StatelessWidget {
final TradeModel trade;
const TradeDetailContent({super.key, required this.trade});
String _fmt(double val) => val.toStringAsFixed(2);
@override
Widget build(BuildContext context) {
final pnlAbs = trade.pnlEur;
final pnlPct = trade.unrealizedPnlPercent;
final isPnlPos = pnlAbs >= 0;
final pnlColor = isPnlPos ? AppTheme.primaryEmerald : AppTheme.accentRed;
final currPrice = trade.currentPrice;
final tpStages = trade.exitPlan.takeProfitStages;
final primaryTp = trade.primaryTakeProfit;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Drift Radar Status
if (trade.isActive) ...[
_buildDriftRadarCard(trade),
const SizedBox(height: 14),
],
// Metrics Grid
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_metricItem(
trade.isActive || trade.isClosed ? 'Einstiegskurs' : 'Ziel-Einstieg',
trade.averageBuyIn > 0 ? '${_fmt(trade.averageBuyIn)}' : '',
Colors.white,
),
_metricItem('Live-Kurs', currPrice > 0 ? '${_fmt(currPrice)}' : '', AppTheme.accentCyan),
_metricItem(
trade.driftStatus == DriftStatus.trailingActive ? 'Stop (Trailing)' : 'Stop-Loss',
'${_fmt(trade.currentStopLoss)}',
AppTheme.accentRed,
),
_metricItem(
tpStages.length > 1 ? 'TP (1. Stufe)' : 'Take-Profit',
primaryTp != null ? '${_fmt(primaryTp)}' : 'Trailing-Exit',
AppTheme.primaryEmerald,
),
],
),
),
if (tpStages.length > 1) ...[
const SizedBox(height: 10),
Row(
children: [
Text(
'Alle Gewinn-Ziele: ',
style: TextStyle(color: AppTheme.textMuted, fontSize: 12, fontWeight: FontWeight.bold),
),
Expanded(
child: Wrap(
spacing: 6,
runSpacing: 4,
children: tpStages.map((stage) {
final isCurrent = primaryTp != null && (primaryTp - stage.targetPrice).abs() < 0.01;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: isCurrent
? AppTheme.primaryEmerald.withValues(alpha: 0.2)
: Colors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isCurrent
? AppTheme.primaryEmerald
: Colors.white.withValues(alpha: 0.15),
),
),
child: Text(
'TP${stage.stageNumber}: €${_fmt(stage.targetPrice)}',
style: TextStyle(
color: isCurrent ? AppTheme.primaryEmerald : Colors.white70,
fontSize: 11,
fontWeight: isCurrent ? FontWeight.w900 : FontWeight.bold,
),
),
);
}).toList(),
),
),
],
),
],
if (trade.isActive || trade.isClosed) ...[
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: pnlColor.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: pnlColor.withValues(alpha: 0.3)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(trade.isClosed ? 'Realisierter PnL:' : 'Aktueller PnL:', style: const TextStyle(color: Colors.white70, fontSize: 13)),
Text(
'${isPnlPos ? '+' : ''}${pnlAbs.abs().toStringAsFixed(2)} (${isPnlPos ? '+' : ''}${pnlPct.toStringAsFixed(2)}%)',
style: TextStyle(color: pnlColor, fontWeight: FontWeight.bold, fontSize: 15),
),
],
),
),
],
const SizedBox(height: 20),
// LIVE-KALKULATION (AUTOMATISCH) & MEHRSTUFIGE TP-ZIELE
TradeCalculationCard(trade: trade),
const SizedBox(height: 18),
// Trade Parameters & Instrument
_sectionTitle(Icons.tune, 'Trade-Parameter & Instrument', Colors.white70),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.02),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withValues(alpha: 0.06)),
),
child: Column(
children: [
_paramRow('Instrument Typ:', trade.instrumentType.label),
if (trade.derivativeIsin != null && trade.derivativeIsin!.isNotEmpty) _paramRow('Derivat ISIN:', trade.derivativeIsin!),
_paramRow('Ausführungsart:', trade.executionMode.label),
_paramRow('Exit-Strategie:', trade.exitPlan.strategyType.label),
_paramRow('Eröffnet am:', _formatDate(trade.openedAtUtc)),
if (trade.closedAtUtc != null) _paramRow('Geschlossen am:', _formatDate(trade.closedAtUtc!)),
],
),
),
// Execution history (replaces the removed AI-Guardian hourly
// check-in timeline, which no backend DTO produces anymore — this is
// the trade's real fill history instead).
if (trade.fills.isNotEmpty) ...[
const SizedBox(height: 18),
_sectionTitle(Icons.history_toggle_off, 'Ausführungshistorie (${trade.fills.length} Fills)', AppTheme.accentCyan),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white.withValues(alpha: 0.06)),
),
child: Column(
children: trade.fills.reversed.map((f) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_formatDate(f.executedAtUtc),
style: TextStyle(color: AppTheme.textMuted, fontSize: 11, fontWeight: FontWeight.bold),
),
const SizedBox(width: 10),
Expanded(
child: Text(
'${_fmt(f.quantity)} Stk. @ €${_fmt(f.price)}${f.fee > 0 ? ' • Gebühr €${_fmt(f.fee)}' : ''}${f.note != null && f.note!.isNotEmpty ? '${f.note}' : ''}',
style: const TextStyle(color: Colors.white70, fontSize: 12),
),
),
],
),
);
}).toList(),
),
),
],
],
);
}
String _formatDate(DateTime d) =>
'${d.day.toString().padLeft(2, '0')}.${d.month.toString().padLeft(2, '0')}.${d.year} ${d.hour.toString().padLeft(2, '0')}:${d.minute.toString().padLeft(2, '0')}';
Widget _buildDriftRadarCard(TradeModel t) {
Color col;
String title;
String desc;
switch (t.driftStatus) {
case DriftStatus.trailingActive:
col = AppTheme.accentCyan;
title = 'Trailing Stop aktiv nachgezogen';
desc = 'Der aktuelle Stop-Loss wurde zur Absicherung von Gewinnen nachgezogen.';
break;
case DriftStatus.driftWarning:
col = Colors.orangeAccent;
title = 'Leichte Drift / Kursabweichung';
desc = 'Der unrealisierte Verlust hat die Warnschwelle überschritten.';
break;
case DriftStatus.onTrack:
col = AppTheme.primaryEmerald;
title = 'Auf Kurs';
desc = 'Keine besonderen Abweichungen erkannt.';
break;
}
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: col.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: col.withValues(alpha: 0.4)),
),
child: Row(
children: [
Icon(Icons.radar, color: col, size: 22),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Drift-Radar: $title', style: TextStyle(color: col, fontWeight: FontWeight.bold, fontSize: 13)),
Text(desc, style: TextStyle(color: AppTheme.textMuted, fontSize: 11)),
],
),
),
],
),
);
}
Widget _sectionTitle(IconData icon, String title, Color color) {
return Row(
children: [
Icon(icon, size: 16, color: color),
const SizedBox(width: 8),
Text(title, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 13)),
],
);
}
Widget _metricItem(String label, String value, Color color) {
return Column(
children: [
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 11)),
const SizedBox(height: 4),
Text(value, style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 13)),
],
);
}
Widget _paramRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 12)),
Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12)),
],
),
);
}
}