learning_ai_invt_trdg/web/src/lib/canonicalLifecycleApi.ts

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;
};