Add day-plan double-count diagnostics
The daily target sometimes inflates to base + consistency×todayPnL (e.g. 1200 + 0.4×1218.48 = 1687.39), which happens when a session's cached daily-P&L row is dated something other than the current tradeDate: it then lands in BOTH the prior-day list and realizedPnL, so day-start equity reads too low while the prior-day sum stays high. Detect it with a book-balance invariant that is coincidence-proof (it does not care whether prior and today P&L happen to be equal): the reconstructed day-start equity (equityProfit − realizedPnL) must equal priorProfit + sum(prior days). The state route now logs the full account state whenever those disagree by more than $50, and GET /api/debug reports the same per-account breakdown on demand (?account=<substring>) so the exact inputs can be captured the moment a number looks wrong. Read-only; no behavior change. Remove once the trigger is fixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5a69ea2f64
commit
ef476ccbb7
+52
-2
@@ -1,12 +1,59 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getClients, resetClients } from '@/lib/clients';
|
||||
import { getTradableFirms } from '@/lib/firm-catalog';
|
||||
import { getAccountConfig } from '@/lib/account-type-resolver';
|
||||
import { combineCmeSessions, computeDayPlan, resolveEffectiveConfig, currentTradeDateCT } from '@/lib/trading-logic';
|
||||
import axios from 'axios';
|
||||
|
||||
export async function GET() {
|
||||
/**
|
||||
* Per-account snapshot of everything computeDayPlan sees, plus the book-balance
|
||||
* check the state route warns on: day-start equity (equityProfit − realizedPnL)
|
||||
* must equal priorProfit + sum(prior days). A large `drift` means a session is
|
||||
* counted in both the prior-day list and realizedPnL — the daily-target inflation
|
||||
* bug. Filter with ?account=<substring> (case-insensitive). Read-only.
|
||||
*/
|
||||
function dayPlanDiag(client: any, acc: { id: number; name: string }, tradeDate: string) {
|
||||
const cfg = getAccountConfig(acc.id).accountType;
|
||||
const cash = client.accountCashBalances[acc.id] ?? { amount: 0, realizedPnL: 0 };
|
||||
if (!cfg) return { tradeDate, note: 'no account type assigned', cash, rawDailyPnL: client.dailyPnL[acc.id] ?? [] };
|
||||
|
||||
const priorProfit = client.priorProfit?.[acc.id] ?? 0;
|
||||
const allFundTxns = client.fundTransactions?.[acc.id] ?? [];
|
||||
const combined = combineCmeSessions(cfg.combineCmeHolidays === true, client.dailyPnL[acc.id] ?? [], tradeDate, cash.realizedPnL);
|
||||
const effective = resolveEffectiveConfig(cfg.profitTarget, cfg.consistency, cfg.minTradingDays, cfg.targetSameEquity, cfg.withdrawalStages, priorProfit, allFundTxns);
|
||||
const equityProfit = cash.amount - cfg.accountSize;
|
||||
const plan = computeDayPlan(acc.id, combined.tradeDate, effective.profitTarget, effective.consistency, combined.dailyPnL, cfg.minDayPnL, effective.minTradingDays, equityProfit, combined.realizedPnL, cfg.splitEnabled);
|
||||
|
||||
const priorDaysSum = combined.dailyPnL.filter((d) => d.date !== combined.tradeDate).reduce((s, d) => s + d.pnl, 0);
|
||||
const dayStartEquity = Math.round((equityProfit - combined.realizedPnL) * 100) / 100;
|
||||
const expectedDayStart = Math.round((priorProfit + priorDaysSum) * 100) / 100;
|
||||
|
||||
return {
|
||||
tradeDate: combined.tradeDate,
|
||||
drift: Math.round((dayStartEquity - expectedDayStart) * 100) / 100,
|
||||
dayStartEquity,
|
||||
expectedDayStart,
|
||||
equityProfit,
|
||||
realizedPnL: combined.realizedPnL,
|
||||
priorProfit,
|
||||
priorDaysSum: Math.round(priorDaysSum * 100) / 100,
|
||||
dayTarget: plan?.dayTarget ?? null,
|
||||
remainingToday: plan?.remainingToday ?? null,
|
||||
path: plan?.path ?? null,
|
||||
consistency: effective.consistency,
|
||||
hasTodayRow: combined.dailyPnL.some((d) => d.date === combined.tradeDate),
|
||||
combineCmeHolidays: cfg.combineCmeHolidays === true,
|
||||
combinedDailyPnL: combined.dailyPnL,
|
||||
rawDailyPnL: client.dailyPnL[acc.id] ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const clients = getClients();
|
||||
const firms = getTradableFirms();
|
||||
const tradeDate = currentTradeDateCT();
|
||||
const nameFilter = (new URL(req.url).searchParams.get('account') ?? '').toLowerCase();
|
||||
|
||||
const debug = firms.map((firm) => {
|
||||
const client = clients.get(firm.id);
|
||||
@@ -20,13 +67,16 @@ export async function GET() {
|
||||
fetchDaysComplete: (client as any).fetchDaysComplete ?? 'n/a (old instance)',
|
||||
lastFetchErrors: (client as any).lastFetchErrors ?? {},
|
||||
lastFetchRaw: (client as any).lastFetchRaw ?? {},
|
||||
accounts: client.accountList.map((acc) => ({
|
||||
accounts: client.accountList
|
||||
.filter((acc) => !nameFilter || acc.name.toLowerCase().includes(nameFilter))
|
||||
.map((acc) => ({
|
||||
id: acc.id,
|
||||
name: acc.name,
|
||||
cash: client.accountCashBalances[acc.id] ?? null,
|
||||
daysTraded: client.daysTraded[acc.id] ?? '(not set)',
|
||||
dailyPnLEntries: (client.dailyPnL[acc.id] ?? []).length,
|
||||
dailyPnL: client.dailyPnL[acc.id] ?? [],
|
||||
dayPlan: dayPlanDiag(client, acc, tradeDate),
|
||||
})),
|
||||
recentEntityEvents: client.recentEntityEvents.slice(-10).map((e) => ({
|
||||
ts: new Date(e.ts).toISOString(),
|
||||
|
||||
Reference in New Issue
Block a user