From 5a4e85cc18036be85ea2bfff08cb4463a3401660 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Sun, 27 Sep 2026 13:43:07 -0500 Subject: [PATCH] Last stand: measured against the full daily loss, not the stop after its reserve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 7 ++++--- app/page.tsx | 2 +- lib/trading-logic.ts | 22 +++++++++++----------- scripts/check-trading-logic.mjs | 15 +++++++++------ 4 files changed, 25 insertions(+), 21 deletions(-) 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);