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
+57
-13
@@ -1339,17 +1339,6 @@ export function dllReserve(dailyLossLimit: number, notional = 0): number {
|
||||
* anything tighter just spends the day's room on fees. */
|
||||
export const DLL_MIN_STOP = 50;
|
||||
|
||||
/**
|
||||
* How much the firm's day may still lose before a hard daily-loss breach.
|
||||
* Infinity when the account type has no limit.
|
||||
*
|
||||
* The day's P&L is the closed balance against the platform's snapshot at the
|
||||
* firm's daily reset when the client knows it (DXTrade: 22:00 UTC), else the
|
||||
* broker's realized P&L for the session. Only a red day counts: a green day
|
||||
* does NOT widen the room past the limit, because a position still open at
|
||||
* the next reset has its whole move counted against the NEW day — room
|
||||
* borrowed from today's profit would be a breach tomorrow.
|
||||
*/
|
||||
/** The stage inputs for one account: the type's daily loss limit, and for a
|
||||
* type whose stages reset each day, the client's count of today's payouts. */
|
||||
export function stageInputsFor(
|
||||
@@ -1365,6 +1354,37 @@ export function stageInputsFor(
|
||||
};
|
||||
}
|
||||
|
||||
/** A leg is given this many of its target holds to be done before a daily
|
||||
* reset counts its open loss against the next day: under a fair walk, a leg
|
||||
* whose take-profit and stop are within 2:1 is still open after four holds
|
||||
* about 1–1.5% of the time, and the reset guard fits those. */
|
||||
export const RESET_CLEAR_HOLDS = 4;
|
||||
|
||||
/** The shortest take-profit the plan's next leg may have — what
|
||||
* resetClearStop is held to: a split day can carve a leg as small as
|
||||
* MIN_LEG (pickLegAmount), else the leg is all that remains. */
|
||||
export function shortestNextLeg(plan: { remainingToday: number; splitAllowed: boolean }): number {
|
||||
return plan.splitAllowed ? Math.min(plan.remainingToday, MIN_LEG) : plan.remainingToday;
|
||||
}
|
||||
|
||||
/**
|
||||
* The widest stop a leg whose take-profit is `leg` away may carry and still
|
||||
* be all but surely closed by a daily reset `resetInMs` away; 0 when the
|
||||
* reset is under RESET_CLEAR_HOLDS holds off. The walk's exit time has a
|
||||
* tail that decays as e^(−π²·t/(2g·hold)), g = (A+B)²/(A·B): a lopsided leg
|
||||
* (a $5,000 stop on a $500 take-profit) lingers far longer than its hold. So
|
||||
* the leg may be as lopsided as k = holds-to-reset ÷ RESET_CLEAR_HOLDS times
|
||||
* the 2:1 shape (g = 4.5) allows — g ≤ 4.5k — which keeps its chance of
|
||||
* still being open at the reset what a 2:1 leg's is at four holds.
|
||||
*/
|
||||
export function resetClearStop(o: { leg: number; resetInMs: number; holdHours: number }): number {
|
||||
if (!(o.leg > 0) || !(o.holdHours > 0) || !Number.isFinite(o.resetInMs)) return 0;
|
||||
const k = o.resetInMs / (RESET_CLEAR_HOLDS * o.holdHours * 3_600_000);
|
||||
if (k < 1) return 0;
|
||||
const c = 4.5 * k - 2;
|
||||
return (o.leg * (c + Math.sqrt(c * c - 4))) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -1373,6 +1393,14 @@ export function stageInputsFor(
|
||||
* 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.
|
||||
*
|
||||
* `gain` widens it: on a split day the next leg may give back what the
|
||||
* earlier legs made, plus the limit — a $1,400 day up $2,604 gets $4,004 —
|
||||
* as Vest's own floor does. The client says how much (dayGainForRoom): the
|
||||
* gain still in the account, no more than keeps the leg all but sure to be
|
||||
* closed by the reset (resetClearStop) — a leg still open there has all of
|
||||
* its loss counted against the next day. Never more than the day's realized
|
||||
* gain either way.
|
||||
*/
|
||||
export function floorRoom(o: {
|
||||
dailyLossLimit: number;
|
||||
@@ -1381,11 +1409,25 @@ export function floorRoom(o: {
|
||||
/** The day's realized P&L since its base (Vest: since this client's
|
||||
* latest claim today, else since the reset). */
|
||||
realizedToday: number;
|
||||
/** What of the day's gain the room may grow by; 0 (default) = none. */
|
||||
gain?: number;
|
||||
}): number {
|
||||
if (!(o.dailyLossLimit > 0)) return Infinity;
|
||||
return round2(Math.min(o.dailyLossLimit + Math.min(0, o.realizedToday), o.closedBalance - o.floor));
|
||||
const widen = Math.min(Math.max(0, o.realizedToday), Math.max(0, o.gain ?? 0));
|
||||
return round2(Math.min(o.dailyLossLimit + Math.min(0, o.realizedToday) + widen, o.closedBalance - o.floor));
|
||||
}
|
||||
|
||||
/**
|
||||
* How much the firm's day may still lose before a hard daily-loss breach.
|
||||
* Infinity when the account type has no limit.
|
||||
*
|
||||
* The day's P&L is the closed balance against the platform's snapshot at the
|
||||
* firm's daily reset when the client knows it (DXTrade: 22:00 UTC), else the
|
||||
* broker's realized P&L for the session. Only a red day counts: a green day
|
||||
* does NOT widen the room past the limit, because a position still open at
|
||||
* the next reset has its whole move counted against the NEW day — room
|
||||
* borrowed from today's profit would be a breach tomorrow.
|
||||
*/
|
||||
export function dllRoom(o: {
|
||||
dailyLossLimit: number;
|
||||
closedBalance: number;
|
||||
@@ -1447,7 +1489,9 @@ export const LAST_STAND_FRACTION = 0.15;
|
||||
*/
|
||||
export function lastStand(o: { dllRoom: number; dailyLossLimit: number; maxLossRoom: number | null }): boolean {
|
||||
if (o.maxLossRoom === null || !(o.dailyLossLimit > 0) || !Number.isFinite(o.dllRoom) || !(o.dllRoom > 0)) return false;
|
||||
return o.maxLossRoom - o.dllRoom <= LAST_STAND_FRACTION * o.dllRoom;
|
||||
// A full day's loss is the limit, however much a green day widened the
|
||||
// room (floorRoom): a stop-out leaving $300 over the floor keeps a day.
|
||||
return o.maxLossRoom - o.dllRoom <= LAST_STAND_FRACTION * Math.min(o.dllRoom, o.dailyLossLimit);
|
||||
}
|
||||
|
||||
/** On a last stand: how far price can move against the leg before the firm's
|
||||
|
||||
Reference in New Issue
Block a user