Last stand: measured against the full daily loss, not the stop after its reserve
The rule is: when hitting the day's loss limit would leave the account 15% of that loss or less above its max-drawdown floor, ride without a daily-loss stop. It had compared against the stop-out after the 10% reserve, which made it stricter — $3,294.70 to the floor with $3,000 of daily room (9.8% left) wasn't a last stand, and now is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ff9b107452
commit
5a4e85cc18
@@ -169,9 +169,10 @@ 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) and the **static max drawdown** (Tradeify
|
||||||
1-Step: $6,000 on 100K). When a daily-loss stop-out would leave the account
|
1-Step: $6,000 on 100K). When hitting the day's loss limit would leave the
|
||||||
within 15% of that loss above its max-drawdown floor, the leg is a *last
|
account within 15% of that loss above its max-drawdown floor, the leg is a
|
||||||
stand*: no daily-loss stop, running to its take-profit or the firm's breach.
|
*last stand*: no daily-loss stop, running to its take-profit or the firm's
|
||||||
|
breach.
|
||||||
|
|
||||||
Before the scheduler trades a DXtrade firm, run the smoke test against a real
|
Before the scheduler trades a DXtrade firm, run the smoke test against a real
|
||||||
login (with this instance's client for the firm disconnected):
|
login (with this instance's client for the firm disconnected):
|
||||||
|
|||||||
+1
-1
@@ -388,7 +388,7 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
|
|||||||
<span className="text-amber-600" title="The next trade's stop — dollars of price movement it may lose before booking the day (fees come on top; the daily-loss stop already reserves for them)"> · stop ${fmt(account.nextStop)}</span>
|
<span className="text-amber-600" title="The next trade's stop — dollars of price movement it may lose before booking the day (fees come on top; the daily-loss stop already reserves for them)"> · stop ${fmt(account.nextStop)}</span>
|
||||||
)}
|
)}
|
||||||
{account.lastStand && (
|
{account.lastStand && (
|
||||||
<span className="text-red-600" title="Last stand: a daily-loss stop-out would leave this account within 15% of that loss of 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 — is never spent."> · DLL room ${fmt(account.dllRoom)}</span>
|
||||||
|
|||||||
+11
-11
@@ -1242,23 +1242,23 @@ export function maxLossRoom(o: { balance: number; accountSize: number; maxLoss:
|
|||||||
return floors.length > 0 ? round2(o.balance - Math.max(...floors)) : null;
|
return floors.length > 0 ? round2(o.balance - Math.max(...floors)) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A daily-loss stop is kept only while a stop-out would leave at least this
|
/** A daily-loss stop is kept only while the max-drawdown floor lies more than
|
||||||
* share of its loss above the max-drawdown floor. */
|
* this share of a full daily loss beyond it. */
|
||||||
export const LAST_STAND_FRACTION = 0.15;
|
export const LAST_STAND_FRACTION = 0.15;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a leg should ride without its daily-loss stop. The stop is there to
|
* Whether a leg should ride without its daily-loss stop. The stop is there to
|
||||||
* keep the account alive for another day; when a stop-out would leave it
|
* keep the account alive for another day; when hitting the day's loss limit
|
||||||
* within 15% of that loss of its max-drawdown floor — or the floor is nearer
|
* would leave it within 15% of that loss of its max-drawdown floor — or the
|
||||||
* than the stop — there is no useful day after, and the stop only gives up
|
* floor is nearer than the limit — there is no useful day after, and the stop
|
||||||
* room (the reserve, and whatever the floor allows past it) for nothing. The
|
* only gives up room (the reserve, and whatever the floor allows past it)
|
||||||
* leg then runs until its take-profit or the firm's own breach.
|
* for nothing. The leg then runs until its take-profit or the firm's own
|
||||||
|
* breach. E.g. $3,000 of daily room with $3,294.70 to the floor: $294.70
|
||||||
|
* left, 9.8% — a last stand.
|
||||||
*/
|
*/
|
||||||
export function lastStand(o: { dllRoom: number; dailyLossLimit: number; maxLossRoom: number | null }): boolean {
|
export function lastStand(o: { dllRoom: number; dailyLossLimit: number; maxLossRoom: number | null }): boolean {
|
||||||
if (o.maxLossRoom === null || !(o.dailyLossLimit > 0) || !Number.isFinite(o.dllRoom)) return false;
|
if (o.maxLossRoom === null || !(o.dailyLossLimit > 0) || !Number.isFinite(o.dllRoom) || !(o.dllRoom > 0)) return false;
|
||||||
const stopOut = o.dllRoom - DLL_RESERVE_FRACTION * o.dailyLossLimit;
|
return o.maxLossRoom - o.dllRoom <= LAST_STAND_FRACTION * o.dllRoom;
|
||||||
if (!(stopOut > 0)) return false;
|
|
||||||
return o.maxLossRoom - stopOut <= LAST_STAND_FRACTION * stopOut;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** On a last stand: how far price can move against the leg before the firm's
|
/** On a last stand: how far price can move against the leg before the firm's
|
||||||
|
|||||||
@@ -375,16 +375,19 @@ test('max-drawdown room: the static floor, the broker floor, the higher', () =>
|
|||||||
assert.equal(maxLossRoom({ balance: 99000, accountSize: 100000, maxLoss: 0 }), null);
|
assert.equal(maxLossRoom({ balance: 99000, accountSize: 100000, maxLoss: 0 }), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('last stand: no daily-loss stop once a stop-out would leave under 15% of it', () => {
|
test('last stand: no daily-loss stop once hitting the daily limit would leave 15% of it or less', () => {
|
||||||
// Full day: a stop-out spends $2,700 (room less the $300 reserve).
|
// A full day's $3,000 of room.
|
||||||
const at = (maxRoom) => lastStand({ dllRoom: 3000, dailyLossLimit: 3000, maxLossRoom: maxRoom });
|
const at = (maxRoom) => lastStand({ dllRoom: 3000, dailyLossLimit: 3000, maxLossRoom: maxRoom });
|
||||||
assert.equal(LAST_STAND_FRACTION, 0.15);
|
assert.equal(LAST_STAND_FRACTION, 0.15);
|
||||||
assert.equal(at(5757.39), false, 'a fresh 1-Step keeps its stop');
|
assert.equal(at(5757.39), false, 'a fresh 1-Step keeps its stop');
|
||||||
assert.equal(at(2700 * 1.15 + 1), false, 'just over 15% left: keep the stop');
|
assert.equal(at(3000 * 1.15 + 1), false, 'just over 15% left: keep the stop');
|
||||||
assert.equal(at(2700 * 1.15), true, '15% left: last stand');
|
assert.equal(at(3000 * 1.15), true, '15% left: last stand');
|
||||||
assert.equal(at(3000), true, 'two stopped days in: last stand');
|
assert.equal(at(3294.70), true, 'one stopped day in: $294.70 left, 9.8% — last stand');
|
||||||
assert.equal(at(800), true, 'floor nearer than the stop: last stand');
|
assert.equal(at(800), true, 'floor nearer than the limit: last stand');
|
||||||
assert.equal(at(null), false, 'no floor known');
|
assert.equal(at(null), false, 'no floor known');
|
||||||
|
// Measured against what's left of the day: $1,000 of room, $1,200 to the floor.
|
||||||
|
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');
|
assert.equal(lastStand({ dllRoom: Infinity, dailyLossLimit: 0, maxLossRoom: 500 }), false, 'no daily limit, no daily stop to drop');
|
||||||
// The far side of the walk is the nearer floor, net of the entry fee.
|
// 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: 3000, maxLossRoom: 2900, entryFee: 120 }), 2780);
|
||||||
|
|||||||
Reference in New Issue
Block a user