Vest: record every trade, the bot's and outside ones, and list them

Vest's position history (GET /v3/positions) gives each position's size,
open and close price, P&L, fees, funding and why it closed. The client
reads it hourly and a minute after a position closes (or a flat balance
moves) into a new trades table, and stores open positions as they
appear. Vest can't tell an API order from a click, so the bot marks its
own opens and closes as it sends them; an unmarked trade since marking
began was someone else's.

The dashboard's Vest firm row gets a Trades button listing every trade
on its accounts with who opened it and how it closed; the account page
shows its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-26 19:23:24 -05:00
co-authored by Claude Opus 5.5
parent 29eaf64a6b
commit 95cc3bd23f
10 changed files with 603 additions and 4 deletions
+34 -1
View File
@@ -11,7 +11,7 @@ import assert from 'node:assert/strict';
import {
isVestWalletAddress, vestAccountId, num, toMs, formatDecimal, vestDayOf, lastVestResetMs, parseCapitalAccount,
vestAccountName, parsePosition, awaitingLegs, parseOpenOrder, unrealizedPnl, parseTxn, bookTxns, txnKind, VEST_TXN,
hourlyVolatility, marketOpen, MIN_HOURLY_RETURNS, nextVestResetMs, resetSafeStop,
hourlyVolatility, marketOpen, MIN_HOURLY_RETURNS, nextVestResetMs, resetSafeStop, parseHistoryPosition,
} from '../lib/vest-helpers.ts';
import { putVestSession, vestSessionFor, dropVestSession, listVestSessions } from '../lib/vest-session.ts';
@@ -263,3 +263,36 @@ test('resetSafeStop: fit an open position into the room the reset will leave', (
assert.equal(dd.action, 'stop');
assert.ok(dd.price > want);
});
test('parseHistoryPosition: a closed position from GET /v3/positions', () => {
// Shaped like the app's History tab reads it (camelCase, decimal strings).
const t = parseHistoryPosition({
positionId: 'bdda9024-63a9-4abc-a5a8-f83df8ba030a', accountId: '843f60e2-e013-5a0e-8b6c-face84bc5509', symbol: 'NDX-USD-PERP',
side: 'short', quantity: '4.2712', leverage: '50', openPrice: '29529.75', closePrice: '29556.50',
pnl: '-114.25', fee: '6.31', funding: '-0.12', collateral: '2522.54', takeProfits: [], stopLosses: [],
openDate: 1793065590123, closeDate: 1793066346456, createdAt: 1793065590123, updatedAt: 1793066346456, closeReason: 'user_requested',
});
assert.deepEqual(t, {
positionId: 'bdda9024-63a9-4abc-a5a8-f83df8ba030a', symbol: 'NDX-USD-PERP', side: 'short', quantity: 4.2712, leverage: 50,
openPrice: 29529.75, closePrice: 29556.5, pnl: -114.25, fee: 6.31, funding: -0.12,
openedAt: 1793065590123, closedAt: 1793066346456, closeReason: 'user_requested',
});
});
test('parseHistoryPosition: open, snake_case, seconds, and junk', () => {
const open = parseHistoryPosition({
position_id: 'p1', symbol: 'GC-PERP', side: 'LONG', quantity: '0', open_price: '4208.9', close_price: '',
pnl: '', open_date: 1793065590, close_date: null, close_reason: '',
});
assert.equal(open.side, 'long');
assert.equal(open.quantity, 0, 'a size Vest leaves out reads 0 — the store keeps the one it knows');
assert.equal(open.openPrice, 4208.9);
assert.equal(open.closePrice, null);
assert.equal(open.pnl, null);
assert.equal(open.openedAt, 1793065590000);
assert.equal(open.closedAt, null);
assert.equal(open.closeReason, null);
assert.equal(parseHistoryPosition({ symbol: 'GC-PERP', openDate: 1 }), null, 'no id');
assert.equal(parseHistoryPosition({ positionId: 'p2', symbol: 'GC-PERP' }), null, 'no open time');
assert.equal(parseHistoryPosition(null), null);
});