Vest: no leg opens into a market close, and accounts plan on Vest's day

Market close: a Vest entry now needs its symbol's market to stay open
for RESET_CLEAR_HOLDS holds of the leg (clearsClose), the clearance the
8 PM ET reset already gets: at the target hold, and at the leg's sized
hold when a leverage cap stretches it. That covers the weekly close and
the daily maintenance hour as Vest's market-hours periods list them.
Before a closure that runs past an 8 PM ET reset (the weekend, a
holiday: longClosure), the bot's own legs still open in the last 5
minutes are closed at market, loudly; a position the bot didn't open is
named in the log and left alone.

Vest's day: Vest accounts are planned, and their legs and entries
counted, on Vest's day (20:00 ET) instead of the Central date
(tradeDateFor, PlatformClient.tradeDate). Between 5 PM CT and the reset
the Central date counted the open Vest day as a prior day and as today's
realized P&L both ($3,000 to go, $800 made: $2,050 left, not $1,900) and
started the leg count over. Other platforms keep the Central date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-27 14:12:27 -05:00
co-authored by Claude Opus 5.5
parent beddbdcf6c
commit ec3e6ea388
8 changed files with 290 additions and 32 deletions
+11 -9
View File
@@ -11,7 +11,7 @@ import { getClients } from './clients';
import { getCachedFirm, getTradableFirms } from './firm-catalog';
import { ensureRulesEvidence, getAccountConfig } from './account-type-resolver';
import {
combineCmeSessions, computeDayPlan, groupLegFraction, pickLegAmount, resolveEffectiveConfig, currentTradeDateCT,
combineCmeSessions, computeDayPlan, groupLegFraction, pickLegAmount, resolveEffectiveConfig, tradeDateFor,
obfuscationStopDollars, takeProfitPlan, onTick, percentFee, dllRoom, dllStopDollars, dllViable, stopTicks,
expectedSwapToWin, planLegSize, maxLossRoom, breachDistance,
isNoTradeTime, isPastSessionClose,
@@ -1043,7 +1043,10 @@ async function runTradeLocked(
// operator's pick; 'Auto' is drawn per batch from the batch firm's list.
const explicit = futuresLane && symbol !== 'Auto' ? symbol : null;
const tradeDate = currentTradeDateCT();
// The lane's own day (tradeDateFor): a notional lane is one firm — a
// Vest lane's day turns at 20:00 ET, its rows and realized P&L with it —
// and the futures lane's clients all keep the Central trade date.
const tradeDate = tradeDateFor(open[0]?.client);
const maxConcurrent = maxConcurrentAccounts();
// ── Phase 1: the eligible accounts across the lane's firms ──
@@ -1742,7 +1745,8 @@ async function copyTradeLocked(lease: number, lane: string): Promise<unknown[]>
return [];
}
const tradeDate = currentTradeDateCT();
// The lane's own day, as in runTrade.
const tradeDate = tradeDateFor(ready[0]?.client);
// Collect eligible accounts (same logic as Phase 1 of runTrade)
const eligible: LegItem[] = [];
@@ -1860,7 +1864,7 @@ export async function fireManualLeg(accountId: number, action: 'Buy' | 'Sell' |
const lease = acquireTradeMutex('[manual-leg]', lane);
if (lease === null) throw new Error('another batch is in flight in this lane');
try {
const tradeDate = currentTradeDateCT();
const tradeDate = tradeDateFor(client);
const ev = evaluateAccountForTrade(client, acc, tradeDate);
if (!ev || !ev.plan) throw new Error(ev?.reason || 'account has no account type');
if (!ev.eligible && !ev.reason.startsWith('already traded today')) throw new Error(ev.reason);
@@ -1986,7 +1990,6 @@ async function runClaims(lane: string, ready: FirmClient[]): Promise<void> {
const lease = acquireTradeMutex('[auto-claim]', lane);
if (lease === null) return;
try {
const tradeDate = currentTradeDateCT();
for (const { firm, client, acc } of due) {
if (!holdsTradeMutex(lease, lane)) return;
// Settled since, or a different amount now: claim what is due now.
@@ -1996,7 +1999,7 @@ async function runClaims(lane: string, ready: FirmClient[]): Promise<void> {
// In flight while it claims: a batch that takes over a wedged
// lane, or a Claim now, leaves the account alone.
const { claimed, received } = await withAccountInFlight(acc.id, () => client.claimProfit!(acc.id, amount));
afterClaim('[auto-claim]', firm, client, acc, claimed, received, tradeDate);
afterClaim('[auto-claim]', firm, client, acc, claimed, received, tradeDateFor(client));
} catch (err) {
(runtime().claimFailedAt ??= new Map()).set(acc.id, Date.now());
console.error(`[auto-claim] ${firm.name} ${acc.name}: claim of $${Math.floor(amount * 100) / 100} failed — ${errorMessage(err)}; retrying in ${CLAIM_RETRY_MS / 60_000}m`);
@@ -2130,7 +2133,7 @@ export async function claimAccountNow(accountId: number, amount: number): Promis
if (!(amount >= 1)) throw new ClaimRefusedError(`$${amount} is below the $1 minimum claim`);
if (amount > c.amount + 0.005) throw new ClaimRefusedError(`only $${c.amount.toFixed(2)} is claimable now`);
const { claimed, received } = await withAccountInFlight(c.acc.id, () => c.client.claimProfit!(c.acc.id, amount));
const stage = afterClaim('[claim-now]', c.firm, c.client, c.acc, claimed, received, currentTradeDateCT());
const stage = afterClaim('[claim-now]', c.firm, c.client, c.acc, claimed, received, tradeDateFor(c.client));
return { claimed, received, stage };
} finally {
releaseTradeMutex(lease, lane);
@@ -2142,7 +2145,6 @@ export async function claimAccountNow(accountId: number, amount: number): Promis
/** Returns true if any configured account could still trade today (eligible for a next leg / extra-day, or has an open position). */
function hasRemainingConfiguredAccounts(): boolean {
const { ready, unready } = firmClients();
const tradeDate = currentTradeDateCT();
// A client that isn't ready can't say whether its accounts are done — its
// snapshot is missing or stale — so while one might hold any of ours, the
@@ -2175,7 +2177,7 @@ function hasRemainingConfiguredAccounts(): boolean {
// Order being placed right now = still in play (evaluation calls it ineligible)
if (runtime().accountsInFlight.has(acc.id)) return true;
const ev = evaluateAccountForTrade(client, acc, tradeDate);
const ev = evaluateAccountForTrade(client, acc, tradeDateFor(client));
if (ev?.eligible) return true;
// Waiting on its history (or its day-start balance), not done: it may
// still owe today's trade — during the session and before it opens