import { tradingRuntime } from './runtime'; import { createRequestId } from '../../../shared/request-id.js'; export type CanonicalLifecycleState = 'OPEN' | 'PARTIAL_EXIT' | 'CLOSED' | 'ORPHAN_EXIT'; export type CanonicalSide = 'BUY' | 'SELL'; export interface CanonicalLifecycleProfileMeta { id: string; userId?: string; name: string; allocatedCapital: number; isActive: boolean; } export interface CanonicalLifecycleRow { id: string; profileId: string; profileName: string; tradeId: string; symbol: string; side: CanonicalSide; state: CanonicalLifecycleState; entryQty: number; exitQty: number; matchedQty: number; openQty: number; entryAvgPrice: number; exitAvgPrice: number; openEntryAvgPrice: number; openNotional: number; realizedPnl: number; realizedPnlPercent: number; unrealizedPnl: number; currentPrice: number; stopLoss?: number; takeProfit?: number; subTag?: string; hasSyntheticOrder: boolean; lastEventAt: number; } export interface CanonicalOpenPosition { id: string; profileId: string; profileName: string; tradeId: string; symbol: string; side: CanonicalSide; size: number; entryPrice: number; currentPrice: number; pnl: number; pnlPercent: number; stopLoss?: number; takeProfit?: number; subTag?: string; lastEventAt: number; } export interface CanonicalRealizedTrade { id: string; profileId: string; profileName: string; tradeId: string; symbol: string; side: CanonicalSide; size: number; entryPrice: number; exitPrice: number; pnl: number; pnlPercent: number; closedAt: number; state: CanonicalLifecycleState; subTag?: string; } export interface CanonicalLifecycleAggregate { profileId: string; profileName: string; allocatedCapital: number; isActive: boolean; openTrades: number; openNotional: number; realizedPnl: number; unrealizedPnl: number; netPnl: number; tradeCount: number; wins: number; winRate: number; lastClosedTradeAt: number; } export interface CanonicalLifecycleSnapshot { generatedAt: number; diagnostics: { orderRows: number; lifecycleRows: number; openPositions: number; realizedTrades: number; truncated: boolean; }; profiles: CanonicalLifecycleProfileMeta[]; lifecycleRows: CanonicalLifecycleRow[]; openPositions: CanonicalOpenPosition[]; realizedTrades: CanonicalRealizedTrade[]; aggregates: { total: { openTrades: number; openNotional: number; realizedPnl: number; unrealizedPnl: number; netPnl: number; tradeCount: number; wins: number; winRate: number; }; byProfile: Record; }; } type CanonicalLifecycleApiResponse = { success: boolean; snapshot?: CanonicalLifecycleSnapshot; error?: string; }; export const fetchCanonicalLifecycleSnapshot = async (args: { token: string; profileId?: string; maxRows?: number; }): Promise => { const token = String(args.token || '').trim(); if (!token) return null; const apiUrl = String(tradingRuntime.tradingApiUrl || '').trim(); if (!apiUrl) return null; const params = new URLSearchParams(); const profileId = String(args.profileId || '').trim(); if (profileId) params.set('profileId', profileId); if (Number.isFinite(Number(args.maxRows)) && Number(args.maxRows) > 0) { params.set('maxRows', String(Math.floor(Number(args.maxRows)))); } const query = params.toString(); const endpoint = `${apiUrl}/api/lifecycle/canonical${query ? `?${query}` : ''}`; const response = await fetch(endpoint, { headers: { Authorization: `Bearer ${token}`, 'x-request-id': createRequestId('web-lifecycle') } }); let payload: CanonicalLifecycleApiResponse | null = null; try { payload = (await response.json()) as CanonicalLifecycleApiResponse; } catch { payload = null; } if (!response.ok) { throw new Error(String(payload?.error || `Canonical lifecycle request failed (${response.status})`)); } if (!payload?.success) { throw new Error(String(payload?.error || 'Canonical lifecycle request returned unsuccessful response')); } if (!payload.snapshot) { throw new Error('Canonical lifecycle response missing snapshot payload'); } return payload.snapshot; };