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:
co-authored by
Claude Opus 5.5
parent
c3b58c8d8b
commit
c0261f42ae
@@ -409,7 +409,10 @@ export default function AccountPage() {
|
|||||||
const fundTransactions = account.fundTransactions ?? [];
|
const fundTransactions = account.fundTransactions ?? [];
|
||||||
const fundMap = new Map(fundTransactions.map((f) => [f.date, f.amount]));
|
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;
|
const daysPassed = cfg != null && account.daysTraded >= cfg.minTradingDays;
|
||||||
// Dead when balance hits the liquidation floor — Tradovate reports its
|
// Dead when balance hits the liquidation floor — Tradovate reports its
|
||||||
// (positive) floor; TopstepX floors are reconstructed MLLs, negative or
|
// (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);
|
const profitPassed = cfg != null && totalProfit >= (account.effectiveProfitTarget ?? cfg.profitTarget);
|
||||||
// dailyTarget is computed server-side in the state API — single source of truth.
|
// dailyTarget is computed server-side in the state API — single source of truth.
|
||||||
const dailyTarget = account.dailyTarget ?? null;
|
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
|
// Consistency target: the total profit level at which the best day no longer
|
||||||
// violates the consistency ratio. Only meaningful once a positive day exists.
|
// violates the consistency ratio. Only meaningful once a positive day exists.
|
||||||
@@ -610,7 +612,8 @@ export default function AccountPage() {
|
|||||||
positive={profitPct != null && profitPct >= 0}
|
positive={profitPct != null && profitPct >= 0}
|
||||||
/>
|
/>
|
||||||
<PerfRow label="Trading Days" value={account.daysTraded} />
|
<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>
|
||||||
|
|
||||||
<div className="flex flex-col w-1/2 bg-white border border-slate-200 rounded-xl shadow-sm p-6">
|
<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"
|
label="Trading Days"
|
||||||
value={cfg ? `${account.daysTraded} of ${cfg.minTradingDays}` : '—'}
|
value={cfg ? `${account.daysTraded} of ${cfg.minTradingDays}` : '—'}
|
||||||
/>
|
/>
|
||||||
{hasLossLimit && (
|
|
||||||
<ObjRow
|
|
||||||
passed={lossPassed}
|
|
||||||
label="Daily Loss Limit"
|
|
||||||
value={`$${fmt(cfg!.minDayPnL)}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{dailyTarget != null && (
|
{dailyTarget != null && (
|
||||||
<div className="flex items-center justify-between py-3 border-b border-slate-100 last:border-0">
|
<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>
|
<span className="text-slate-500 text-sm">Next Trading Day Amount</span>
|
||||||
|
|||||||
+10
-3
@@ -5,6 +5,7 @@ import { getCachedCatalog } from '@/lib/firm-catalog';
|
|||||||
import { getAccountConfig, getInferredSize, matchAccountType } from '@/lib/account-type-resolver';
|
import { getAccountConfig, getInferredSize, matchAccountType } from '@/lib/account-type-resolver';
|
||||||
import { historyFresh } from '@/lib/platform-client';
|
import { historyFresh } from '@/lib/platform-client';
|
||||||
import { rankNextLeg } from '@/lib/leg-estimate';
|
import { rankNextLeg } from '@/lib/leg-estimate';
|
||||||
|
import { obfuscationDayBooked } from '@/lib/auto-trade';
|
||||||
import type { AccountState } from '@/types';
|
import type { AccountState } from '@/types';
|
||||||
import {
|
import {
|
||||||
combineCmeSessions, computeDayPlan, cycleCompletion, resolveEffectiveConfig, currentTradeDateCT,
|
combineCmeSessions, computeDayPlan, cycleCompletion, resolveEffectiveConfig, currentTradeDateCT,
|
||||||
@@ -123,8 +124,14 @@ export async function GET() {
|
|||||||
balance: cash.amount, accountSize: cfg.accountSize, maxLoss: cfg.maxLoss ?? 0,
|
balance: cash.amount, accountSize: cfg.accountSize, maxLoss: cfg.maxLoss ?? 0,
|
||||||
autoLiqThreshold: client.autoLiqThresholds?.[acc.id],
|
autoLiqThreshold: client.autoLiqThresholds?.[acc.id],
|
||||||
});
|
});
|
||||||
lastStandNext = dllRoom !== null && isLastStand({ dllRoom, dailyLossLimit: dll, maxLossRoom: floorRoom });
|
// A day an obfuscation stop booked has no next leg — the
|
||||||
if (plan && (botStopsActive(cfg))) {
|
// 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 obf = obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday);
|
||||||
const hard = dllRoom !== null && !lastStandNext
|
const hard = dllRoom !== null && !lastStandNext
|
||||||
? dllStopDollars({ room: dllRoom, dailyLossLimit: dll, entryFee: 0, commissionPct: 0 })
|
? 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
|
// DXtrade: the next leg sized on every surveyed instrument as
|
||||||
// fireLeg would, and the one a batch would pick.
|
// fireLeg would, and the one a batch would pick.
|
||||||
if (plan && plan.remainingToday > 0) {
|
if (plan && !dayBooked && plan.remainingToday > 0) {
|
||||||
const best = rankNextLeg({
|
const best = rankNextLeg({
|
||||||
client, accountId: acc.id, cfg, firm: f, leg: plan.remainingToday, dllRoom,
|
client, accountId: acc.id, cfg, firm: f, leg: plan.remainingToday, dllRoom,
|
||||||
obfuscationDollars: obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday),
|
obfuscationDollars: obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday),
|
||||||
|
|||||||
+24
-13
@@ -144,6 +144,28 @@ function legSumToday(accountId: number, tradeDate: string): number {
|
|||||||
return entry && entry.date === tradeDate ? entry.sum : 0;
|
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 {
|
interface EntryOutcome {
|
||||||
tradeDate: string;
|
tradeDate: string;
|
||||||
state: 'filled' | 'unknown';
|
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' };
|
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
|
const booked = obfuscationDayBooked(cfg, acc.id, tradeDate, combined.realizedPnL);
|
||||||
// legs fired today — the day is booked; the make-back ride is tomorrow.
|
if (booked) return { ...base, eligible: false, reason: booked };
|
||||||
// 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 priorProfit = client.priorProfit?.[acc.id] ?? 0;
|
const priorProfit = client.priorProfit?.[acc.id] ?? 0;
|
||||||
const allFundTxns = client.fundTransactions?.[acc.id] ?? [];
|
const allFundTxns = client.fundTransactions?.[acc.id] ?? [];
|
||||||
|
|||||||
Reference in New Issue
Block a user