diff --git a/app/accounts/[id]/page.tsx b/app/accounts/[id]/page.tsx
index e076541..cbd130f 100644
--- a/app/accounts/[id]/page.tsx
+++ b/app/accounts/[id]/page.tsx
@@ -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}
/>
-
+
+
@@ -632,13 +635,6 @@ export default function AccountPage() {
label="Trading Days"
value={cfg ? `${account.daysTraded} of ${cfg.minTradingDays}` : '—'}
/>
- {hasLossLimit && (
-
- )}
{dailyTarget != null && (
Next Trading Day Amount
diff --git a/app/api/state/route.ts b/app/api/state/route.ts
index aa5d28c..8463600 100644
--- a/app/api/state/route.ts
+++ b/app/api/state/route.ts
@@ -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),
diff --git a/lib/auto-trade.ts b/lib/auto-trade.ts
index 0116e14..6b70b29 100644
--- a/lib/auto-trade.ts
+++ b/lib/auto-trade.ts
@@ -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] ?? [];