Unknown entries: judge only on a fresh good sync; all live orders count as working
Seventh review round (57835c0):
- An entry of unknown outcome is judged — filled after all, or never
filled — only on a good sync that started after its settle window and
with no order still working. A partial pass (position read failed or
predated the fill, trades read after it) can no longer release it as
"filled and flat" into a second entry.
- Tradovate counts every order not Filled/Canceled/Rejected/Expired/
Completed as working (PendingCancel, Unknown included).
- TopstepX: an account whose order ended with its outcome unknown gets its
open orders checked (Order/searchOpen) on each sync until none show, and
counts as holding a working order until then.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
57835c01f0
commit
809f337055
+42
-1
@@ -244,6 +244,10 @@ export class TopstepXClient implements PlatformClient {
|
||||
private reconcilePendingIds = new Set<number>();
|
||||
/** See PlatformClient: when the last good pass started reading. */
|
||||
public lastSyncStartedAt = 0;
|
||||
/** Accounts whose last order ended with its outcome unknown: each sync
|
||||
* checks their open orders (Order/searchOpen) until it shows none. The
|
||||
* value is the open count last seen (undefined until checked). */
|
||||
private suspectOrderAccounts = new Map<number, number | undefined>();
|
||||
/** Settles when the sync in flight ends (requestSync hands it to a caller
|
||||
* that arrives mid-pass). */
|
||||
private syncRunning: Promise<void> = Promise.resolve();
|
||||
@@ -864,6 +868,19 @@ export class TopstepXClient implements PlatformClient {
|
||||
}
|
||||
if (superseded()) return;
|
||||
|
||||
if (this.suspectOrderAccounts.has(acc.id)) {
|
||||
try {
|
||||
const open = await this.post<{ orders?: unknown[] }>('/api/Order/searchOpen', { accountId: acc.id });
|
||||
if (superseded()) return;
|
||||
const count = (open.orders ?? []).length;
|
||||
if (count === 0) this.suspectOrderAccounts.delete(acc.id);
|
||||
else this.suspectOrderAccounts.set(acc.id, count);
|
||||
} catch (err) {
|
||||
accountFresh = false;
|
||||
console.error(`[topstepx] open-order check failed for ${acc.name}: ${errorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const tradeRes = await this.post<{ trades: TsxTrade[] }>(
|
||||
'/api/Trade/search', { accountId: acc.id, startTimestamp: start, endTimestamp: end });
|
||||
@@ -1460,8 +1477,16 @@ export class TopstepXClient implements PlatformClient {
|
||||
}
|
||||
}
|
||||
|
||||
/** See PlatformClient: an account whose last order's outcome is unknown
|
||||
* counts as holding a working order until a sync's Order/searchOpen shows
|
||||
* none. */
|
||||
public hasWorkingOrders(accountId: number): boolean {
|
||||
return this.suspectOrderAccounts.has(accountId);
|
||||
}
|
||||
|
||||
/** Place an order and wait for its fill (trade event, with a REST poll as
|
||||
* backstop). Resolves with an object exposing the fill `price`. */
|
||||
* backstop). Resolves with an object exposing the fill `price`. An order
|
||||
* whose outcome stays unknown marks the account for open-order checks. */
|
||||
public async sendOrder(
|
||||
accountId: number,
|
||||
contractSymbol: string,
|
||||
@@ -1469,6 +1494,22 @@ export class TopstepXClient implements PlatformClient {
|
||||
action: 'Buy' | 'Sell',
|
||||
orderType: 'Market' | 'Limit',
|
||||
price?: number
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
return await this.sendOrderAndWait(accountId, contractSymbol, quantity, action, orderType, price);
|
||||
} catch (err) {
|
||||
if (!isOrderNotPlaced(err)) this.suspectOrderAccounts.set(accountId, undefined);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async sendOrderAndWait(
|
||||
accountId: number,
|
||||
contractSymbol: string,
|
||||
quantity: number,
|
||||
action: 'Buy' | 'Sell',
|
||||
orderType: 'Market' | 'Limit',
|
||||
price?: number
|
||||
): Promise<Record<string, unknown>> {
|
||||
const contract = await this.contractForOrder(contractSymbol);
|
||||
const orderId = await this.placeOrder(
|
||||
|
||||
Reference in New Issue
Block a user