Files
Finlytic/FinlyticApp/lib/features/trades/widgets/trade_calculation_card.dart
T

263 lines
9.7 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../core/theme/app_theme.dart';
import '../models/trade_model.dart';
class TradeCalculationCard extends StatelessWidget {
final TradeModel trade;
final bool initiallyExpanded;
final bool isCollapsible;
const TradeCalculationCard({
super.key,
required this.trade,
this.initiallyExpanded = true,
this.isCollapsible = false,
});
@override
Widget build(BuildContext context) {
final entry = trade.averageBuyIn;
final quantity = trade.totalQuantity;
final posSize = entry * quantity;
final isShort = !trade.direction.isLong;
// Distance-to-stop risk, computed only from real DTO fields (averageBuyIn,
// currentStopLoss, totalQuantity) — no fee/leverage assumptions are
// invented since ActiveTradeDto no longer carries either (Rules.md §4).
final sl = trade.currentStopLoss;
final riskAmountAbs = (isShort ? (sl - entry) : (entry - sl)).abs() * quantity;
// Reward-to-target(s), same principle.
final stages = trade.exitPlan.takeProfitStages;
final primaryTp = trade.primaryTakeProfit;
final rewardAmountAbs = primaryTp != null ? (isShort ? (entry - primaryTp) : (primaryTp - entry)).abs() * quantity : null;
final crv = (rewardAmountAbs != null && riskAmountAbs > 0) ? (rewardAmountAbs / riskAmountAbs) : null;
final content = Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_statTile(
'Stückzahl',
'${quantity.toStringAsFixed(2)} Stk.',
Colors.white,
),
_statTile(
'Risiko bis Stop-Loss',
'-€${riskAmountAbs.toStringAsFixed(2)}',
AppTheme.accentRed,
),
_statTile(
'Potenzial bis 1. Ziel',
rewardAmountAbs != null ? '+€${rewardAmountAbs.toStringAsFixed(2)}' : 'Trailing-Exit',
AppTheme.primaryEmerald,
),
_statTile(
'Chance-Risiko (CRV)',
crv != null ? '1 : ${crv.toStringAsFixed(2)}' : '-',
AppTheme.accentCyan,
),
],
),
const Divider(color: Colors.white10, height: 16),
Text(
'Investiertes Kapital: €${posSize.toStringAsFixed(2)} • Exit-Strategie: ${trade.exitPlan.strategyType.label}',
style: TextStyle(color: AppTheme.textMuted, fontSize: 11),
),
if (stages.length > 1) ...[
const Divider(color: Colors.white10, height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'🎯 MEHRSTUFIGE GEWINN-KALKULATION',
style: TextStyle(
color: AppTheme.primaryEmerald,
fontSize: 11,
fontWeight: FontWeight.w900,
letterSpacing: 0.5,
),
),
Text(
'${stages.length} Ziele',
style: TextStyle(color: AppTheme.textMuted, fontSize: 10),
),
],
),
const SizedBox(height: 8),
...stages.map((stage) {
final targetPrice = stage.targetPrice;
final isCurrent = primaryTp != null && (primaryTp - targetPrice).abs() < 0.001;
final targetMove = (isShort ? (entry - targetPrice) : (targetPrice - entry));
final netTargetProfit = targetMove * quantity;
final cappedNet = netTargetProfit > 0 ? netTargetProfit : 0.0;
final retPct = posSize > 0 ? (cappedNet / posSize * 100) : 0.0;
final targetCrv = (riskAmountAbs > 0 && cappedNet > 0)
? (cappedNet / riskAmountAbs)
: 0.0;
final baseMove = entry > 0 ? (targetMove / entry * 100).abs() : 0.0;
return Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: isCurrent
? AppTheme.primaryEmerald.withValues(alpha: 0.14)
: Colors.white.withValues(alpha: 0.03),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isCurrent
? AppTheme.primaryEmerald.withValues(alpha: 0.7)
: Colors.white.withValues(alpha: 0.07),
width: isCurrent ? 1.5 : 1,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: isCurrent
? AppTheme.primaryEmerald
: Colors.white12,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'TP${stage.stageNumber}',
style: TextStyle(
color: isCurrent ? Colors.black : Colors.white,
fontSize: 10,
fontWeight: FontWeight.w900,
),
),
),
const SizedBox(width: 8),
Text(
'${targetPrice.toStringAsFixed(2)}',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 6),
Text(
'(+${baseMove.toStringAsFixed(1)}% Basiswert)',
style: TextStyle(
color: AppTheme.textMuted, fontSize: 10.5),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'+€${cappedNet.toStringAsFixed(2)} (+${retPct.toStringAsFixed(1)}%)',
style: TextStyle(
color: isCurrent
? AppTheme.primaryEmerald
: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w900,
),
),
if (targetCrv > 0)
Text(
'CRV 1 : ${targetCrv.toStringAsFixed(2)}',
style: TextStyle(
color: isCurrent
? AppTheme.primaryEmerald
: AppTheme.accentCyan,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
],
),
],
),
);
}),
],
],
),
);
if (!isCollapsible) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'3. LIVE-KALKULATION (AUTOMATISCH)',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w900,
letterSpacing: 0.5,
),
),
const SizedBox(height: 8),
content,
],
);
}
return Theme(
data: ThemeData(dividerColor: Colors.transparent),
child: ExpansionTile(
initiallyExpanded: initiallyExpanded,
tilePadding: EdgeInsets.zero,
childrenPadding: EdgeInsets.zero,
dense: true,
iconColor: AppTheme.primaryEmerald,
collapsedIconColor: Colors.white70,
title: Row(
children: [
Icon(Icons.calculate_outlined, color: AppTheme.primaryEmerald, size: 16),
const SizedBox(width: 8),
const Text(
'Live-Kalkulation & Gewinn-Potenzial',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
],
),
children: [
content,
],
),
);
}
Widget _statTile(String label, String value, Color col) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 10)),
const SizedBox(height: 2),
Text(value,
style: TextStyle(
color: col, fontWeight: FontWeight.bold, fontSize: 12.5)),
],
);
}
}