One-and-done counts confirmed fills; holds need a persistent refusal; wipes keep their yardstick
Fourth review round (52ed9c7):
- The one-and-done guard counts only entries the broker confirmed filled,
so an entry that timed out or was rejected unfilled no longer benches the
account for the day.
- TopstepX: an account is promoted only by a sync that actually checked its
funds; one with no account type stays out (not traded) without costing a
sync per pass, and once typed waits for that check. Wipe routes don't
count untyped accounts as failures.
- Tradovate: an unchecked balance-hold release needs Cash History refused
for a full hour straight (a 4xx other than 401/403/408/429, or an answer
without data); any other outcome resets it, so one stray reply during a
5xx outage can't release a payout.
- A wipe keeps the pre-wipe baseline and fund rows as its yardstick: the
rebuild must explain today's balance against them, so a payout the
rebuilt report hasn't booked yet stays held.
- The roll base of an unrolled record is kept per trade date in
account_meta, so a restart later the same trade date counts from the same
point instead of from a value that already includes the day's fills.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
52ed9c75e4
commit
1eb0736fbe
+17
-5
@@ -37,6 +37,10 @@ interface TradeRuntime {
|
||||
* how a stop-out is detected. Resets on restart, which is fine — day
|
||||
* completion is derived from realized PnL, not from this counter. */
|
||||
legCounts: Map<number, { date: string; count: number; sum: number }>;
|
||||
/** Per account, the trade date of its last entry the broker confirmed as
|
||||
* filled — the one-and-done guard's "already traded", which an entry
|
||||
* whose outcome is unknown or that never filled must not trip. */
|
||||
filledEntries?: Map<number, string>;
|
||||
/** Today's session-open delay, keyed by Central calendar day (see openJitterMinutes). */
|
||||
sessionOpenJitter: Map<string, number>;
|
||||
/** Accounts whose orders are being placed right now. Their position only
|
||||
@@ -120,6 +124,10 @@ function legSumToday(accountId: number, tradeDate: string): number {
|
||||
return entry && entry.date === tradeDate ? entry.sum : 0;
|
||||
}
|
||||
|
||||
function filledEntryToday(accountId: number, tradeDate: string): boolean {
|
||||
return runtime().filledEntries?.get(accountId) === tradeDate;
|
||||
}
|
||||
|
||||
function recordLeg(accountId: number, tradeDate: string, legAmount: number): void {
|
||||
runtime().legCounts.set(accountId, {
|
||||
date: tradeDate,
|
||||
@@ -168,7 +176,9 @@ async function sendEntry(
|
||||
legAmount: number,
|
||||
) {
|
||||
try {
|
||||
return await client.sendOrder(acc.id, contractName, contracts, action, 'Market');
|
||||
const fill = await client.sendOrder(acc.id, contractName, contracts, action, 'Market');
|
||||
(runtime().filledEntries ??= new Map()).set(acc.id, tradeDate);
|
||||
return fill;
|
||||
} catch (err) {
|
||||
if (isOrderNotPlaced(err)) {
|
||||
unrecordLeg(acc.id, tradeDate, legAmount);
|
||||
@@ -479,10 +489,12 @@ function evaluateAccountForTrade(client: any, acc: { id: number; name: string; a
|
||||
if (combined.realizedPnL !== 0) {
|
||||
return { ...evaluated, eligible: false, reason: `already traded today (realizedPnL=${combined.realizedPnL})` };
|
||||
}
|
||||
// And any leg this process fired today, whatever the broker's
|
||||
// realized P&L says (a record replaced under it, a fill not booked yet).
|
||||
if (legsToday(acc.id, tradeDate) > 0) {
|
||||
return { ...evaluated, eligible: false, reason: 'already traded today (leg fired)' };
|
||||
// And any entry of today's the broker confirmed filled, whatever its
|
||||
// realized P&L says (a record replaced under it, a fill not booked
|
||||
// yet). Only a confirmed fill: an entry that timed out or was
|
||||
// rejected unfilled leaves the account free to retry.
|
||||
if (filledEntryToday(acc.id, tradeDate)) {
|
||||
return { ...evaluated, eligible: false, reason: 'already traded today (entry filled)' };
|
||||
}
|
||||
if (!isExtraDay && plan.remainingToday <= 0) {
|
||||
const reason = effective.drain && plan.dayTarget === 0
|
||||
|
||||
Reference in New Issue
Block a user