Vest: a stale sync keeps the bot's fill, a cut-off open is still the bot's, and the day turns with the numbers

A sync whose positions read went out before the bot's own open or close
finished no longer writes that account's positions back (ownOrderSeq, as
claimSeq guards balances): the record a flatten goes by stays, so a read
lagging the fill still gives FLATTEN FAILED, not "nothing to flatten".

An open is noted in account_meta before its POST and dropped once its
outcome is known. After a restart the first sync takes the note up as an
open of unknown outcome, so the position it made is marked the bot's and
guardClose sees it. Such an open is matched by the position id its answer
named, else only to a position with no exit legs opened between the send
and a few seconds past the error (was two minutes): an outside
copy-trader's open half a minute on isn't taken for the bot's, and
several matches adopt none, loudly.

VestClient.tradeDate follows the last sync's numbers: past 8 PM ET the
day turns once a sync has worked out the fresh day's realized P&L, so
the engine, reporter and dashboard never pair the new day with the ended
day's numbers. A stalled sync holds the day back only while isClientReady
still trades on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-27 15:31:28 -05:00
co-authored by Claude Opus 5.5
parent ab588adbac
commit 032ba1ca9c
4 changed files with 403 additions and 49 deletions
+22 -10
View File
@@ -130,20 +130,32 @@ const openPosition = {
};
test('an open of unknown outcome is the position on its account, symbol and side opened around it', () => {
const pos = (positionId, accountId, side, openedAt, symbol = 'NDX-USD') => ({
positionId, accountId, symbol, side, quantity: 0.5, openPrice: 20000, collateral: 500, leverage: 20, openedAt, stopLosses: [], takeProfits: [],
const pos = (positionId, accountId, side, openedAt, symbol = 'NDX-USD', stopLosses = []) => ({
positionId, accountId, symbol, side, quantity: 0.5, openPrice: 20000, collateral: 500, leverage: 20, openedAt, stopLosses, takeProfits: [],
});
const sent = { accountId: 'acc-1', symbol: 'NDX-USD', side: 'long', from: iso('2026-10-30T12:28:00Z'), until: iso('2026-10-30T12:32:15Z') };
// Sent at 12:30:00, its error at 12:30:15: the window is a few seconds of clock either side.
const sent = { accountId: 'acc-1', symbol: 'NDX-USD', side: 'long', from: iso('2026-10-30T12:29:55Z'), until: iso('2026-10-30T12:30:20Z') };
const filled = pos('9001', 'acc-1', 'long', iso('2026-10-30T12:30:01Z'));
assert.equal(unknownOpenFill(sent, [pos('9002', 'acc-2', 'long', iso('2026-10-30T12:30:01Z')), filled]), filled);
assert.deepEqual(unknownOpenFill(sent, [pos('9002', 'acc-2', 'long', iso('2026-10-30T12:30:01Z')), filled]), [filled]);
// Another side or symbol, a position from before it, or one opened after
// the window (a hand trade later on): not it.
assert.equal(unknownOpenFill(sent, [pos('9003', 'acc-1', 'short', iso('2026-10-30T12:30:01Z'))]), null);
assert.equal(unknownOpenFill(sent, [pos('9004', 'acc-1', 'long', iso('2026-10-30T12:30:01Z'), 'SPX-USD')]), null);
assert.equal(unknownOpenFill(sent, [pos('9005', 'acc-1', 'long', iso('2026-10-30T11:00:00Z'))]), null);
assert.equal(unknownOpenFill(sent, [pos('9006', 'acc-1', 'long', iso('2026-10-30T14:00:00Z'))]), null);
// the window (a hand trade, or an outside copy-trader, half a minute on): not it.
assert.deepEqual(unknownOpenFill(sent, [pos('9003', 'acc-1', 'short', iso('2026-10-30T12:30:01Z'))]), []);
assert.deepEqual(unknownOpenFill(sent, [pos('9004', 'acc-1', 'long', iso('2026-10-30T12:30:01Z'), 'SPX-USD')]), []);
assert.deepEqual(unknownOpenFill(sent, [pos('9005', 'acc-1', 'long', iso('2026-10-30T11:00:00Z'))]), []);
assert.deepEqual(unknownOpenFill(sent, [pos('9006', 'acc-1', 'long', iso('2026-10-30T12:30:45Z'))]), []);
// No open time: can't be told from a hand trade.
assert.equal(unknownOpenFill(sent, [pos('9007', 'acc-1', 'long', NaN)]), null);
assert.deepEqual(unknownOpenFill(sent, [pos('9007', 'acc-1', 'long', NaN)]), []);
// One with exit legs isn't the bot's open — none went on it.
const leg = { id: 'sl-1', triggerPrice: 19900, executionType: 'market', status: 'awaiting' };
assert.deepEqual(unknownOpenFill(sent, [pos('9008', 'acc-1', 'long', iso('2026-10-30T12:30:01Z'), 'NDX-USD', [leg])]), []);
// Two inside the window: both come back, for the caller to adopt neither.
const copy = pos('9009', 'acc-1', 'long', iso('2026-10-30T12:30:02Z'));
assert.deepEqual(unknownOpenFill(sent, [filled, copy]), [filled, copy]);
// The position id the open's answer named settles it, legs and window aside.
const named = pos('9010', 'acc-1', 'long', iso('2026-10-30T12:31:00Z'), 'NDX-USD', [leg]);
assert.deepEqual(unknownOpenFill({ ...sent, positionId: '9010' }, [filled, copy, named]), [named]);
assert.deepEqual(unknownOpenFill({ ...sent, positionId: '9010' }, [filled, copy]), []);
assert.deepEqual(unknownOpenFill({ ...sent, positionId: '9010' }, [{ ...named, accountId: 'acc-2' }]), []);
});
test('positions, their legs and unrealized P&L', () => {