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] ?? [];