import 'package:equatable/equatable.dart'; enum DriftStatus { onTrack, trailingActive, driftWarning, exitAlert, } class TradeHourlyUpdateModel extends Equatable { final String recommendation; final double currentPrice; final double? suggestedStopLoss; final double? suggestedTakeProfit; final double vixValue; final String reasoning; final DateTime timestamp; const TradeHourlyUpdateModel({ required this.recommendation, required this.currentPrice, this.suggestedStopLoss, this.suggestedTakeProfit, this.vixValue = 0.0, required this.reasoning, required this.timestamp, }); factory TradeHourlyUpdateModel.fromJson(Map json) { double parseDbl(dynamic val) { if (val == null) return 0.0; if (val is num) return val.toDouble(); return double.tryParse(val.toString()) ?? 0.0; } DateTime ts = DateTime.now(); final tsStr = (json['timestamp'] ?? json['Timestamp'])?.toString(); if (tsStr != null && tsStr.isNotEmpty) { ts = DateTime.tryParse(tsStr) ?? DateTime.now(); } return TradeHourlyUpdateModel( recommendation: (json['recommendation'] ?? json['Recommendation'])?.toString() ?? 'Hold', currentPrice: parseDbl(json['currentPrice'] ?? json['CurrentPrice']), suggestedStopLoss: json['suggestedStopLoss'] != null ? parseDbl(json['suggestedStopLoss'] ?? json['SuggestedStopLoss']) : null, suggestedTakeProfit: json['suggestedTakeProfit'] != null ? parseDbl(json['suggestedTakeProfit'] ?? json['SuggestedTakeProfit']) : null, vixValue: parseDbl(json['vixValue'] ?? json['VixValue']), reasoning: (json['reasoning'] ?? json['Reasoning'])?.toString() ?? '', timestamp: ts, ); } @override List get props => [recommendation, currentPrice, suggestedStopLoss, suggestedTakeProfit, reasoning, timestamp]; } class TradeModel extends Equatable { final String id; final String analysisId; final String status; final bool isGlobalProposal; final String userId; final String symbol; final String isin; final String companyName; final String sector; final String signalType; final double entryPrice; final double actualEntryPrice; final double currentPrice; final double stopLoss; final double takeProfit; final double positionSize; final double leverageUsed; final double pnlAbsolute; final double pnlPercent; final String reasoning; final String technicalRationale; final String fundamentalRationale; final String riskWarning; final double winRate; final String timeframe; final String instrumentType; final String assetType; final bool hasCfd; final List derivativeProductCategories; final String derivativeIsin; final DateTime? createdAt; final String riskTolerance; final double vixValue; final String vixRegime; final List takeProfitTargets; final double maxLeverage; final double entryZoneMin; final double entryZoneMax; final double entryFee; final double exitFee; final double quantity; final String closeReason; final DateTime? userExitTimestamp; final bool hasPendingExitAlert; final String pendingExitReason; final List hourlyUpdates; const TradeModel({ required this.id, this.analysisId = '', this.status = 'Active', this.isGlobalProposal = false, this.userId = '', required this.symbol, this.isin = '', this.companyName = '', this.sector = '', required this.signalType, required this.entryPrice, this.actualEntryPrice = 0.0, this.currentPrice = 0.0, required this.stopLoss, required this.takeProfit, this.positionSize = 0.0, this.leverageUsed = 1.0, this.pnlAbsolute = 0.0, this.pnlPercent = 0.0, this.reasoning = '', this.technicalRationale = '', this.fundamentalRationale = '', this.riskWarning = '', this.winRate = 50.0, this.timeframe = '1D', this.instrumentType = 'Stock', this.assetType = 'stock', this.hasCfd = false, this.derivativeProductCategories = const [], this.derivativeIsin = '', this.createdAt, this.riskTolerance = 'Moderate', this.vixValue = 0.0, this.vixRegime = 'Normal', this.takeProfitTargets = const [], this.maxLeverage = 1.0, this.entryZoneMin = 0.0, this.entryZoneMax = 0.0, this.entryFee = 0.0, this.exitFee = 0.0, this.quantity = 0.0, this.closeReason = '', this.userExitTimestamp, this.hasPendingExitAlert = false, this.pendingExitReason = '', this.hourlyUpdates = const [], }); bool get isActive => status.toLowerCase() == 'active'; bool get isClosed => status.toLowerCase() == 'closed'; bool get isRejected => status.toLowerCase() == 'rejected'; bool get isProposed => (status.toLowerCase() == 'proposed' || isGlobalProposal) && !isRejected && !isActive && !isClosed; DriftStatus get driftStatus { if (hasPendingExitAlert) return DriftStatus.exitAlert; if (hourlyUpdates.any((u) => u.recommendation.toLowerCase().contains('adjustsl') || u.recommendation.toLowerCase().contains('trailing'))) { return DriftStatus.trailingActive; } if (calculatedPnlPct < -3.5) return DriftStatus.driftWarning; return DriftStatus.onTrack; } double get effectiveCurrentPrice { if (currentPrice > 0) return currentPrice; if (actualEntryPrice > 0) return actualEntryPrice; return entryPrice; } double get actualExitPrice => currentPrice; double get calculatedPnlAbs { if (isClosed && pnlAbsolute != 0) return pnlAbsolute; final curr = currentPrice; if (curr <= 0) return pnlAbsolute; final entry = actualEntryPrice > 0 ? actualEntryPrice : entryPrice; if (entry <= 0) return 0.0; final isShort = signalType == 'SELL' || signalType == 'SHORT'; final rawMove = isShort ? ((entry - curr) / entry) : ((curr - entry) / entry); final posSize = positionSize > 0 ? positionSize : (quantity > 0 ? quantity * entry : entry); final lev = leverageUsed > 0 ? leverageUsed : 1.0; final fees = entryFee + exitFee; return (rawMove * posSize * lev) - fees; } double get calculatedPnlPct { if (isClosed && pnlPercent != 0) return pnlPercent; final pnlAbs = calculatedPnlAbs; final posSize = positionSize > 0 ? positionSize : (actualEntryPrice > 0 ? actualEntryPrice : (entryPrice > 0 ? entryPrice : 1.0)); if (posSize <= 0) return 0.0; return (pnlAbs / posSize) * 100.0; } double calculateLivePnlAbs(double livePrice) { if (isClosed && pnlAbsolute != 0) return pnlAbsolute; final curr = livePrice > 0 ? livePrice : currentPrice; if (curr <= 0) return pnlAbsolute; final entry = actualEntryPrice > 0 ? actualEntryPrice : entryPrice; if (entry <= 0) return 0.0; final isShort = signalType == 'SELL' || signalType == 'SHORT'; final rawMove = isShort ? ((entry - curr) / entry) : ((curr - entry) / entry); final posSize = positionSize > 0 ? positionSize : (quantity > 0 ? quantity * entry : entry); final lev = leverageUsed > 0 ? leverageUsed : 1.0; final fees = entryFee + exitFee; return (rawMove * posSize * lev) - fees; } double calculateLivePnlPct(double livePrice) { if (isClosed && pnlPercent != 0) return pnlPercent; final pnlAbs = calculateLivePnlAbs(livePrice); final posSize = positionSize > 0 ? positionSize : (actualEntryPrice > 0 ? actualEntryPrice : (entryPrice > 0 ? entryPrice : 1.0)); if (posSize <= 0) return 0.0; return (pnlAbs / posSize) * 100.0; } factory TradeModel.fromJson(Map json) { double parseDbl(dynamic val) { if (val == null) return 0.0; if (val is num) return val.toDouble(); return double.tryParse(val.toString()) ?? 0.0; } final idVal = (json['tradeId'] ?? json['TradeId'] ?? json['id'] ?? json['Id'])?.toString() ?? ''; final sig = (json['signalType'] ?? json['SignalType'] ?? json['side'] ?? json['Side'])?.toString() ?? 'BUY'; final entry = parseDbl(json['entryPrice'] ?? json['EntryPrice']); final actualEntry = parseDbl(json['actualEntryPrice'] ?? json['ActualEntryPrice']); final currPrice = parseDbl(json['currentPrice'] ?? json['CurrentPrice'] ?? json['price'] ?? json['Price']); final sl = parseDbl(json['stopLoss'] ?? json['StopLoss']); final tp = parseDbl(json['takeProfit'] ?? json['TakeProfit']); final pnlAbs = parseDbl(json['pnlAbsolute'] ?? json['PnlAbsolute'] ?? json['pnl'] ?? json['Pnl']); final pnlPct = parseDbl(json['pnlPercent'] ?? json['PnlPercent']); DateTime? dt; final createdStr = (json['createdAt'] ?? json['CreatedAt'])?.toString(); if (createdStr != null && createdStr.isNotEmpty) { dt = DateTime.tryParse(createdStr); } DateTime? exitDt; final exitStr = (json['userExitTimestamp'] ?? json['UserExitTimestamp'])?.toString(); if (exitStr != null && exitStr.isNotEmpty) { exitDt = DateTime.tryParse(exitStr); } List updates = []; final rawUpdates = json['hourlyUpdates'] ?? json['HourlyUpdates']; if (rawUpdates is List) { updates = rawUpdates.map((u) => TradeHourlyUpdateModel.fromJson(Map.from(u))).toList(); } return TradeModel( id: idVal, analysisId: (json['analysisId'] ?? json['AnalysisId'])?.toString() ?? '', status: (json['status'] ?? json['Status'])?.toString() ?? 'Active', isGlobalProposal: json['isGlobalProposal'] == true || json['IsGlobalProposal'] == true, userId: (json['userId'] ?? json['UserId'])?.toString() ?? '', symbol: (json['symbol'] ?? json['Symbol'])?.toString() ?? 'UNKNOWN', isin: (json['isin'] ?? json['Isin'])?.toString() ?? '', companyName: (json['companyName'] ?? json['CompanyName'])?.toString() ?? '', sector: (json['sector'] ?? json['Sector'])?.toString() ?? '', signalType: sig.toUpperCase(), entryPrice: entry, actualEntryPrice: actualEntry, currentPrice: currPrice, stopLoss: sl, takeProfit: tp, positionSize: parseDbl(json['positionSize'] ?? json['PositionSize']), leverageUsed: parseDbl(json['leverageUsed'] ?? json['LeverageUsed']) == 0 ? 1.0 : parseDbl(json['leverageUsed'] ?? json['LeverageUsed']), pnlAbsolute: pnlAbs, pnlPercent: pnlPct, reasoning: (json['reasoning'] ?? json['Reasoning'])?.toString() ?? '', technicalRationale: (json['technicalRationale'] ?? json['TechnicalRationale'])?.toString() ?? '', fundamentalRationale: (json['fundamentalRationale'] ?? json['FundamentalRationale'])?.toString() ?? '', riskWarning: (json['riskWarning'] ?? json['RiskWarning'])?.toString() ?? '', winRate: parseDbl(json['winRate'] ?? json['WinRate']), timeframe: (json['timeframe'] ?? json['Timeframe'])?.toString() ?? '1D', instrumentType: (json['instrumentType'] ?? json['InstrumentType'])?.toString() ?? 'Stock', assetType: (json['assetType'] ?? json['AssetType'])?.toString() ?? 'stock', hasCfd: json['hasCfd'] == true || json['HasCfd'] == true, derivativeProductCategories: (json['derivativeProductCategories'] ?? json['DerivativeProductCategories']) is List ? ((json['derivativeProductCategories'] ?? json['DerivativeProductCategories']) as List).map((e) => e.toString()).toList() : const [], derivativeIsin: (json['derivativeIsin'] ?? json['DerivativeIsin'] ?? json['knockoutIsin'] ?? json['KnockoutIsin'])?.toString() ?? '', createdAt: dt, riskTolerance: (json['riskTolerance'] ?? json['RiskTolerance'])?.toString() ?? 'Moderate', vixValue: parseDbl(json['vixValue'] ?? json['VixValue']), vixRegime: (json['vixRegime'] ?? json['VixRegime'])?.toString() ?? 'Normal', takeProfitTargets: (json['takeProfitTargets'] ?? json['TakeProfitTargets']) is List ? ((json['takeProfitTargets'] ?? json['TakeProfitTargets']) as List).map((e) => parseDbl(e)).toList() : [], maxLeverage: parseDbl(json['maxLeverage'] ?? json['MaxLeverage']), entryZoneMin: parseDbl(json['entryZoneMin'] ?? json['EntryZoneMin']), entryZoneMax: parseDbl(json['entryZoneMax'] ?? json['EntryZoneMax']), entryFee: parseDbl(json['entryFee'] ?? json['EntryFee']), exitFee: parseDbl(json['exitFee'] ?? json['ExitFee']), quantity: parseDbl(json['quantity'] ?? json['Quantity']), closeReason: (json['closeReason'] ?? json['CloseReason'])?.toString() ?? '', userExitTimestamp: exitDt, hasPendingExitAlert: json['hasPendingExitAlert'] == true || json['HasPendingExitAlert'] == true, pendingExitReason: (json['pendingExitReason'] ?? json['PendingExitReason'])?.toString() ?? '', hourlyUpdates: updates, ); } Map toJson() { return { 'tradeId': id, 'analysisId': analysisId, 'status': status, 'isGlobalProposal': isGlobalProposal, 'userId': userId, 'symbol': symbol, 'isin': isin, 'companyName': companyName, 'sector': sector, 'signalType': signalType, 'entryPrice': entryPrice, 'actualEntryPrice': actualEntryPrice, 'currentPrice': currentPrice, 'stopLoss': stopLoss, 'takeProfit': takeProfit, 'positionSize': positionSize, 'leverageUsed': leverageUsed, 'pnlAbsolute': pnlAbsolute, 'pnlPercent': pnlPercent, 'reasoning': reasoning, 'technicalRationale': technicalRationale, 'fundamentalRationale': fundamentalRationale, 'riskWarning': riskWarning, 'winRate': winRate, 'timeframe': timeframe, 'instrumentType': instrumentType, 'assetType': assetType, 'hasCfd': hasCfd, 'derivativeProductCategories': derivativeProductCategories, 'derivativeIsin': derivativeIsin, 'createdAt': createdAt?.toIso8601String(), 'riskTolerance': riskTolerance, 'vixValue': vixValue, 'vixRegime': vixRegime, 'takeProfitTargets': takeProfitTargets, 'maxLeverage': maxLeverage, 'entryZoneMin': entryZoneMin, 'entryZoneMax': entryZoneMax, 'entryFee': entryFee, 'exitFee': exitFee, 'quantity': quantity, 'closeReason': closeReason, 'userExitTimestamp': userExitTimestamp?.toIso8601String(), 'hasPendingExitAlert': hasPendingExitAlert, 'pendingExitReason': pendingExitReason, }; } @override List get props => [ id, analysisId, status, isGlobalProposal, userId, symbol, isin, signalType, entryPrice, currentPrice, pnlAbsolute, pnlPercent, hasPendingExitAlert, hourlyUpdates, ]; }