172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
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<string, CanonicalLifecycleAggregate>;
|
|
};
|
|
}
|
|
|
|
type CanonicalLifecycleApiResponse = {
|
|
success: boolean;
|
|
snapshot?: CanonicalLifecycleSnapshot;
|
|
error?: string;
|
|
};
|
|
|
|
export const fetchCanonicalLifecycleSnapshot = async (args: {
|
|
token: string;
|
|
profileId?: string;
|
|
maxRows?: number;
|
|
}): Promise<CanonicalLifecycleSnapshot | null> => {
|
|
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;
|
|
};
|