Vest: a crypto leg's stop holds back the engine's default slippage, not NDX's

Crypto (exchangeInfo asset "crypto": BTC, ETH, SOL, HYPE) trades on weekends
when NDX, SPX and GC are shut, but its stop slippage on Vest is unmeasured, and
its book is a fraction of NDX's: about $10M an hour on a weekend against $600M,
with a batch's stops firing together. Its stops now leave 0.1% of the
position unspent (the engine's default) instead of the 0.02% measured on NDX
stop-outs; every other symbol keeps 0.02%.

The share rides on the picked contract and is used wherever a Vest leg's stop
is sized: the batch's plan, the leg's own stop, the dashboard's next stop, and
the 7:55 PM ET reset guard, which now sums each open position's own share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-30 17:26:53 -05:00
co-authored by Claude Opus 5.5
parent 618976bf5f
commit 10da0ca83e
8 changed files with 94 additions and 16 deletions
+2 -2
View File
@@ -1426,7 +1426,7 @@ function sizeLeg(item: SizingItem, contract: LegContract, legAmount: number, tra
maxLossRoom: floorRoom,
obfuscationDollars: obfuscation,
availableFunds: live.availableFunds ?? live.amount,
slippageFraction: item.client.stopSlippageFraction,
slippageFraction: contract.stopSlippageFraction ?? item.client.stopSlippageFraction,
});
return { ...plan, pct, obfuscation, maxLossRoom: floorRoom };
}
@@ -1577,7 +1577,7 @@ async function fireLeg(
const dllFee = pct > 0 ? entryFee : (measured ?? MAX_COMMISSION_PER_CONTRACT * contracts);
const notional = fill.price * pv * contracts;
const dllDollars = dllOn && !plan.lastStand
? dllStopDollars({ room: item.dllRoom, dailyLossLimit: dll, entryFee: dllFee, commissionPct: pct, notional, slippageFraction: client.stopSlippageFraction })
? dllStopDollars({ room: item.dllRoom, dailyLossLimit: dll, entryFee: dllFee, commissionPct: pct, notional, slippageFraction: legContract.stopSlippageFraction ?? client.stopSlippageFraction })
: Infinity;
const ticks = stopTicks({ obfuscationDollars: obfuscation, dllDollars, pointValue: pv, contracts, tickSize: legContract.tickSize });
if (ticks === 0) {
+1 -1
View File
@@ -100,7 +100,7 @@ export function rankNextLeg(o: {
maxLossRoom: floorRoom,
obfuscationDollars: o.obfuscationDollars,
availableFunds: funds,
slippageFraction: o.client.stopSlippageFraction,
slippageFraction: c.stopSlippageFraction ?? o.client.stopSlippageFraction,
});
const p = plan(true);
if (!p.sizing || p.cost === null) return [];
+4
View File
@@ -53,6 +53,10 @@ export interface PlatformContract {
* says: no leg starts that wouldn't be done by then (clearsClose), each
* account's at its own size (fireLeg). */
closesAt?: number;
/** This instrument's share of notional a stop may fill past its price,
* where it differs from the client's stopSlippageFraction (Vest: crypto,
* unmeasured, keeps the engine's default). */
stopSlippageFraction?: number;
}
/** What an order is for, where the caller knows (see sendOrder). */
+12 -5
View File
@@ -53,7 +53,7 @@ import { vestSessionFor, dropVestSession, listVestSessions, refuseVestSession }
import {
vestAccountId, vestAccountName, vestDayOf, lastVestResetMs, nextVestResetMs, parseCapitalAccount, parsePosition, parseOpenOrder,
parseTxn, bookTxns, awaitingLegs, unknownOpenFill, unrealizedPnl, hourlyVolatility, marketOpen, longClosure, num, toMs, formatDecimal, resetSafeStop,
parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_STOP_SLIPPAGE_FRACTION,
parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_STOP_SLIPPAGE_FRACTION, vestStopSlippageFraction,
vestStatusName, awaitingActivation,
VestApiError, VestNotSentError, nothingWentIn, tokenForRequest, settleClaims, readClaims, payoutDaysOf, payoutsSince,
} from './vest-helpers';
@@ -155,6 +155,9 @@ interface SymbolSpec {
marginRate: number;
tradingStatus: string;
capitalTradingStatus: string;
/** exchangeInfo's asset class: "crypto", "forex", "stock" (indices and
* commodities too). */
asset: string;
}
export interface VestConfig {
@@ -558,6 +561,7 @@ export class VestClient implements PlatformClient {
marginRate: capitalIm && capitalIm > 0 ? capitalIm : 0,
tradingStatus: String(s.tradingStatus ?? ''),
capitalTradingStatus: String(s.capitalTradingStatus ?? s.tradingStatus ?? ''),
asset: String(s.asset ?? ''),
});
}
if (next.size > 0) {
@@ -1230,13 +1234,15 @@ export class VestClient implements PlatformClient {
|| (x.currentStop !== null && y.currentStop !== null ? lossAt(x.pos, x.currentStop) - lossAt(y.pos, y.currentStop) : 0)
|| x.pos.openedAt - y.pos.openedAt || x.pos.positionId.localeCompare(y.pos.positionId));
let committed = 0;
let notionalSoFar = 0;
// The slippage reserve the positions so far hold together, in
// dollars: each its own symbol's share of its notional.
let slippageSoFar = 0;
for (const { pos, currentStop } of held) {
const spec = specs.get(pos.symbol);
if (!spec) {
// Can't be fitted without its tick, but it still holds
// its loss against the room the others share.
notionalSoFar += pos.quantity * pos.openPrice;
slippageSoFar += this.stopSlippageFraction * pos.quantity * pos.openPrice;
if (currentStop !== null) committed += lossAt(pos, currentStop);
else if (!guard.unfittable.has(pos.positionId)) {
guard.unfittable.add(pos.positionId);
@@ -1248,13 +1254,13 @@ export class VestClient implements PlatformClient {
const mark = this.marks.get(pos.symbol)?.mark ?? NaN;
const stopLeg = pos.stopLosses.find((l) => l.status === 'awaiting') ?? null;
const notional = pos.quantity * (Number.isFinite(mark) ? mark : pos.openPrice);
notionalSoFar += notional;
slippageSoFar += vestStopSlippageFraction(spec.asset) * notional;
const exitFee = spec.takerFee * notional;
const decision = resetSafeStop({
side: pos.side, quantity: pos.quantity, openPrice: pos.openPrice, mark,
balance: cash.amount - committed, dailyLossPct: a.maxDailyLossPct, maxDrawdownFloor: a.maxDrawdownLimit,
floorBalance: cash.amount,
reserve: dllReserve(cash.amount * a.maxDailyLossPct, notionalSoFar, this.stopSlippageFraction),
reserve: dllReserve(cash.amount * a.maxDailyLossPct, slippageSoFar, 1),
exitFeeRate: spec.takerFee, currentStop, tick: spec.tick,
});
const stopAt = decision.action === 'stop' ? decision.price : decision.action === 'none' ? currentStop : null;
@@ -2022,6 +2028,7 @@ export class VestClient implements PlatformClient {
minContracts: 1,
marginRate: spec.marginRate || undefined,
closesAt: gateAt ?? undefined,
stopSlippageFraction: vestStopSlippageFraction(spec.asset),
},
});
}
+12
View File
@@ -148,6 +148,18 @@ export function nextVestResetMs(now: number): number {
* past the trigger on average, 2.25 at worst — the 0.1% default held back
* about 30. The flat 10% of the limit still applies under it. */
export const VEST_STOP_SLIPPAGE_FRACTION = 0.0002;
/** Crypto's (asset "crypto" in exchangeInfo: BTC, ETH, SOL, HYPE): unmeasured,
* so the engine's default share (trading-logic's DLL_SLIPPAGE_FRACTION).
* Vest's BTC book trades ~$10M an hour on a weekend against NDX's ~$600M, and
* a batch's stops fire together: a square-root impact estimate scaled to the
* NDX measurement puts the worst fill near 7bp. To come down once crypto
* stop-outs are measured, as NDX's was. */
export const VEST_CRYPTO_STOP_SLIPPAGE_FRACTION = 0.001;
/** The stop-slippage share for a symbol of this exchangeInfo asset class. */
export function vestStopSlippageFraction(asset: string | undefined): number {
return asset === 'crypto' ? VEST_CRYPTO_STOP_SLIPPAGE_FRACTION : VEST_STOP_SLIPPAGE_FRACTION;
}
/**
* What to do with an open position before the daily reset so the reset can't