Vest: stops reserve 0.02% of the position for slippage, not 0.1%

A platform may now state its own stop slippage share
(PlatformClient.stopSlippageFraction); the daily-loss reserve is the
larger of 10% of the limit and that share of the position. Vest's 9
measured stop-outs filled 0.9 points past the trigger on average and
2.25 at worst, against the ~30 points 0.1% held back. On a $1,400 day
with ~$495k of NDX the stop goes from ~$880 to ~$1,230, and the leg
sizes up for the same hold. Sizing, the placed stop, the dashboard's
next stop and the reset guard all use it; other platforms keep 0.1%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-27 11:34:27 -05:00
co-authored by Claude Opus 5.5
parent f0c1569d96
commit beddbdcf6c
11 changed files with 75 additions and 18 deletions
+1 -1
View File
@@ -167,7 +167,7 @@ per-contract fees and the CME calendar.
Per account type, in the **Hard Daily Loss** column: the **daily loss limit** Per account type, in the **Hard Daily Loss** column: the **daily loss limit**
(Tradeify: 3% of size — every leg then carries a stop that keeps the firm's (Tradeify: 3% of size — every leg then carries a stop that keeps the firm's
day inside it, less fees and a reserve of 10% of the limit or 0.1% of the day inside it, less fees and a reserve of 10% of the limit or 0.1% of the
position, whichever is more) and the **static max drawdown** (Tradeify position, whichever is more — 0.02% on Vest, whose stops fill closer) and the **static max drawdown** (Tradeify
1-Step: $6,000 on 100K). When hitting the day's loss limit would leave the 1-Step: $6,000 on 100K). When hitting the day's loss limit would leave the
account within 15% of that loss above its max-drawdown floor, the leg is a account within 15% of that loss above its max-drawdown floor, the leg is a
*last stand*: no daily-loss stop, running to its take-profit or the firm's *last stand*: no daily-loss stop, running to its take-profit or the firm's
+1 -1
View File
@@ -176,7 +176,7 @@ export async function GET() {
// The reserve grows with the position (dllReserve): at // The reserve grows with the position (dllReserve): at
// the size the next leg would trade, where it's known. // the size the next leg would trade, where it's known.
const hard = dllRoom !== null && !lastStandNext const hard = dllRoom !== null && !lastStandNext
? dllStopDollars({ room: dllRoom, dailyLossLimit: dll, entryFee: 0, commissionPct: 0, notional: holdEstimate?.notional }) ? dllStopDollars({ room: dllRoom, dailyLossLimit: dll, entryFee: 0, commissionPct: 0, notional: holdEstimate?.notional, slippageFraction: client.stopSlippageFraction })
: Infinity; : Infinity;
const stop = Math.min(Number.isFinite(obf) && obf > 0 ? obf : Infinity, hard); const stop = Math.min(Number.isFinite(obf) && obf > 0 ? obf : Infinity, hard);
if (Number.isFinite(stop) && stop > 0) nextStop = Math.round(stop * 100) / 100; if (Number.isFinite(stop) && stop > 0) nextStop = Math.round(stop * 100) / 100;
+1 -1
View File
@@ -473,7 +473,7 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned, onStateChang
<span className="text-red-600" title="Last stand: hitting the day's loss limit would leave this account within 15% of that loss above its max-drawdown floor — nothing left to trade on — so the next leg carries no daily-loss stop and runs to its take-profit or the firm's breach"> · no stop (last stand)</span> <span className="text-red-600" title="Last stand: hitting the day's loss limit would leave this account within 15% of that loss above its max-drawdown floor — nothing left to trade on — so the next leg carries no daily-loss stop and runs to its take-profit or the firm's breach"> · no stop (last stand)</span>
)} )}
{typeof account.dllRoom === 'number' && ( {typeof account.dllRoom === 'number' && (
<span className="text-slate-500" title="Hard daily loss limit: what the firm's day may still lose before a breach. A reserve — 10% of the limit, or 0.1% of the position if larger — is never spent."> · DLL room ${fmt(account.dllRoom)}</span> <span className="text-slate-500" title="Hard daily loss limit: what the firm's day may still lose before a breach. A reserve — 10% of the limit, or 0.1% of the position if larger (0.02% on Vest) — is never spent."> · DLL room ${fmt(account.dllRoom)}</span>
)} )}
{account.holdEstimate && ( {account.holdEstimate && (
<span <span
+2 -1
View File
@@ -1399,6 +1399,7 @@ function sizeLeg(item: SizingItem, contract: LegContract, legAmount: number, tra
maxLossRoom: floorRoom, maxLossRoom: floorRoom,
obfuscationDollars: obfuscation, obfuscationDollars: obfuscation,
availableFunds: live.availableFunds ?? live.amount, availableFunds: live.availableFunds ?? live.amount,
slippageFraction: item.client.stopSlippageFraction,
}); });
return { ...plan, pct, obfuscation, maxLossRoom: floorRoom }; return { ...plan, pct, obfuscation, maxLossRoom: floorRoom };
} }
@@ -1545,7 +1546,7 @@ async function fireLeg(
const dllFee = pct > 0 ? entryFee : (measured ?? MAX_COMMISSION_PER_CONTRACT * contracts); const dllFee = pct > 0 ? entryFee : (measured ?? MAX_COMMISSION_PER_CONTRACT * contracts);
const notional = fill.price * pv * contracts; const notional = fill.price * pv * contracts;
const dllDollars = dllOn && !plan.lastStand const dllDollars = dllOn && !plan.lastStand
? dllStopDollars({ room: item.dllRoom, dailyLossLimit: dll, entryFee: dllFee, commissionPct: pct, notional }) ? dllStopDollars({ room: item.dllRoom, dailyLossLimit: dll, entryFee: dllFee, commissionPct: pct, notional, slippageFraction: client.stopSlippageFraction })
: Infinity; : Infinity;
const ticks = stopTicks({ obfuscationDollars: obfuscation, dllDollars, pointValue: pv, contracts, tickSize: legContract.tickSize }); const ticks = stopTicks({ obfuscationDollars: obfuscation, dllDollars, pointValue: pv, contracts, tickSize: legContract.tickSize });
if (ticks === 0) { if (ticks === 0) {
+1
View File
@@ -100,6 +100,7 @@ export function rankNextLeg(o: {
maxLossRoom: floorRoom, maxLossRoom: floorRoom,
obfuscationDollars: o.obfuscationDollars, obfuscationDollars: o.obfuscationDollars,
availableFunds: funds, availableFunds: funds,
slippageFraction: o.client.stopSlippageFraction,
}); });
const p = plan(true); const p = plan(true);
if (!p.sizing || p.cost === null) return []; if (!p.sizing || p.cost === null) return [];
+6 -6
View File
@@ -1,9 +1,9 @@
/** /**
* The hold this machine sizes DXtrade legs for. Every person's machine would * The hold this machine sizes DXtrade and Vest legs for. Every person's
* otherwise size a like account in a like state to the same position as * machine would otherwise size a like account in a like state to the same
* everyone else's; each person's own draw (personHoldHours) puts their sizes * position as everyone else's; each person's own draw (personHoldHours) puts
* within ±PERSON_SIZE_SPREAD of the plan instead, the same for all of their * their sizes within ±PERSON_SIZE_SPREAD of the plan instead, the same for
* accounts and new each trade date. * all of their accounts and new each trade date.
*/ */
import { getSetting } from './db'; import { getSetting } from './db';
@@ -15,7 +15,7 @@ function personKey(): string {
return getSetting('instance_id') || getSetting('instance_name') || ''; return getSetting('instance_id') || getSetting('instance_name') || '';
} }
/** The hold a DXtrade firm's legs are sized for on this machine today. */ /** The hold a DXtrade or Vest firm's legs are sized for on this machine today. */
export function sizingHoldHours( export function sizingHoldHours(
firm: { targetHoldHours?: number } | null | undefined, firm: { targetHoldHours?: number } | null | undefined,
tradeDate: string = currentTradeDateCT(), tradeDate: string = currentTradeDateCT(),
+5
View File
@@ -74,6 +74,11 @@ export interface PlatformClient {
fundTransactions: { [accountId: number]: { date: string; amount: number }[] }; fundTransactions: { [accountId: number]: { date: string; amount: number }[] };
priorProfit: { [accountId: number]: number }; priorProfit: { [accountId: number]: number };
autoLiqThresholds: { [accountId: number]: number }; autoLiqThresholds: { [accountId: number]: number };
/** Share of a position's notional a stop may fill past its price — the
* part of the daily-loss reserve that grows with size (dllReserve).
* Only where this platform's stops are measured to fill closer than
* DLL_SLIPPAGE_FRACTION, the default. */
stopSlippageFraction?: number;
fetchDaysComplete: boolean; fetchDaysComplete: boolean;
syncComplete: boolean; syncComplete: boolean;
+10 -6
View File
@@ -1327,12 +1327,14 @@ export function obfuscationStopDollars(
* that fills worse than its price (gaps, thin weekend books). */ * that fills worse than its price (gaps, thin weekend books). */
export const DLL_RESERVE_FRACTION = 0.10; export const DLL_RESERVE_FRACTION = 0.10;
/** …or this share of the position's notional, when that is more: a stop's /** …or this share of the position's notional, when that is more: a stop's
* slippage grows with the position, a flat reserve doesn't. */ * slippage grows with the position, a flat reserve doesn't. A platform whose
* stops are known to fill closer states its own (stopSlippageFraction). */
export const DLL_SLIPPAGE_FRACTION = 0.001; export const DLL_SLIPPAGE_FRACTION = 0.001;
/** The part of the day's room a stop leaves unspent. */ /** The part of the day's room a stop leaves unspent. */
export function dllReserve(dailyLossLimit: number, notional = 0): number { export function dllReserve(dailyLossLimit: number, notional = 0, slippageFraction = DLL_SLIPPAGE_FRACTION): number {
return Math.max(DLL_RESERVE_FRACTION * dailyLossLimit, DLL_SLIPPAGE_FRACTION * Math.max(0, notional)); const share = Number.isFinite(slippageFraction) && slippageFraction >= 0 ? slippageFraction : DLL_SLIPPAGE_FRACTION;
return Math.max(DLL_RESERVE_FRACTION * dailyLossLimit, share * Math.max(0, notional));
} }
/** Smallest daily-loss stop worth trading. Under a fair walk a leg of L with a /** Smallest daily-loss stop worth trading. Under a fair walk a leg of L with a
* stop of S wins S/(S + L) of the time — about 9% for $50 against $500 — so * stop of S wins S/(S + L) of the time — about 9% for $50 against $500 — so
@@ -1454,10 +1456,10 @@ export function dllRoom(o: {
* reset has its entry fee on the old day's books, and the swap takes no more * reset has its entry fee on the old day's books, and the swap takes no more
* than that slot on the new day's. * than that slot on the new day's.
*/ */
export function dllStopDollars(o: { room: number; dailyLossLimit: number; entryFee: number; commissionPct: number; notional?: number }): number { export function dllStopDollars(o: { room: number; dailyLossLimit: number; entryFee: number; commissionPct: number; notional?: number; slippageFraction?: number }): number {
if (!Number.isFinite(o.room)) return Infinity; if (!Number.isFinite(o.room)) return Infinity;
const r = Math.max(0, o.commissionPct) / 100; const r = Math.max(0, o.commissionPct) / 100;
return (o.room - dllReserve(o.dailyLossLimit, o.notional) - 2 * o.entryFee - 0.01) / (1 + r); return (o.room - dllReserve(o.dailyLossLimit, o.notional, o.slippageFraction) - 2 * o.entryFee - 0.01) / (1 + r);
} }
/** /**
@@ -1765,6 +1767,8 @@ export function planLegSize(o: {
/** Infinity (or ≤ 0) = no obfuscation stop. */ /** Infinity (or ≤ 0) = no obfuscation stop. */
obfuscationDollars: number; obfuscationDollars: number;
availableFunds?: number; availableFunds?: number;
/** The platform's stopSlippageFraction; default DLL_SLIPPAGE_FRACTION. */
slippageFraction?: number;
}): { }): {
lastStand: boolean; lastStand: boolean;
cap: number; cap: number;
@@ -1780,7 +1784,7 @@ export function planLegSize(o: {
obf, obf,
!dllOn ? Infinity !dllOn ? Infinity
: stand ? breachDistance({ dllRoom: o.dllRoom, maxLossRoom: o.maxLossRoom, entryFee: r * notional }) : stand ? breachDistance({ dllRoom: o.dllRoom, maxLossRoom: o.maxLossRoom, entryFee: r * notional })
: dllStopDollars({ room: o.dllRoom, dailyLossLimit: o.dailyLossLimit, entryFee: r * notional, commissionPct: o.commissionPct, notional }), : dllStopDollars({ room: o.dllRoom, dailyLossLimit: o.dailyLossLimit, entryFee: r * notional, commissionPct: o.commissionPct, notional, slippageFraction: o.slippageFraction }),
); );
const cap = notionalCap({ marginRate: o.contract.marginRate, availableFunds: o.availableFunds }); const cap = notionalCap({ marginRate: o.contract.marginRate, availableFunds: o.availableFunds });
const step = o.contract.stepNotional ?? 0; const step = o.contract.stepNotional ?? 0;
+3 -2
View File
@@ -53,7 +53,7 @@ import { vestSessionFor, dropVestSession, listVestSessions, refuseVestSession }
import { import {
vestAccountId, vestAccountName, vestDayOf, lastVestResetMs, nextVestResetMs, parseCapitalAccount, parsePosition, parseOpenOrder, vestAccountId, vestAccountName, vestDayOf, lastVestResetMs, nextVestResetMs, parseCapitalAccount, parsePosition, parseOpenOrder,
parseTxn, bookTxns, awaitingLegs, unrealizedPnl, hourlyVolatility, marketOpen, num, toMs, formatDecimal, resetSafeStop, parseTxn, bookTxns, awaitingLegs, unrealizedPnl, hourlyVolatility, marketOpen, num, toMs, formatDecimal, resetSafeStop,
parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_TXN, parseHistoryPosition, jwtClaimStrings, accountScopedToken, VEST_STATUS_ACTIVE, VEST_TXN, VEST_STOP_SLIPPAGE_FRACTION,
} from './vest-helpers'; } from './vest-helpers';
import type { VestCapitalAccount, VestPosition, VestOpenOrder, VestTxn, VestTrade } from './vest-helpers'; import type { VestCapitalAccount, VestPosition, VestOpenOrder, VestTxn, VestTrade } from './vest-helpers';
@@ -174,6 +174,7 @@ export class VestClient implements PlatformClient {
public fundTransactions: { [accountId: number]: { date: string; amount: number }[] } = {}; public fundTransactions: { [accountId: number]: { date: string; amount: number }[] } = {};
public priorProfit: { [accountId: number]: number } = {}; public priorProfit: { [accountId: number]: number } = {};
public autoLiqThresholds: { [accountId: number]: number } = {}; public autoLiqThresholds: { [accountId: number]: number } = {};
public readonly stopSlippageFraction = VEST_STOP_SLIPPAGE_FRACTION;
public fetchDaysComplete = false; public fetchDaysComplete = false;
public syncComplete = false; public syncComplete = false;
@@ -1010,7 +1011,7 @@ export class VestClient implements PlatformClient {
side: pos.side, quantity: pos.quantity, openPrice: pos.openPrice, mark, side: pos.side, quantity: pos.quantity, openPrice: pos.openPrice, mark,
balance: cash.amount - committed, dailyLossPct: a.maxDailyLossPct, maxDrawdownFloor: a.maxDrawdownLimit, balance: cash.amount - committed, dailyLossPct: a.maxDailyLossPct, maxDrawdownFloor: a.maxDrawdownLimit,
floorBalance: cash.amount, floorBalance: cash.amount,
reserve: dllReserve(cash.amount * a.maxDailyLossPct, notionalSoFar), reserve: dllReserve(cash.amount * a.maxDailyLossPct, notionalSoFar, this.stopSlippageFraction),
exitFeeRate: spec.takerFee, currentStop, tick: spec.tick, exitFeeRate: spec.takerFee, currentStop, tick: spec.tick,
}); });
const stopAt = decision.action === 'stop' ? decision.price : decision.action === 'none' ? currentStop : null; const stopAt = decision.action === 'stop' ? decision.price : decision.action === 'none' ? currentStop : null;
+6
View File
@@ -101,6 +101,12 @@ export function nextVestResetMs(now: number): number {
return NaN; return NaN;
} }
/** Vest's stopSlippageFraction: 0.02% of the position, about 6 NDX points.
* Measured 2026-10-27 on 9 bot stop-outs (Me, $74k–$132k): fills 0.9 points
* 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;
/** /**
* What to do with an open position before the daily reset so the reset can't * What to do with an open position before the daily reset so the reset can't
* breach it. At 20:00 ET Vest captures the realized balance (collateral in, * breach it. At 20:00 ET Vest captures the realized balance (collateral in,
+39
View File
@@ -419,6 +419,21 @@ test('the daily-loss reserve grows with the position past 10% of the limit', ()
assert.equal(dllStopDollars({ room: 3000, dailyLossLimit: 3000, entryFee: 0, commissionPct: 0 }), 2699.99); assert.equal(dllStopDollars({ room: 3000, dailyLossLimit: 3000, entryFee: 0, commissionPct: 0 }), 2699.99);
}); });
test('a platform whose stops fill closer reserves its own share of the position', () => {
// Me's Vest eval on 2026-10-27: $1,400 of room on $495,439 of NDX. At the
// 0.1% default the reserve is $495.44; at 0.02% the flat $140 binds.
const n = 495439;
assert.ok(Math.abs(dllReserve(1400, n) - 495.439) < 1e-9);
assert.equal(dllReserve(1400, n, 0.0002), 140);
assert.ok(Math.abs(dllReserve(1400, 900000, 0.0002) - 180) < 1e-9, 'past $700k the share binds again');
// Left out, or not a share at all: the default.
for (const bad of [undefined, NaN, -0.0002, Infinity]) assert.equal(dllReserve(1400, n, bad), dllReserve(1400, n), String(bad));
assert.equal(dllReserve(1400, n, 0), 140, 'no slippage at all still keeps the flat 10%');
const stop = (slippageFraction) => dllStopDollars({ room: 1400, dailyLossLimit: 1400, entryFee: 12.45, commissionPct: 0, notional: n, slippageFraction });
assert.ok(Math.abs(stop(undefined) - (1400 - 495.439 - 24.9 - 0.01)) < 1e-9);
assert.ok(Math.abs(stop(0.0002) - (1400 - 140 - 24.9 - 0.01)) < 1e-9);
});
test('max-drawdown room: the static floor, the broker floor, the higher', () => { test('max-drawdown room: the static floor, the broker floor, the higher', () => {
assert.equal(maxLossRoom({ balance: 99757.39, accountSize: 100000, maxLoss: 6000 }), 5757.39); assert.equal(maxLossRoom({ balance: 99757.39, accountSize: 100000, maxLoss: 6000 }), 5757.39);
assert.equal(maxLossRoom({ balance: 99757.39, accountSize: 100000, maxLoss: -6000 }), 5757.39); assert.equal(maxLossRoom({ balance: 99757.39, accountSize: 100000, maxLoss: -6000 }), 5757.39);
@@ -531,6 +546,30 @@ test('planLegSize: a split day\'s next leg, with the day\'s gain on its stop, si
assert.ok(widened.walkAt(n) > 2604.24 && widened.walkAt(n) < 4004.24, `stop ${widened.walkAt(n)}`); assert.ok(widened.walkAt(n) > 2604.24 && widened.walkAt(n) < 4004.24, `stop ${widened.walkAt(n)}`);
}); });
test('planLegSize: a platform\'s own slippage share widens the stop and sizes the leg up for the same hold', () => {
// Me's first-day Vest eval leg on 2026-10-27: $5,000 to pass, $1,400 of
// room, NDX at ~$29.8k, sized for 2.6 h.
const ndx = { stepNotional: 2.98, volatilityPerHour: 0.0131 / Math.sqrt(24), spreadFraction: 0.0000085, marginRate: 0.02 };
const leg = {
leg: 5000, contract: ndx, commissionPct: 0.0025, targetHoldHours: 2.6, financingPctPerDay: 0,
dailyLossLimit: 1400, dllRoom: 1400, obfuscationDollars: Infinity, availableFunds: 50000, maxLossRoom: 3000,
};
const deflt = planLegSize(leg);
const vest = planLegSize({ ...leg, slippageFraction: 0.0002 });
assert.equal(planLegSize({ ...leg, slippageFraction: undefined }).sizing.notional, deflt.sizing.notional);
for (const p of [deflt, vest]) {
assert.equal(p.lastStand, false);
assert.ok(Math.abs(p.sizing.predictedHoldHours - 2.6) < 0.05, `hold ${p.sizing.predictedHoldHours}`);
}
const r = 0.0025 / 100;
const nd = deflt.sizing.notional, nv = vest.sizing.notional;
assert.ok(Math.abs(deflt.walkAt(nd) - (1400 - 0.001 * nd - 2 * r * nd - 0.01) / (1 + r)) < 1e-9);
assert.ok(Math.abs(vest.walkAt(nv) - (1400 - 140 - 2 * r * nv - 0.01) / (1 + r)) < 1e-9, 'the flat $140 binds');
assert.ok(vest.walkAt(nv) > 1200 && deflt.walkAt(nd) < 950, `stops ${vest.walkAt(nv)} vs ${deflt.walkAt(nd)}`);
assert.ok(nv > 1.1 * nd, `${nv} vs ${nd}`);
assert.ok(vest.sizing.winProbability > deflt.sizing.winProbability);
});
test('personSizeFactor: one draw per person and trade date, within the spread', () => { test('personSizeFactor: one draw per person and trade date, within the spread', () => {
const people = Array.from({ length: 300 }, (_, i) => `0b6f${i.toString(16).padStart(4, '0')}-5c1e-4a7e-9d2b-${i}`); const people = Array.from({ length: 300 }, (_, i) => `0b6f${i.toString(16).padStart(4, '0')}-5c1e-4a7e-9d2b-${i}`);
const draws = people.map((p) => personSizeFactor(p, '2026-10-20')); const draws = people.map((p) => personSizeFactor(p, '2026-10-20'));