201 lines
7.5 KiB
Dart
201 lines
7.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../../core/theme/app_theme.dart';
|
|
import '../../../core/widgets/asset_logo_widget.dart';
|
|
import '../../../core/widgets/glass_container.dart';
|
|
import '../models/bot_models.dart';
|
|
|
|
class BotPositionCard extends StatelessWidget {
|
|
final BotTradeOrderModel position;
|
|
final VoidCallback? onClosePressed;
|
|
|
|
const BotPositionCard({
|
|
super.key,
|
|
required this.position,
|
|
this.onClosePressed,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isLong = position.isLong;
|
|
final isProfitable = position.unrealizedPnlEur >= 0;
|
|
final pnlPercent = position.pnlPercent;
|
|
final rMult = position.rMultiple;
|
|
|
|
return GlassContainer(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
AssetLogoWidget(
|
|
symbolOrName: position.symbol.isNotEmpty ? position.symbol : position.isin,
|
|
imageUrl: '/api/v1/logo/${position.isin}',
|
|
size: 36,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Text(
|
|
position.symbol,
|
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(4),
|
|
color: isLong ? AppTheme.primaryEmerald.withValues(alpha: 0.2) : AppTheme.accentRed.withValues(alpha: 0.2),
|
|
),
|
|
child: Text(
|
|
position.direction.toUpperCase(),
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: isLong ? AppTheme.primaryEmerald : AppTheme.accentRed,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
_buildVenueBadge(position.venue),
|
|
],
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
position.isin,
|
|
style: TextStyle(color: AppTheme.textMuted, fontSize: 11),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
'${isProfitable ? '+' : ''}€${position.unrealizedPnlEur.toStringAsFixed(2)}',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: isProfitable ? AppTheme.primaryEmerald : AppTheme.accentRed,
|
|
),
|
|
),
|
|
Text(
|
|
'${isProfitable ? '+' : ''}${pnlPercent.toStringAsFixed(2)}% (${rMult >= 0 ? '+' : ''}${rMult.toStringAsFixed(1)}R)',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
color: isProfitable ? AppTheme.primaryEmerald : AppTheme.accentRed,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
const Divider(height: 1, color: Colors.white10),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildPriceInfo('Entry / Buy-In', '€${position.averageBuyIn > 0 ? position.averageBuyIn.toStringAsFixed(2) : position.entryPrice.toStringAsFixed(2)}'),
|
|
_buildPriceInfo('Current Price', '€${position.currentPrice.toStringAsFixed(2)}'),
|
|
_buildPriceInfo('Stop Loss', '€${position.currentStopLoss.toStringAsFixed(2)}'),
|
|
_buildPriceInfo('Take Profit 1', '€${position.takeProfit1.toStringAsFixed(2)}'),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildDynamicStateBadge(position),
|
|
if (position.isActive && onClosePressed != null)
|
|
TextButton.icon(
|
|
onPressed: onClosePressed,
|
|
icon: Icon(Icons.close, size: 14, color: AppTheme.accentRed),
|
|
label: Text('Glattstellen', style: TextStyle(fontSize: 12, color: AppTheme.accentRed)),
|
|
|
|
style: TextButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
visualDensity: VisualDensity.compact,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPriceInfo(String label, String value) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 10)),
|
|
const SizedBox(height: 2),
|
|
Text(value, style: const TextStyle(color: Colors.white70, fontSize: 12, fontWeight: FontWeight.w600)),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildVenueBadge(String venue) {
|
|
final isAlpaca = venue.toLowerCase().contains('alpaca');
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(4),
|
|
color: isAlpaca ? Colors.blue.withValues(alpha: 0.15) : Colors.purple.withValues(alpha: 0.15),
|
|
),
|
|
child: Text(
|
|
isAlpaca ? 'Alpaca US' : 'Synthetic KO',
|
|
style: TextStyle(
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.w600,
|
|
color: isAlpaca ? Colors.lightBlueAccent : Colors.purpleAccent,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDynamicStateBadge(BotTradeOrderModel position) {
|
|
String text = 'Pending TP1';
|
|
Color color = Colors.amber;
|
|
|
|
if (position.isBreakEven) {
|
|
text = 'Free-Roll Active (BE)';
|
|
color = AppTheme.primaryEmerald;
|
|
} else if (position.isTp1Hit) {
|
|
text = 'TP1 Hit (Trailing Active)';
|
|
color = Colors.cyanAccent;
|
|
} else if (position.isClosed) {
|
|
text = 'Closed';
|
|
color = Colors.grey;
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
color: color.withValues(alpha: 0.15),
|
|
border: Border.all(color: color.withValues(alpha: 0.4), width: 1),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.bolt, size: 12, color: color),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
text,
|
|
style: TextStyle(color: color, fontSize: 11, fontWeight: FontWeight.bold),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|