Vest: a split day's next leg gives back the day's gain plus the limit

On a split day the next Vest leg's daily-loss room now adds the day's gain
still in the account to the bot's limit (floorRoom's `gain`), never past
Vest's own floor, and the leg sizes up for the same target hold (Dingo's
next eval leg: $455k -> $842k at 1.5 h, win 28% -> 56%). Other platforms
are unchanged: dllRoom still never widens on a green day.

The gain only widens a leg all but sure to close before Vest's 8 PM ET
reset (resetClearStop, held to the shortest leg the day may carve), and
none of a hand claim's profit counts. The last-stand margin is measured
against the limit, so a green day can't drop a leg's stop.

Around the reset: no new Vest position opens in the last 7 minutes, and
the guard re-fits every 15 s on fresh positions instead of one pass. An
account's positions share the next-day room (stops first, least at risk
first; locked-in profit counts), a hand-moved stop is fitted again, and a
close or profit-side stop that doesn't take is retried or closed.

The dashboard's next stop now reserves for the leg's notional, as fireLeg
does, so DXtrade labels read smaller (display only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-27 07:21:52 -05:00
co-authored by Claude Opus 5.5
parent 1e844178de
commit f0c1569d96
9 changed files with 364 additions and 89 deletions
+13 -4
View File
@@ -112,15 +112,20 @@ export function nextVestResetMs(now: number): number {
* 'none' — its stop is already at least that tight;
* 'stop' — move (or place) the stop at `price`, on the tick grid, rounded
* toward the position's safe side;
* 'close' — no room at all, or the mark is already past that price.
* 'close' — the mark is already past that price (with no room left by
* the account's other positions, it sits on the profit side),
* or no room and no mark to tell.
*/
export function resetSafeStop(o: {
side: 'long' | 'short';
quantity: number;
openPrice: number;
mark: number;
/** Realized balance, collateral included — what the reset captures. */
/** Realized balance, collateral included — what the reset captures —
* less what the account's other positions may lose at their stops. */
balance: number;
/** The realized balance the floor is set from; default `balance`. */
floorBalance?: number;
dailyLossPct: number;
maxDrawdownFloor: number | undefined;
reserve: number;
@@ -129,10 +134,14 @@ export function resetSafeStop(o: {
currentStop: number | null;
tick: number;
}): { action: 'none' } | { action: 'stop'; price: number } | { action: 'close' } {
const floor = Math.max(o.balance * (1 - o.dailyLossPct), o.maxDrawdownFloor ?? -Infinity);
const floor = Math.max((o.floorBalance ?? o.balance) * (1 - o.dailyLossPct), o.maxDrawdownFloor ?? -Infinity);
const exitFee = o.exitFeeRate * o.quantity * (Number.isFinite(o.mark) && o.mark > 0 ? o.mark : o.openPrice);
const allowed = o.balance - floor - o.reserve - exitFee;
if (!(allowed > 0) || !(o.quantity > 0)) return { action: 'close' };
if (!(o.quantity > 0)) return { action: 'close' };
// No room to lose (the account's other positions took it): the stop must
// lock in at least the shortfall — on the profit side of the entry,
// which a position already that far up can still carry.
if (!(allowed > 0) && !Number.isFinite(o.mark)) return { action: 'close' };
const long = o.side === 'long';
const raw = long ? o.openPrice - allowed / o.quantity : o.openPrice + allowed / o.quantity;
const price = o.tick > 0