Merge branch 'vest-fix-intent' into vest-fixes

This commit is contained in:
Brandon Li
2026-10-27 14:51:36 -05:00
5 changed files with 186 additions and 26 deletions
+18 -7
View File
@@ -28,7 +28,7 @@ import { POINT_VALUES, housekeepingRoot, HOUSEKEEPING_ROOT, MAX_HOLD_HOURS, futu
import { firmInstruments, drawFuturesInstrument, spreadBlock, type LegShape } from './instruments';
import { recordEntry } from './trade-mix';
import { sizingHoldHours } from './person-size';
import { errorMessage, isClientReady, isOrderNotPlaced, type PlatformClient, type PlatformContract, type PickContext } from './platform-client';
import { errorMessage, isClientReady, isNothingToClose, isOrderNotPlaced, type PlatformClient, type PlatformContract, type PickContext } from './platform-client';
// ── process-wide state ───────────────────────────────────────────────────────
@@ -260,7 +260,10 @@ function unrecordLeg(accountId: number, tradeDate: string, legAmount: number): v
* (OrderNotPlacedError). Anything ambiguous, like an ack or fill timeout,
* keeps the leg: the order may well have filled, and a leg counted that never
* happened only costs a leg, while one forgotten that did can trade the
* account twice.
* account twice. Open-only: where the platform checks (Vest, DXtrade), an
* account that already holds the symbol — a hand trade the synced positions
* don't show yet — is refused rather than having that position closed as the
* "entry".
*/
async function sendEntry(
tag: string,
@@ -277,7 +280,7 @@ async function sendEntry(
const at = Date.now();
const realizedBefore = client.accountCashBalances[acc.id]?.realizedPnL ?? 0;
try {
const fill = await client.sendOrder(acc.id, contractName, contracts, action, 'Market');
const fill = await client.sendOrder(acc.id, contractName, contracts, action, 'Market', undefined, 'open');
outcomes.set(acc.id, { tradeDate, state: 'filled', at, realizedBefore, legAmount });
return fill;
} catch (err) {
@@ -1240,10 +1243,10 @@ async function runTradeLocked(
const mnqContract = await client.findFrontMonthContract(microRoot);
if (!mnqContract) throw new Error(`${microRoot} contract not found for extra-day trade`);
const fill = await client.sendOrder(acc.id, mnqContract.name, 1, resolvedAction, 'Market');
const fill = await client.sendOrder(acc.id, mnqContract.name, 1, resolvedAction, 'Market', undefined, 'open');
recordEntry(item.firmName, microRoot, resolvedAction, 1, mnqContract.id);
const exitAction: 'Buy' | 'Sell' = resolvedAction === 'Buy' ? 'Sell' : 'Buy';
const exitFill = await client.sendOrder(acc.id, mnqContract.name, 1, exitAction, 'Market');
const exitFill = await client.sendOrder(acc.id, mnqContract.name, 1, exitAction, 'Market', undefined, 'close');
console.log(`[auto-trade] ${acc.name} (${item.firmName}) extra-day: ${resolvedAction} 1x${mnqContract.name} @ ${fill.price} | exited @ ${exitFill.price} (market)`);
@@ -1303,7 +1306,11 @@ const round2 = (n: number) => Math.round(n * 100) / 100;
* Close a just-filled position at market because it must not stay open — its
* daily-loss stop has no room, or the broker refused its exit bracket — and
* report the leg as failed. A flatten that fails too is the worst case: a
* position with no stop, so it is shouted about.
* position with no stop, so it is shouted about. Close-only: on an account
* that turned out flat it must not open a fresh position with no exits, and
* where the platform checks (Vest, DXtrade) and its own record agrees, that
* flat account has nothing riding — the leg still fails, but without the
* alarm. A flat read the record contradicts still gets the alarm.
*/
async function flattenAndFail(
tag: string,
@@ -1317,9 +1324,13 @@ async function flattenAndFail(
why: string,
): Promise<never> {
try {
const exit = await client.sendOrder(acc.id, contractName, contracts, exitAction, 'Market');
const exit = await client.sendOrder(acc.id, contractName, contracts, exitAction, 'Market', undefined, 'close');
console.warn(`${tag} ${acc.name}: flattened @ ${exit.price} — ${why}`);
} catch (err) {
if (isNothingToClose(err)) {
console.warn(`${tag} ${acc.name}: nothing to flatten — ${errorMessage(err)} (${why})`);
throw new Error(`entry filled @ ${entryPrice}; ${why}; already flat — nothing to flatten`);
}
console.error(`${tag} ${acc.name}: FLATTEN FAILED (${errorMessage(err)}) — the position may be open WITHOUT A STOP; close it at the broker (${why})`);
throw new Error(`entry filled @ ${entryPrice}; ${why}; flatten failed — ${errorMessage(err)}`);
}