diff --git a/README.md b/README.md
index 0d292ef..bbe789f 100644
--- a/README.md
+++ b/README.md
@@ -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
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
-1-Step: $6,000 on 100K). When a daily-loss stop-out would leave the account
-within 15% of that loss above its max-drawdown floor, the leg is a *last
-stand*: no daily-loss stop, running to its take-profit or the firm's breach.
+1-Step: $6,000 on 100K). When hitting the day's loss limit would leave the
+account within 15% of that loss above its max-drawdown floor, the leg is a
+*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
login (with this instance's client for the firm disconnected):
diff --git a/app/page.tsx b/app/page.tsx
index b3cd058..701fb40 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -388,7 +388,7 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
· stop ${fmt(account.nextStop)}
)}
{account.lastStand && (
- · no stop (last stand)
+ · no stop (last stand)
)}
{typeof account.dllRoom === 'number' && (
· DLL room ${fmt(account.dllRoom)}
diff --git a/lib/trading-logic.ts b/lib/trading-logic.ts
index d74a9fe..cfacdb4 100644
--- a/lib/trading-logic.ts
+++ b/lib/trading-logic.ts
@@ -1242,23 +1242,23 @@ export function maxLossRoom(o: { balance: number; accountSize: number; maxLoss:
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
- * share of its loss above the max-drawdown floor. */
+/** A daily-loss stop is kept only while the max-drawdown floor lies more than
+ * this share of a full daily loss beyond it. */
export const LAST_STAND_FRACTION = 0.15;
/**
* 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
- * within 15% of that loss of its max-drawdown floor — or the floor is nearer
- * than the stop — there is no useful day after, and the stop only gives up
- * room (the reserve, and whatever the floor allows past it) for nothing. The
- * leg then runs until its take-profit or the firm's own breach.
+ * keep the account alive for another day; when hitting the day's loss limit
+ * would leave it within 15% of that loss of its max-drawdown floor — or the
+ * floor is nearer than the limit — there is no useful day after, and the stop
+ * only gives up room (the reserve, and whatever the floor allows past it)
+ * 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 {
- if (o.maxLossRoom === null || !(o.dailyLossLimit > 0) || !Number.isFinite(o.dllRoom)) return false;
- const stopOut = o.dllRoom - DLL_RESERVE_FRACTION * o.dailyLossLimit;
- if (!(stopOut > 0)) return false;
- return o.maxLossRoom - stopOut <= LAST_STAND_FRACTION * stopOut;
+ 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;
}
/** On a last stand: how far price can move against the leg before the firm's
diff --git a/scripts/check-trading-logic.mjs b/scripts/check-trading-logic.mjs
index fcb69bd..a060d74 100644
--- a/scripts/check-trading-logic.mjs
+++ b/scripts/check-trading-logic.mjs
@@ -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);
});
-test('last stand: no daily-loss stop once a stop-out would leave under 15% of it', () => {
- // Full day: a stop-out spends $2,700 (room less the $300 reserve).
+test('last stand: no daily-loss stop once hitting the daily limit would leave 15% of it or less', () => {
+ // A full day's $3,000 of room.
const at = (maxRoom) => lastStand({ dllRoom: 3000, dailyLossLimit: 3000, maxLossRoom: maxRoom });
assert.equal(LAST_STAND_FRACTION, 0.15);
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(2700 * 1.15), true, '15% left: last stand');
- assert.equal(at(3000), true, 'two stopped days in: last stand');
- assert.equal(at(800), true, 'floor nearer than the stop: last stand');
+ assert.equal(at(3000 * 1.15 + 1), false, 'just over 15% left: keep the stop');
+ assert.equal(at(3000 * 1.15), true, '15% left: 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 limit: last stand');
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');
// 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);