diff --git a/app/api/state/route.ts b/app/api/state/route.ts index 2065fac..970782b 100644 --- a/app/api/state/route.ts +++ b/app/api/state/route.ts @@ -3,7 +3,10 @@ import { loadDailyPnL } from '@/lib/db'; import { getClients } from '@/lib/clients'; import { getCachedCatalog } from '@/lib/firm-catalog'; import { getAccountConfig, getInferredSize, matchAccountType } from '@/lib/account-type-resolver'; -import { combineCmeSessions, computeDayPlan, resolveEffectiveConfig, currentTradeDateCT, LEG_COMPLETION_EPSILON } from '@/lib/trading-logic'; +import { + combineCmeSessions, computeDayPlan, resolveEffectiveConfig, currentTradeDateCT, + BIG_DAY_THRESHOLD, FIRST_DAY_STOP, LEG_COMPLETION_EPSILON, MIN_DAY_STOP_MULT, +} from '@/lib/trading-logic'; export async function GET() { try { @@ -66,6 +69,7 @@ export async function GET() { let dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null = null; let remainingToday: number | null = null; let effectiveProfitTarget: number | null = null; + let nextStop: number | null = null; if (cfg && !isDead) { const effective = resolveEffectiveConfig( cfg.profitTarget, @@ -85,6 +89,27 @@ export async function GET() { dailyTarget = plan ? { amount: plan.dayTarget, path: plan.path } : null; remainingToday = plan?.remainingToday ?? null; + // The stop the next trade would carry under the obfuscation + // options, mirroring obfuscationStopDollars in auto-trade: + // day 1 keeps the day's net floor at −$1,000, min days stop + // at 2× the target, and the day after a loss rides stopless. + if (plan && (cfg.firstDayStop || cfg.minDayStop)) { + const prior = dailyPnL + .filter((d) => d.date < combined.tradeDate && d.pnl !== 0) + .sort((a, b) => a.date.localeCompare(b.date)); + const recoveryDay = prior.length > 0 && prior[prior.length - 1].pnl < 0; + if (!recoveryDay) { + let stop = Infinity; + if (cfg.firstDayStop && prior.length === 0) { + stop = Math.max(0, Math.round((FIRST_DAY_STOP + combined.realizedPnL) * 100) / 100); + } + if (cfg.minDayStop && plan.dayTarget <= BIG_DAY_THRESHOLD) { + stop = Math.min(stop, MIN_DAY_STOP_MULT * plan.dayTarget); + } + if (Number.isFinite(stop) && stop > 0) nextStop = Math.round(stop * 100) / 100; + } + } + // Effective profit target = max(stage target, consistency realTarget). // If a big day forces the consistency rule, the account must reach maxDay/consistency // in total trading profit for the stage, not just profitTarget. @@ -168,6 +193,7 @@ export async function GET() { targetHit, stage, dailyTarget, + nextStop, remainingToday, effectiveProfitTarget, dailyPnL, diff --git a/app/page.tsx b/app/page.tsx index c0c2059..3499b30 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -350,7 +350,12 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: { {account.dailyTarget && ( - ${fmt(account.dailyTarget.amount)} today + + ${fmt(account.dailyTarget.amount)} today + {account.nextStop !== null && ( + · stop ${fmt(account.nextStop)} + )} + )} diff --git a/types.ts b/types.ts index 156ea55..0049e6a 100644 --- a/types.ts +++ b/types.ts @@ -56,6 +56,10 @@ export interface AccountState { stage: number; /** Next trading day's target, computed server-side. null when account is dead or config is missing. */ dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null; + /** Dollars the next trade may lose before its obfuscation stop fires. + * null = the next trade rides stopless (options off, big non-first day, + * or a make-back day after a loss). */ + nextStop: number | null; /** Actual profit target to hit — max(stage profit target, consistency realTarget = maxDay/consistency). */ effectiveProfitTarget: number | null; /** Current cycle daily P&L — used for target calculations. */