Vest platform: a client for Vest Markets, traded on a session the extension relays

Vest (next.vestmarkets.com) is a perps prop firm: index, metal and energy
perps on CME hours, plus stocks, FX and crypto. It joins as a fourth
platform, 'vest', on the DXtrade engine paths. Those are now keyed by
isNotionalPlatform() instead of === 'dxtrade': the firm's own symbols,
percent fees, notional sizing from volatility, and a scheduler lane of its
own.

- Login is relayed, not stored. Vest signs people in with Google through
  Privy and renews the session with a device key the browser won't export.
  Extension 0.17.0's vest-relay.js reads the token the Vest app renews for
  itself (~15 min). It renews nothing, so the tab is never logged out. The
  background script posts that token to POST /api/vest/session, which holds
  it in memory only. With no Vest tab open on a machine that trades Vest,
  the extension opens one.
- The firm's credential is the Vest login's wallet address. The dashboard's
  form offers the relayed wallet with one click.
- VestClient (lib/vest-class.ts), mapped from the web app's traffic and code
  since Vest publishes no API:
  - Reads the capital accounts, balances (free amount plus position
    collateral), positions and open orders with the user token.
  - Mints per-account tokens for orders and transactions.
  - Names accounts for their rules (V50K-E-D3-T10-34, V50K-F-D3-34,
    V25K-I-7) so account-type prefixes can match.
  - Takes Vest's own max-drawdown and daily-loss floors. dayStartBalance =
    floor + the type's limit, so the engine's room is never more than
    Vest's.
  - Uses each symbol's taker fee and margin rate from exchangeInfo, and
    hourly volatility over in-session hours only.
  - Places take-profit and stop as position legs, which Vest cancels as an
    OCO pair.
- Daily P&L and fund rows come from /v3/transactions by Vest's 20:00 ET
  day. A books check holds an account, naming the transaction types, when
  the balance and the transactions disagree.
- scripts/check-vest.mjs (in npm run check): Vest's day across DST, account
  names, parsing, volatility gaps, market hours, the session store.

Not yet run against Vest with a live session: the order and exit bodies
come from the app's code, and the transaction classification from its enum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-26 14:19:31 -05:00
co-authored by Claude Opus 5.5
parent 5ec5abde6e
commit 96fdd9ca23
22 changed files with 2520 additions and 28 deletions
+10 -8
View File
@@ -23,6 +23,7 @@ import {
} from './trading-logic';
import { getSetting, setSetting, loadAccountMeta, saveAccountMeta } from './db';
import type { LaneTick, TickReason } from './master-protocol';
import { isNotionalPlatform } from './master-protocol';
import { POINT_VALUES, housekeepingRoot, HOUSEKEEPING_ROOT, MAX_HOLD_HOURS, futuresContracts } from './instrument-specs';
import { firmInstruments, drawFuturesInstrument, spreadBlock, type LegShape } from './instruments';
import { recordEntry } from './trade-mix';
@@ -310,10 +311,11 @@ interface FirmClient { firm: TradableFirm; client: PlatformClient }
* multi-hour crypto position never holds up the futures open and a stuck
* futures position never freezes a 24/7 firm's weekend. Every CME-futures
* firm (Tradovate, TopstepX) shares the one 'futures' lane — exactly how all
* firms traded before lanes existed; each DXTrade firm is a lane of its own.
* firms traded before lanes existed; each DXTrade or Vest firm is a lane of
* its own.
*/
export function laneOf(firm: { id: string; platform: string }): string {
return firm.platform === 'dxtrade' ? firm.id : FUTURES_LANE;
return isNotionalPlatform(firm.platform) ? firm.id : FUTURES_LANE;
}
const FUTURES_LANE = 'futures';
@@ -455,9 +457,9 @@ function releaseTradeMutex(lease: number, lane: string): void {
/** What a firm's tiny legs (drain, extra-day) trade: a futures firm the
* micro of what it lists (MNQ when NQ is listed, as always — see
* housekeepingRoot); a DXtrade firm its current pick. */
* housekeepingRoot); a DXtrade or Vest firm its current pick. */
function housekeepingFor(firm: { platform: string; instruments?: string[]; bannedSymbols?: string[]; symbolMap?: Record<string, { symbol: string }[]> }): string {
if (firm.platform === 'dxtrade') return '*';
if (isNotionalPlatform(firm.platform)) return '*';
return housekeepingRoot(firmInstruments(firm)) ?? HOUSEKEEPING_ROOT;
}
@@ -1830,11 +1832,11 @@ export async function fireManualLeg(accountId: number, action: 'Buy' | 'Sell' |
cash: ev.cash, dailyPnL: ev.dailyPnL, plan: ev.plan, drain: ev.drain,
prefix: cfg.prefix, dllRoom: room,
};
// The instrument: a DXtrade firm ranks its list; a futures firm joins the
// held root, else trades an explicit scheduler symbol it lists, else
// draws from its list.
// The instrument: a DXtrade or Vest firm ranks its list; a futures
// firm joins the held root, else trades an explicit scheduler symbol
// it lists, else draws from its list.
let symbol = '*';
if (firm.platform !== 'dxtrade') {
if (!isNotionalPlatform(firm.platform)) {
const listed = firmInstruments(firm);
const state = getState();
const found = held.length > 0 ? await heldRoot(client, held[0].contractId, [...listed, housekeepingFor(firm)]) : null;