Hide the next stop on a booked day; label min day P&L as such

On 2026-10-08 a Tradeify min day ($150) stopped out at −$306 on its 2×
stop of $300. The dashboard then read "stop $912": the next-stop label is
recomputed from the remaining target, which the loss grew to $456, for a
leg that never fires — the day is booked and the make-back rides tomorrow,
stopless.

- obfuscationDayBooked() holds the scheduler's booked-day check; the state
  route asks it too and shows no next stop, last stand or hold on a
  booked day.
- The account page's "Daily Loss Limit" showed minDayPnL. It now shows the
  type's real daily loss limit, with Min Day P&L on its own row; the
  objective that passed on total profit ≥ minDayPnL is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-10-08 16:06:35 -05:00
co-authored by Claude Opus 5.5
parent c3b58c8d8b
commit c0261f42ae
3 changed files with 40 additions and 26 deletions
+6 -10
View File
@@ -409,7 +409,10 @@ export default function AccountPage() {
const fundTransactions = account.fundTransactions ?? [];
const fundMap = new Map(fundTransactions.map((f) => [f.date, f.amount]));
const hasLossLimit = cfg != null && cfg.minDayPnL !== -999;
// The firm's hard daily loss limit, and the least a day must make to count
// as a trading day — two different numbers; neither is set on every type.
const dailyLossLimit = (cfg?.dailyLossLimit ?? 0) > 0 ? cfg!.dailyLossLimit! : null;
const minDayPnL = (cfg?.minDayPnL ?? 0) > 0 ? cfg!.minDayPnL : null;
const daysPassed = cfg != null && account.daysTraded >= cfg.minTradingDays;
// Dead when balance hits the liquidation floor — Tradovate reports its
// (positive) floor; TopstepX floors are reconstructed MLLs, negative or
@@ -427,7 +430,6 @@ export default function AccountPage() {
const profitPassed = cfg != null && totalProfit >= (account.effectiveProfitTarget ?? cfg.profitTarget);
// dailyTarget is computed server-side in the state API — single source of truth.
const dailyTarget = account.dailyTarget ?? null;
const lossPassed = !hasLossLimit || (cfg != null && totalProfit >= cfg.minDayPnL);
// Consistency target: the total profit level at which the best day no longer
// violates the consistency ratio. Only meaningful once a positive day exists.
@@ -610,7 +612,8 @@ export default function AccountPage() {
positive={profitPct != null && profitPct >= 0}
/>
<PerfRow label="Trading Days" value={account.daysTraded} />
<PerfRow label="Daily Loss Limit" value={hasLossLimit ? `$${fmt(cfg!.minDayPnL)}` : 'None'} />
<PerfRow label="Daily Loss Limit" value={dailyLossLimit !== null ? `$${fmt(dailyLossLimit)}` : 'None'} />
<PerfRow label="Min Day P&L" value={minDayPnL !== null ? `$${fmt(minDayPnL)}` : 'None'} />
</div>
<div className="flex flex-col w-1/2 bg-white border border-slate-200 rounded-xl shadow-sm p-6">
@@ -632,13 +635,6 @@ export default function AccountPage() {
label="Trading Days"
value={cfg ? `${account.daysTraded} of ${cfg.minTradingDays}` : '—'}
/>
{hasLossLimit && (
<ObjRow
passed={lossPassed}
label="Daily Loss Limit"
value={`$${fmt(cfg!.minDayPnL)}`}
/>
)}
{dailyTarget != null && (
<div className="flex items-center justify-between py-3 border-b border-slate-100 last:border-0">
<span className="text-slate-500 text-sm">Next Trading Day Amount</span>
+10 -3
View File
@@ -5,6 +5,7 @@ import { getCachedCatalog } from '@/lib/firm-catalog';
import { getAccountConfig, getInferredSize, matchAccountType } from '@/lib/account-type-resolver';
import { historyFresh } from '@/lib/platform-client';
import { rankNextLeg } from '@/lib/leg-estimate';
import { obfuscationDayBooked } from '@/lib/auto-trade';
import type { AccountState } from '@/types';
import {
combineCmeSessions, computeDayPlan, cycleCompletion, resolveEffectiveConfig, currentTradeDateCT,
@@ -123,8 +124,14 @@ export async function GET() {
balance: cash.amount, accountSize: cfg.accountSize, maxLoss: cfg.maxLoss ?? 0,
autoLiqThreshold: client.autoLiqThresholds?.[acc.id],
});
lastStandNext = dllRoom !== null && isLastStand({ dllRoom, dailyLossLimit: dll, maxLossRoom: floorRoom });
if (plan && (botStopsActive(cfg))) {
// A day an obfuscation stop booked has no next leg — the
// make-back rides tomorrow, stopless — so no next stop, last
// stand or hold to show. Its remaining target has grown by
// the loss: a $150 min day stopped at −$306 would read
// "stop $912" for a leg that never fires.
const dayBooked = obfuscationDayBooked(cfg, acc.id, tradeDate, combined.realizedPnL) !== null;
lastStandNext = !dayBooked && dllRoom !== null && isLastStand({ dllRoom, dailyLossLimit: dll, maxLossRoom: floorRoom });
if (plan && !dayBooked && botStopsActive(cfg)) {
const obf = obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday);
const hard = dllRoom !== null && !lastStandNext
? dllStopDollars({ room: dllRoom, dailyLossLimit: dll, entryFee: 0, commissionPct: 0 })
@@ -135,7 +142,7 @@ export async function GET() {
// DXtrade: the next leg sized on every surveyed instrument as
// fireLeg would, and the one a batch would pick.
if (plan && plan.remainingToday > 0) {
if (plan && !dayBooked && plan.remainingToday > 0) {
const best = rankNextLeg({
client, accountId: acc.id, cfg, firm: f, leg: plan.remainingToday, dllRoom,
obfuscationDollars: obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday),
+24 -13
View File
@@ -144,6 +144,28 @@ function legSumToday(accountId: number, tradeDate: string): number {
return entry && entry.date === tradeDate ? entry.sum : 0;
}
/**
* Why an obfuscation stop has booked the day, or null when it hasn't. A
* stop-out shows as realized P&L falling short of the legs fired today — the
* day is booked; the make-back ride is tomorrow. The plain negative-realized
* check is what survives a restart, when the in-memory leg counter has
* forgotten the morning's stop-out. The dashboard asks too: a booked day has
* no next leg, so no next stop to show.
*/
export function obfuscationDayBooked(
cfg: { firstDayStop: boolean; minDayStop: boolean },
accountId: number,
tradeDate: string,
realizedPnL: number,
): string | null {
if (!cfg.firstDayStop && !cfg.minDayStop) return null;
if (realizedPnL <= -25) return 'red day booked — make-back rides tomorrow';
if (legsToday(accountId, tradeDate) > 0 && realizedPnL < legSumToday(accountId, tradeDate) - 25) {
return 'stopped out — day booked';
}
return null;
}
interface EntryOutcome {
tradeDate: string;
state: 'filled' | 'unknown';
@@ -685,19 +707,8 @@ function evaluateAccountForTrade(client: any, acc: { id: number; name: string; a
return { ...base, eligible: false, reason: 'exit refused — flattened, benched for the day' };
}
// Obfuscation stops: a stop-out shows as realized P&L falling short of the
// legs fired today — the day is booked; the make-back ride is tomorrow.
// The plain negative-realized check is what survives a restart, when the
// in-memory leg counter has forgotten the morning's stop-out.
if (cfg.firstDayStop || cfg.minDayStop) {
if (combined.realizedPnL <= -25) {
return { ...base, eligible: false, reason: 'red day booked — make-back rides tomorrow' };
}
if (legsToday(acc.id, tradeDate) > 0
&& combined.realizedPnL < legSumToday(acc.id, tradeDate) - 25) {
return { ...base, eligible: false, reason: 'stopped out — day booked' };
}
}
const booked = obfuscationDayBooked(cfg, acc.id, tradeDate, combined.realizedPnL);
if (booked) return { ...base, eligible: false, reason: booked };
const priorProfit = client.priorProfit?.[acc.id] ?? 0;
const allFundTxns = client.fundTransactions?.[acc.id] ?? [];