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:
co-authored by
Claude Opus 5.5
parent
1e844178de
commit
f0c1569d96
@@ -14,7 +14,7 @@ import {
|
||||
projectPerfectPath, cycleCompletion, DLL_RESERVE_FRACTION, DLL_MIN_STOP, notionalCap, expectedSwapToWin, MARGIN_HEADROOM,
|
||||
dllReserve, DLL_SLIPPAGE_FRACTION, maxLossRoom, lastStand, breachDistance, LAST_STAND_FRACTION, planLegSize, rankForTarget,
|
||||
currentTradeDateCT, foldReason, withinFirmCaps, personSizeFactor, personHoldHours, PERSON_SIZE_SPREAD, machineSide,
|
||||
resolveEffectiveConfig, stageInputsFor, floorRoom, computeDayPlan,
|
||||
resolveEffectiveConfig, stageInputsFor, floorRoom, resetClearStop, shortestNextLeg, computeDayPlan,
|
||||
} from '../lib/trading-logic.ts';
|
||||
|
||||
// Deterministic PRNG so a failure reproduces.
|
||||
@@ -441,6 +441,13 @@ test('last stand: no daily-loss stop once hitting the daily limit would leave 15
|
||||
assert.equal(lastStand({ dllRoom: 1000, dailyLossLimit: 3000, maxLossRoom: 1200 }), false);
|
||||
assert.equal(lastStand({ dllRoom: 1000, dailyLossLimit: 3000, maxLossRoom: 1150 }), true);
|
||||
assert.equal(lastStand({ dllRoom: Infinity, dailyLossLimit: 0, maxLossRoom: 500 }), false, 'no daily limit, no daily stop to drop');
|
||||
// A green day's widened room is measured against a full day's limit: a
|
||||
// $1,400 eval starting $1,700 over its floor, up $1,000 — leg 2's $2,400
|
||||
// stop-out leaves $300, over 15% of $1,400, so it keeps its stop.
|
||||
assert.equal(lastStand({ dllRoom: 2400, dailyLossLimit: 1400, maxLossRoom: 2700 }), false);
|
||||
assert.equal(lastStand({ dllRoom: 2400, dailyLossLimit: 1400, maxLossRoom: 2610 }), true);
|
||||
// The room past the floor is a last stand however it was widened.
|
||||
assert.equal(lastStand({ dllRoom: 2400, dailyLossLimit: 1400, maxLossRoom: 2300 }), true);
|
||||
// The far side of the walk is the nearer floor, net of the entry fee.
|
||||
assert.equal(breachDistance({ dllRoom: 3000, maxLossRoom: 2900, entryFee: 120 }), 2780);
|
||||
assert.equal(breachDistance({ dllRoom: 2757.39, maxLossRoom: null, entryFee: 0 }), 2757.39);
|
||||
@@ -476,6 +483,54 @@ test('planLegSize: the stop, a last stand, the margin cap and the cost, as fireL
|
||||
assert.equal(planLegSize({ ...base, maxLossRoom: null, contract: {} }).sizing, null);
|
||||
});
|
||||
|
||||
test('resetClearStop: the widest stop a leg may carry and still close before the reset', () => {
|
||||
const h = 3_600_000;
|
||||
const near = (a, b) => Math.abs(a - b) < 1e-6;
|
||||
// Exactly four holds off: a 2:1 leg, no more.
|
||||
assert.ok(near(resetClearStop({ leg: 2395.76, resetInMs: 6 * h, holdHours: 1.5 }), 2 * 2395.76));
|
||||
// Under four holds (the 5:30 PM CT open batch, 1.5 h before Vest's
|
||||
// reset): no widening at all.
|
||||
assert.equal(resetClearStop({ leg: 2395.76, resetInMs: 1.5 * h, holdHours: 1.5 }), 0);
|
||||
assert.equal(resetClearStop({ leg: 2395.76, resetInMs: 6 * h - 1, holdHours: 1.5 }), 0);
|
||||
// Dingo's second leg at ~4 AM ET, 16 h off: far past the $4,004 the day allows.
|
||||
assert.ok(resetClearStop({ leg: 2395.76, resetInMs: 16 * h, holdHours: 1.5 }) > 20000);
|
||||
// A $500 final leg 10 h off may carry about $2,656, not the $5,900 a
|
||||
// $4,500 gain would give: g = (A+B)²/(AB) stays at 4.5 × 10/6.
|
||||
const lopsided = resetClearStop({ leg: 500, resetInMs: 10 * h, holdHours: 1.5 });
|
||||
assert.ok(Math.abs(lopsided - 2656) < 1, `${lopsided}`);
|
||||
assert.ok(near((500 + lopsided) ** 2 / (500 * lopsided), 4.5 * 10 / 6));
|
||||
assert.equal(resetClearStop({ leg: 0, resetInMs: 16 * h, holdHours: 1.5 }), 0);
|
||||
assert.equal(resetClearStop({ leg: 500, resetInMs: 16 * h, holdHours: 0 }), 0);
|
||||
assert.equal(resetClearStop({ leg: 500, resetInMs: NaN, holdHours: 1.5 }), 0);
|
||||
// The cap is held to the shortest leg the day may carve: a split day's
|
||||
// $2,395.76 can come out a $500 leg.
|
||||
assert.equal(shortestNextLeg({ remainingToday: 2395.76, splitAllowed: true }), 500);
|
||||
assert.equal(shortestNextLeg({ remainingToday: 320, splitAllowed: true }), 320);
|
||||
assert.equal(shortestNextLeg({ remainingToday: 2395.76, splitAllowed: false }), 2395.76);
|
||||
// Dingo at ~4 AM ET still gets the whole $2,604.24: a $500 leg 16 h off
|
||||
// may carry ~$4,950.
|
||||
assert.ok(resetClearStop({ leg: 500, resetInMs: 16 * h, holdHours: 1.5 }) - 1400 > 2604.24);
|
||||
});
|
||||
|
||||
test('planLegSize: a split day\'s next leg, with the day\'s gain on its stop, sizes up for the same hold', () => {
|
||||
// Dingo's 2026-10-27 evals: up $2,604.24 on a $1,400 day, $2,395.76 to
|
||||
// pass, NDX at ~$29.5k on Vest (1.31%/day), sized for 1.5 h.
|
||||
const ndx = { stepNotional: 2.95, volatilityPerHour: 0.0131 / Math.sqrt(24), spreadFraction: 0.0000085, marginRate: 0.02 };
|
||||
const leg = {
|
||||
leg: 2395.76, contract: ndx, commissionPct: 0.0025, targetHoldHours: 1.5, financingPctPerDay: 0,
|
||||
dailyLossLimit: 1400, obfuscationDollars: Infinity, availableFunds: 52604.24, maxLossRoom: 5604.24,
|
||||
};
|
||||
const capped = planLegSize({ ...leg, dllRoom: 1400 });
|
||||
const room = floorRoom({ dailyLossLimit: 1400, closedBalance: 52604.24, floor: 48500, realizedToday: 2604.24, gain: 2604.24 });
|
||||
const widened = planLegSize({ ...leg, dllRoom: room });
|
||||
assert.equal(widened.lastStand, false);
|
||||
for (const p of [capped, widened]) assert.ok(Math.abs(p.sizing.predictedHoldHours - 1.5) < 0.05, `hold ${p.sizing.predictedHoldHours}`);
|
||||
assert.ok(widened.sizing.notional > 1.5 * capped.sizing.notional, `${widened.sizing.notional} vs ${capped.sizing.notional}`);
|
||||
// The stop gives back the day's gain plus the limit, less the reserve.
|
||||
const n = widened.sizing.notional;
|
||||
assert.ok(widened.walkAt(n) > 2604.24 && widened.walkAt(n) < 4004.24, `stop ${widened.walkAt(n)}`);
|
||||
});
|
||||
|
||||
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 draws = people.map((p) => personSizeFactor(p, '2026-10-20'));
|
||||
@@ -999,14 +1054,26 @@ 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 });
|
||||
});
|
||||
|
||||
test('floorRoom: the platform\'s own floor, never more than what\'s left of the stage\'s limit', () => {
|
||||
test('floorRoom: what\'s left of the stage\'s limit, widened by the day\'s gain the client allows, never past the platform\'s floor', () => {
|
||||
// Fresh day: $50k, Vest floor $48,500, the type's $1,229.
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50000, floor: 48500, realizedToday: 0 }), 1229);
|
||||
// Lost $1,111.65 today: Vest still allows $388.35, but the bot's own
|
||||
// $1,229 day has only $117.35 left.
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 48888.35, floor: 48500, realizedToday: -1111.65 }), 117.35);
|
||||
// A green day doesn't widen the limit.
|
||||
// A green day alone doesn't widen the limit...
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50600, floor: 48500, realizedToday: 600 }), 1229);
|
||||
// ...the gain the client allows does: the next leg may give it back plus
|
||||
// the limit, as on Dingo's 2026-10-27 evals ($1,400 a day, up $2,604.24)...
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50600, floor: 48500, realizedToday: 600, gain: 600 }), 1829);
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1400, closedBalance: 52604.24, floor: 48500, realizedToday: 2604.24, gain: 2604.24 }), 4004.24);
|
||||
// ...never past Vest's own floor...
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1400, closedBalance: 52604.24, floor: 49000, realizedToday: 2604.24, gain: 2604.24 }), 3604.24);
|
||||
// ...and never by more than the day realized: $2,250 claimed by hand
|
||||
// leaves realized at $2,250 (added back) but none of it in the account.
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50000, floor: 46250, realizedToday: 2250, gain: 0 }), 1229);
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 50600, floor: 48500, realizedToday: 600, gain: 5000 }), 1829);
|
||||
// A red day still counts in full, whatever gain is offered.
|
||||
assert.equal(floorRoom({ dailyLossLimit: 1229, closedBalance: 49500, floor: 48500, realizedToday: -500, gain: 300 }), 729);
|
||||
// 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);
|
||||
|
||||
Reference in New Issue
Block a user