cycleCompletion() says whether today's P&L finished the current cycle (profit target, consistency and minimum days all met) — projectPerfectPath's completion probe run without and with today. The reporter counts those as `completed` (a subset of targetHit) and sends their names, so the master can keep an account a firm erases after it passes. Checked ahead of the lockout branch: a firm locking a passed account is no DLL loss. The local dashboard shows them with a purple Completed pill. Because the master keeps a completion for good, doubtful reads stay 'none': weekends, a Monday with a weekend-dated row, the first minutes after the 5 PM roll, and history that hasn't freshly loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
/**
|
|
* The surface a platform trading client must expose. TradovateClient grew this
|
|
* shape organically; TopstepXClient implements the same one so the scheduler,
|
|
* reporter, state route and resolver can drive either platform without caring
|
|
* which is behind the map entry.
|
|
*
|
|
* Contract ids are `number` on Tradovate and `string` (e.g.
|
|
* "CON.F.US.ENQ.Z25") on TopstepX — everything that compares them only ever
|
|
* compares within one client, so the union is safe.
|
|
*/
|
|
|
|
export interface PlatformAccount {
|
|
id: number;
|
|
name: string;
|
|
active: boolean;
|
|
}
|
|
|
|
export interface PlatformContract {
|
|
id: number | string;
|
|
name: string;
|
|
tickSize: number;
|
|
}
|
|
|
|
export interface PlatformClient {
|
|
accountList: PlatformAccount[];
|
|
accountCashBalances: { [accountId: number]: { amount: number; realizedPnL: number } };
|
|
positions: {
|
|
[accountId: number]: {
|
|
contractId: number | string;
|
|
netPos: number;
|
|
netPrice: number;
|
|
timestamp: Date;
|
|
};
|
|
};
|
|
|
|
daysTraded: { [accountId: number]: number };
|
|
dailyPnL: { [accountId: number]: { date: string; pnl: number }[] };
|
|
fullDailyPnL: { [accountId: number]: { date: string; pnl: number }[] };
|
|
lastFundDates: { [accountId: number]: string | null };
|
|
fundTransactions: { [accountId: number]: { date: string; amount: number }[] };
|
|
priorProfit: { [accountId: number]: number };
|
|
autoLiqThresholds: { [accountId: number]: number };
|
|
|
|
fetchDaysComplete: boolean;
|
|
syncComplete: boolean;
|
|
lastFetchErrors: Record<string, string>;
|
|
lastFetchRaw: Record<string, string>;
|
|
recentEntityEvents: { entityType: string; eventType: string; entity: unknown; ts: number }[];
|
|
products: { id: number; name: string }[];
|
|
|
|
fetchDaysTraded(onlyAccountIds?: number[]): Promise<{ failedAccounts: number }>;
|
|
findFrontMonthContract(productName: string): Promise<PlatformContract | null>;
|
|
fetchInstrumentFees(symbols: string[]): Promise<{ [symbol: string]: number }>;
|
|
|
|
sendOrder(
|
|
accountId: number,
|
|
contractSymbol: string,
|
|
quantity: number,
|
|
action: 'Buy' | 'Sell',
|
|
orderType: 'Market' | 'Limit',
|
|
price?: number
|
|
): Promise<Record<string, unknown>>;
|
|
|
|
placeOrderNoWait(
|
|
accountId: number,
|
|
contractSymbol: string,
|
|
quantity: number,
|
|
action: 'Buy' | 'Sell',
|
|
orderType: 'Market' | 'Limit',
|
|
price?: number
|
|
): Promise<{ orderId?: number }>;
|
|
|
|
placeOcoExit(
|
|
accountId: number,
|
|
contractSymbol: string,
|
|
quantity: number,
|
|
exitAction: 'Buy' | 'Sell',
|
|
limitPrice: number,
|
|
stopPrice: number,
|
|
): Promise<{ orderId?: number }>;
|
|
|
|
disconnect(): void;
|
|
onFill(orderId: number, callback: (fill: Record<string, unknown>) => void): void;
|
|
}
|
|
|
|
/** lastFetchErrors note for a Tradovate account whose Cash History is
|
|
* unavailable (passed accounts) and whose rows came from the Fills report —
|
|
* a fresh fetch, not a failure. */
|
|
export const FILLS_FALLBACK_NOTE = 'cash history unavailable (using fills fallback)';
|
|
|
|
/**
|
|
* Can this account's daily P&L be trusted for a verdict the master keeps
|
|
* for good (cycleCompletion 'today')? Not while a fetch run is in flight —
|
|
* at startup Tradovate serves the DB cache before each account's fetch —
|
|
* and not when the account's last fetch failed and left cached rows behind.
|
|
*/
|
|
export function historyFresh(client: PlatformClient, account: PlatformAccount): boolean {
|
|
if (!client.fetchDaysComplete) return false;
|
|
const error = client.lastFetchErrors?.[account.name];
|
|
return !error || error === FILLS_FALLBACK_NOTE;
|
|
}
|