export interface RuntimePositionSnapshot { id: string; symbol: string; side: 'BUY' | 'SELL'; size: number; entryPrice: number; currentPrice: number; stopLoss: number; takeProfit: number; unrealizedPnl: number; unrealizedPnlPercent: number; marketValue: number; userId?: string; profileId?: string; profileName?: string; tradeId?: string; } export interface RuntimeOrderSnapshot { id: string; symbol: string; type: string; side: string; qty: number; price: number; status: string; timestamp: number; userId?: string; profileId?: string; trade_id?: string; subTag?: string; action?: string; source?: 'BOT' | 'MANUAL'; created_at?: string; } const STABLE_SYNC_SUFFIX = '-SYNC'; const normalizeTradeId = (value?: string): string => String(value || '').trim(); const hasStableTradeId = (tradeId?: string): boolean => { const normalized = normalizeTradeId(tradeId); return normalized.length > 0 && !normalized.endsWith(STABLE_SYNC_SUFFIX); }; const toTimestamp = (value: number | string | undefined, createdAt?: string): number => { if (typeof value === 'number' && Number.isFinite(value)) return value; if (typeof value === 'string') { const numeric = Number(value); if (Number.isFinite(numeric) && numeric > 0) return numeric; const parsed = Date.parse(value); if (Number.isFinite(parsed) && parsed > 0) return parsed; } if (createdAt) { const parsedCreatedAt = Date.parse(createdAt); if (Number.isFinite(parsedCreatedAt) && parsedCreatedAt > 0) return parsedCreatedAt; } return 0; }; const toNumber = (value: unknown): number => { const numeric = Number(value); return Number.isFinite(numeric) ? numeric : 0; }; const positive = (value: unknown): number => { const numeric = toNumber(value); return numeric > 0 ? numeric : 0; }; const positionScore = (position: RuntimePositionSnapshot): number => { const tradeScore = hasStableTradeId(position.tradeId) ? 4 : (normalizeTradeId(position.tradeId) ? 2 : 0); const profileScore = position.profileId ? 3 : 0; const userScore = position.userId ? 2 : 0; const namedScore = position.profileName ? 1 : 0; const priceScore = positive(position.currentPrice) > 0 ? 1 : 0; const notionalScore = positive(position.entryPrice) * positive(position.size); return tradeScore + profileScore + userScore + namedScore + priceScore + Math.min(notionalScore, 100_000); }; const mergePosition = ( left: RuntimePositionSnapshot, right: RuntimePositionSnapshot ): RuntimePositionSnapshot => { const leftScore = positionScore(left); const rightScore = positionScore(right); const preferred = rightScore >= leftScore ? right : left; const fallback = preferred === right ? left : right; return { ...fallback, ...preferred, id: preferred.id || fallback.id, symbol: preferred.symbol || fallback.symbol, side: preferred.side || fallback.side, size: positive(preferred.size) > 0 ? preferred.size : fallback.size, entryPrice: positive(preferred.entryPrice) > 0 ? preferred.entryPrice : fallback.entryPrice, currentPrice: positive(preferred.currentPrice) > 0 ? preferred.currentPrice : fallback.currentPrice, stopLoss: positive(preferred.stopLoss) > 0 ? preferred.stopLoss : fallback.stopLoss, takeProfit: positive(preferred.takeProfit) > 0 ? preferred.takeProfit : fallback.takeProfit, marketValue: positive(preferred.marketValue) > 0 ? preferred.marketValue : fallback.marketValue, profileId: preferred.profileId || fallback.profileId, userId: preferred.userId || fallback.userId, profileName: preferred.profileName || fallback.profileName, tradeId: normalizeTradeId(preferred.tradeId) || normalizeTradeId(fallback.tradeId) || undefined }; }; const fallbackPositionKey = (position: RuntimePositionSnapshot): string => { const owner = `${position.userId || 'global'}:${position.profileId || 'global'}`; return `${owner}:${position.symbol}:${position.side}`; }; const orderStatusRank = (status?: string): number => { const normalized = String(status || '').trim().toLowerCase(); if (normalized === 'filled') return 6; if (normalized === 'partially_filled' || normalized === 'partially-filled') return 5; if (normalized === 'canceled' || normalized === 'cancelled' || normalized === 'rejected' || normalized === 'expired') return 4; if (normalized === 'pending_new' || normalized === 'pending' || normalized === 'accepted' || normalized === 'new') return 2; if (normalized === 'unknown') return 1; return 2; }; const pickReliableStatus = (left: RuntimeOrderSnapshot, right: RuntimeOrderSnapshot): string => { const leftStatus = String(left.status || '').trim().toLowerCase() || 'unknown'; const rightStatus = String(right.status || '').trim().toLowerCase() || 'unknown'; const leftRank = orderStatusRank(leftStatus); const rightRank = orderStatusRank(rightStatus); if (leftRank !== rightRank) { return rightRank > leftRank ? rightStatus : leftStatus; } const leftTs = toTimestamp(left.timestamp, left.created_at); const rightTs = toTimestamp(right.timestamp, right.created_at); return rightTs >= leftTs ? rightStatus : leftStatus; }; const normalizeOrderSource = (order: RuntimeOrderSnapshot): 'BOT' | 'MANUAL' => { const normalized = String(order.source || '').trim().toUpperCase(); if (normalized === 'BOT' || normalized === 'MANUAL') return normalized; return order.profileId ? 'BOT' : 'MANUAL'; }; const orderScore = (order: RuntimeOrderSnapshot): number => { const profileScore = order.profileId ? 2 : 0; const userScore = order.userId ? 1 : 0; const tradeScore = normalizeTradeId(order.trade_id) ? 2 : 0; const actionScore = order.action ? 1 : 0; const statusScore = orderStatusRank(order.status); return profileScore + userScore + tradeScore + actionScore + statusScore; }; const mergeOrder = ( left: RuntimeOrderSnapshot, right: RuntimeOrderSnapshot ): RuntimeOrderSnapshot => { const leftScore = orderScore(left); const rightScore = orderScore(right); const preferred = rightScore >= leftScore ? right : left; const fallback = preferred === right ? left : right; const preferredTs = toTimestamp(preferred.timestamp, preferred.created_at); const fallbackTs = toTimestamp(fallback.timestamp, fallback.created_at); return { ...fallback, ...preferred, id: preferred.id || fallback.id, symbol: preferred.symbol || fallback.symbol, type: preferred.type || fallback.type, side: preferred.side || fallback.side, qty: positive(preferred.qty) > 0 ? preferred.qty : fallback.qty, price: positive(preferred.price) > 0 ? preferred.price : fallback.price, timestamp: Math.max(preferredTs, fallbackTs), status: pickReliableStatus(left, right), userId: preferred.userId || fallback.userId, profileId: preferred.profileId || fallback.profileId, trade_id: normalizeTradeId(preferred.trade_id) || normalizeTradeId(fallback.trade_id) || undefined, subTag: preferred.subTag || fallback.subTag, action: preferred.action || fallback.action, source: normalizeOrderSource({ ...fallback, ...preferred, profileId: preferred.profileId || fallback.profileId }) }; }; export const mergePositionSnapshots = ( snapshotsBySource: RuntimePositionSnapshot[][] ): RuntimePositionSnapshot[] => { const mergedByKey = new Map(); for (const position of snapshotsBySource.flat()) { const tradeId = normalizeTradeId(position.tradeId); const key = hasStableTradeId(tradeId) ? `trade:${tradeId}` : `fallback:${fallbackPositionKey(position)}`; const existing = mergedByKey.get(key); if (!existing) { mergedByKey.set(key, { ...position, tradeId: tradeId || undefined }); continue; } mergedByKey.set(key, mergePosition(existing, { ...position, tradeId: tradeId || undefined })); } const groupedByOwnerSymbol = new Map(); for (const position of mergedByKey.values()) { const groupKey = fallbackPositionKey(position); const group = groupedByOwnerSymbol.get(groupKey) || []; group.push(position); groupedByOwnerSymbol.set(groupKey, group); } const output: RuntimePositionSnapshot[] = []; for (const group of groupedByOwnerSymbol.values()) { const stable = group.filter((position) => hasStableTradeId(position.tradeId)); if (stable.length > 0) { output.push(...stable); continue; } const reduced = group.reduce((acc, current) => (acc ? mergePosition(acc, current) : current), undefined as RuntimePositionSnapshot | undefined); if (reduced) output.push(reduced); } return output.sort((a, b) => { const profileCompare = String(a.profileId || '').localeCompare(String(b.profileId || '')); if (profileCompare !== 0) return profileCompare; const symbolCompare = String(a.symbol || '').localeCompare(String(b.symbol || '')); if (symbolCompare !== 0) return symbolCompare; return String(a.tradeId || a.id).localeCompare(String(b.tradeId || b.id)); }); }; export const mergeOrderSnapshots = ( snapshotsBySource: RuntimeOrderSnapshot[][] ): RuntimeOrderSnapshot[] => { const mergedByOrderId = new Map(); for (const order of snapshotsBySource.flat()) { const orderId = String(order.id || '').trim(); if (!orderId) continue; const normalizedOrder: RuntimeOrderSnapshot = { ...order, id: orderId, userId: order.userId || undefined, profileId: order.profileId || undefined, trade_id: normalizeTradeId(order.trade_id) || undefined, subTag: String((order as any).subTag || (order as any).subtag || (order as any).sub_tag || '').trim() || undefined, status: String(order.status || 'unknown').toLowerCase(), timestamp: toTimestamp(order.timestamp, order.created_at), source: normalizeOrderSource(order) }; const existing = mergedByOrderId.get(orderId); if (!existing) { mergedByOrderId.set(orderId, normalizedOrder); continue; } mergedByOrderId.set(orderId, mergeOrder(existing, normalizedOrder)); } return Array.from(mergedByOrderId.values()).sort((a, b) => { const tsDiff = toTimestamp(b.timestamp, b.created_at) - toTimestamp(a.timestamp, a.created_at); if (tsDiff !== 0) return tsDiff; return String(a.id || '').localeCompare(String(b.id || '')); }); };