Vest: a day that lost to the bot's limit stops, even with Vest room left

The daily-loss room on Vest was min(the stage's limit, balance − Vest's
floor), which ignored the day's losses against the bot's own limit. With
the type's $1,229 under Vest's 3% ($1,500), five 50k evals that had
lost $1,111.65 today still saw $388 of room and traded again. The room
is now min(limit + the day's realized loss, balance − floor).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-26 18:55:54 -05:00
co-authored by Claude Opus 5.5
parent 32081f3dcf
commit 29eaf64a6b
4 changed files with 39 additions and 14 deletions
+4 -1
View File
@@ -631,7 +631,10 @@ function dailyLossGate(
if (floor === null) return { reason: DLL_BASE_PENDING }; if (floor === null) return { reason: DLL_BASE_PENDING };
let room: number; let room: number;
if (typeof floor === 'number') { if (typeof floor === 'number') {
room = floorRoom({ dailyLossLimit: dll, closedBalance: cash.amount, floor }); room = floorRoom({
dailyLossLimit: dll, closedBalance: cash.amount, floor,
realizedToday: Math.min(cash.realizedPnL, combinedRealized),
});
} else { } else {
const dayStart: number | null | undefined = client.dayStartBalance?.(accountId); const dayStart: number | null | undefined = client.dayStartBalance?.(accountId);
if (dayStart === null) return { reason: DLL_BASE_PENDING }; if (dayStart === null) return { reason: DLL_BASE_PENDING };
+6 -1
View File
@@ -31,7 +31,12 @@ export function dayLossRoom(
if (!(dll > 0)) return null; if (!(dll > 0)) return null;
const floor: number | null | undefined = client.dailyLossFloor?.(accountId); const floor: number | null | undefined = client.dailyLossFloor?.(accountId);
if (floor === null) return null; if (floor === null) return null;
if (typeof floor === 'number') return floorRoom({ dailyLossLimit: dll, closedBalance: cash.amount, floor }); if (typeof floor === 'number') {
return floorRoom({
dailyLossLimit: dll, closedBalance: cash.amount, floor,
realizedToday: Math.min(cash.realizedPnL, combinedRealized),
});
}
const dayStart = client.dayStartBalance?.(accountId); const dayStart = client.dayStartBalance?.(accountId);
return dayStart === null ? null : computeDllRoom({ return dayStart === null ? null : computeDllRoom({
dailyLossLimit: dll, closedBalance: cash.amount, dayStartBalance: dayStart, dailyLossLimit: dll, closedBalance: cash.amount, dayStartBalance: dayStart,
+14 -4
View File
@@ -1368,12 +1368,22 @@ export function stageInputsFor(
/** /**
* dllRoom where the platform states its daily-loss floor itself (Vest: the * dllRoom where the platform states its daily-loss floor itself (Vest: the
* equity the account may not fall below today, already moved by the day's * equity the account may not fall below today, already moved by the day's
* transfers): never more than the stage's limit, nor than the floor leaves. * transfers): what's left of the stage's limit after the day's losses, and
* Infinity when there is no limit. * never more than the floor leaves. The firm's floor alone isn't enough — a
* limit set below the firm's (Vest's 3% is $1,500; the bot's day stops at
* $1,229) would let a day that already lost $1,111 trade the $389 Vest still
* allows. Infinity when there is no limit.
*/ */
export function floorRoom(o: { dailyLossLimit: number; closedBalance: number; floor: number }): number { export function floorRoom(o: {
dailyLossLimit: number;
closedBalance: number;
floor: number;
/** The day's realized P&L since its base (Vest: since this client's
* latest claim today, else since the reset). */
realizedToday: number;
}): number {
if (!(o.dailyLossLimit > 0)) return Infinity; if (!(o.dailyLossLimit > 0)) return Infinity;
return round2(Math.min(o.dailyLossLimit, o.closedBalance - o.floor)); return round2(Math.min(o.dailyLossLimit + Math.min(0, o.realizedToday), o.closedBalance - o.floor));
} }
export function dllRoom(o: { export function dllRoom(o: {
+15 -8
View File
@@ -999,17 +999,24 @@ test('same-day stages: today\'s payouts pick the stage, and a stage may carry it
assert.deepEqual(stageInputsFor({}, 7, { stagesSameDay: true }), { dailyLossLimit: 0, stagesSameDay: true, sameDayWithdrawals: 0 }); assert.deepEqual(stageInputsFor({}, 7, { stagesSameDay: true }), { dailyLossLimit: 0, stagesSameDay: true, sameDayWithdrawals: 0 });
}); });
test('floorRoom: the platform\'s own floor, never more than the stage\'s limit', () => { test('floorRoom: the platform\'s own floor, never more than what\'s left of the stage\'s limit', () => {
// Fresh day: $50k, Vest floor $48,500, the type's $1,229. // Fresh day: $50k, Vest floor $48,500, the type's $1,229.
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50000, floor: 48500 }), 1229); assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50000, floor: 48500, realizedToday: 0 }), 1229);
// Won $1,500 and claimed it: balance $50k, Vest's floor lowered to $47k. // Lost $1,111.65 today: Vest still allows $388.35, but the bot's own
// Stage 2's $3,000 limit gets all of it. // $1,229 day has only $117.35 left.
assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 50000, floor: 47000 }), 3000); assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 48888.35, floor: 48500, realizedToday: -1111.65 }), 117.35);
// A green day doesn't widen the limit.
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50600, floor: 48500, realizedToday: 600 }), 1229);
// Won $1,500 and claimed it: balance $50k, Vest's floor lowered to $47k,
// the day's P&L counted from the claim. Stage 2's $3,000 gets all of it.
assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 50000, floor: 47000, realizedToday: 0 }), 3000);
// ...and after a $500 stage-2 loss, $2,500 either way.
assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 49500, floor: 47000, realizedToday: -500 }), 2500);
// If Vest hadn't lowered the floor, only what it really leaves. // If Vest hadn't lowered the floor, only what it really leaves.
assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 50000, floor: 48500 }), 1500); assert.equal(floorRoom({ dailyLossLimit: 3000, closedBalance: 50000, floor: 48500, realizedToday: 0 }), 1500);
// Below the floor already: negative room (no trade). // Below the floor already: negative room (no trade).
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 48400, floor: 48500 }), -100); assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 48400, floor: 48500, realizedToday: -1600 }), -371);
assert.equal(floorRoom({ dailyLossLimit: 0, closedBalance: 50000, floor: 48500 }), Infinity); assert.equal(floorRoom({ dailyLossLimit: 0, closedBalance: 50000, floor: 48500, realizedToday: -900 }), Infinity);
}); });
test('the day after a claim: a fresh cycle that aims at the next stage\'s target', () => { test('the day after a claim: a fresh cycle that aims at the next stage\'s target', () => {