feat(trades): add live execution cockpit, closing cockpit, calculation cards and precision trade settings

This commit is contained in:
2026-08-15 19:30:25 +02:00
parent 882d24a316
commit 34fa774cbf
31 changed files with 4235 additions and 630 deletions
@@ -10,7 +10,6 @@ import 'trade_execution_ai_plan_card.dart';
class TradeExecutionDialog {
static const double _defaultPositionSize = 1000.0;
static const double _defaultLeverage = 1.0;
static const List<String> _allowedInstruments = ['Stock', 'KnockOut', 'Option', 'CFD', 'Crypto'];
static String _normalizeInstrumentType(String raw) {
final clean = raw.toLowerCase().trim();
@@ -167,25 +166,56 @@ class TradeExecutionDialog {
return StatefulBuilder(
builder: (stfContext, setModalState) {
final isKnockout = selectedInstrumentType.toLowerCase().contains('knock') ||
selectedInstrumentType.toLowerCase().contains('zertifikat') ||
selectedInstrumentType.toLowerCase().contains('option') ||
selectedInstrumentType.toLowerCase().contains('cfd');
selectedInstrumentType.toLowerCase().contains('factor') ||
selectedInstrumentType.toLowerCase().contains('derivat');
final safeInstrumentValue = _allowedInstruments.contains(selectedInstrumentType) ? selectedInstrumentType : 'KnockOut';
final assetType = trade.assetType.toLowerCase();
final categories = trade.derivativeProductCategories;
final hasCfd = trade.hasCfd;
final availableOptions = <MapEntry<String, String>>[];
if (assetType == 'crypto') {
availableOptions.add(const MapEntry('Crypto', 'Krypto'));
if (hasCfd) availableOptions.add(const MapEntry('CFD', 'Krypto CFD'));
} else if (assetType == 'etf') {
availableOptions.add(const MapEntry('Stock', 'ETF (Direktinvestment)'));
if (categories.isEmpty || categories.contains('knockOutProduct')) {
availableOptions.add(const MapEntry('KnockOut', 'Knock-Out Zertifikat'));
}
if (categories.contains('vanillaWarrant')) {
availableOptions.add(const MapEntry('Option', 'Optionsschein'));
}
if (categories.contains('factorCertificate')) {
availableOptions.add(const MapEntry('Factor', 'Faktor-Zertifikat'));
}
if (hasCfd) availableOptions.add(const MapEntry('CFD', 'CFD (Hebel-Derivat)'));
} else {
availableOptions.add(const MapEntry('Stock', 'Aktie (Direktinvestment)'));
if (categories.isEmpty || categories.contains('knockOutProduct')) {
availableOptions.add(const MapEntry('KnockOut', 'Knock-Out Zertifikat'));
}
if (categories.contains('vanillaWarrant')) {
availableOptions.add(const MapEntry('Option', 'Optionsschein'));
}
if (categories.contains('factorCertificate')) {
availableOptions.add(const MapEntry('Factor', 'Faktor-Zertifikat'));
}
if (hasCfd) availableOptions.add(const MapEntry('CFD', 'CFD (Hebel-Derivat)'));
}
final safeInstrumentValue = availableOptions.any((o) => o.key == selectedInstrumentType)
? selectedInstrumentType
: (availableOptions.isNotEmpty ? availableOptions.first.key : 'Stock');
return AlertDialog(
backgroundColor: AppTheme.cardSurface,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: AppTheme.glassBorder)),
title: Row(
children: [
Icon(isActive ? Icons.tune : Icons.edit_note_outlined, color: AppTheme.primaryEmerald, size: 22),
Icon(Icons.flash_on, color: AppTheme.primaryEmerald),
const SizedBox(width: 8),
Expanded(
child: Text(
isActive ? 'Einstellungen für Trade #${trade.id}' : 'Trade-Ausführung & Parameter',
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
),
),
Text(isActive ? 'Aktiven Trade anpassen' : 'Trade-Vorschlag ausführen', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)),
],
),
content: SizedBox(
@@ -205,13 +235,9 @@ class TradeExecutionDialog {
initialValue: safeInstrumentValue,
dropdownColor: AppTheme.cardSurface,
decoration: const InputDecoration(labelText: 'Finanzinstrument Typ', contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8)),
items: const [
DropdownMenuItem(value: 'Stock', child: Text('Aktie / ETF (Direktinvestment)', style: TextStyle(color: Colors.white, fontSize: 13))),
DropdownMenuItem(value: 'KnockOut', child: Text('Knock-Out Zertifikat', style: TextStyle(color: Colors.white, fontSize: 13))),
DropdownMenuItem(value: 'Option', child: Text('Optionsschein / Derivat', style: TextStyle(color: Colors.white, fontSize: 13))),
DropdownMenuItem(value: 'CFD', child: Text('CFD (Hebel-Derivat)', style: TextStyle(color: Colors.white, fontSize: 13))),
DropdownMenuItem(value: 'Crypto', child: Text('Krypto', style: TextStyle(color: Colors.white, fontSize: 13))),
],
items: availableOptions.map((opt) {
return DropdownMenuItem(value: opt.key, child: Text(opt.value, style: const TextStyle(color: Colors.white, fontSize: 13)));
}).toList(),
onChanged: (val) {
if (val != null) setModalState(() => selectedInstrumentType = val);
},
@@ -318,6 +344,48 @@ class TradeExecutionDialog {
),
],
),
if (trade.takeProfitTargets.isNotEmpty) ...[
const SizedBox(height: 8),
Wrap(
spacing: 6,
runSpacing: 4,
children: trade.takeProfitTargets.asMap().entries.map((entry) {
final idx = entry.key;
final targetPrice = entry.value;
return ActionChip(
label: Text('TP${idx + 1}: €${targetPrice.toStringAsFixed(2)}', style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold)),
onPressed: () {
tpController.text = targetPrice.toStringAsFixed(2);
},
backgroundColor: Colors.white10,
side: BorderSide(color: AppTheme.primaryEmerald.withValues(alpha: 0.4)),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0),
);
}).toList(),
),
],
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.amber.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.amber.withValues(alpha: 0.25)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.gavel_outlined, size: 14, color: Colors.amber.withValues(alpha: 0.85)),
const SizedBox(width: 8),
Expanded(
child: Text(
'Rechtlicher Hinweis: Keine Anlageberatung. Sämtliche Angaben dienen ausschließlich Informationszwecken. Hebelprodukte bergen ein hohes Verlustrisiko bis hin zum Totalverlust.',
style: TextStyle(color: AppTheme.textMuted, fontSize: 10, height: 1.3),
),
),
],
),
),
],
),
),