Files
autofirmer-expanded/app/api/accounts/[id]/wipe/route.ts
T
Brandon LiandClaude Opus 5.5 8e66631b0e Entries of unknown outcome hold the account until settled; wipes keep their guards
Fifth review round (1eb0736):

- An entry now has three outcomes. Filled (confirmed): one-and-done is done
  for the day. Never placed: leg taken back, free to retry. Unknown (an ack
  or fill that never came back): the account — any mode — is held until a
  good sync at least 90s later shows it flat with realized P&L unchanged,
  then the leg is taken back and it may retry. Stop-after-all counts it in
  play meanwhile. This closes both the day-long bench of 52ed9c7 and the
  stacked re-entries of 1eb0736.
- Tradovate cancels an acked order whose fill didn't come within 30s (as
  TopstepX already does), so a retry can't sit beside a still-working one.
- A wipe keeps the rebuild's guards: wipeAccountData leaves the Cash History
  baseline, roll base, wipe hold, pre-wipe fund signature and the
  resolver's inferred size in account_meta; the routes forget history
  before wiping (so the stored rows are still there to read), and a wipe
  hold restored after a restart keeps its pre-wipe yardstick.
- Wipe routes take the set of typed accounts before wiping, so an account
  the resolver un-assigns mid-rebuild still counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:12:51 -05:00

48 lines
2.0 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getClients } from '@/lib/clients';
import { getAccountConfig } from '@/lib/account-type-resolver';
import { wipeAccountData } from '@/lib/db';
/**
* POST /api/accounts/[id]/wipe — drop every cached row for the account and
* rebuild it from a fresh Tradovate report.
*
* Destructive on purpose: cached rows older than the report's 28-day window
* cannot be refetched. The UI warns and confirms before calling this.
*/
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const accountId = Number(id);
if (!Number.isInteger(accountId)) {
return NextResponse.json({ error: 'Bad account id' }, { status: 400 });
}
for (const client of getClients().values()) {
const account = client.accountList.find((a) => a.id === accountId);
if (!account) continue;
// Read before the wipe: the resolver may un-assign it mid-rebuild.
const ours = Boolean(getAccountConfig(accountId).accountType);
// Clear the in-memory view too — a failed refetch must leave the
// account visibly empty, not showing the data that was just wiped —
// and out of trading until a fetch rebuilds it. First, so the client
// reads the stored rows it keeps as the rebuild's yardstick.
client.forgetHistory(accountId, account.name);
wipeAccountData(accountId);
await client.fetchDaysTraded([accountId]);
const loaded = client.historyReady(accountId) || !ours;
return NextResponse.json({
ok: true,
entries: (client.dailyPnL[accountId] ?? []).length,
daysTraded: client.daysTraded[accountId] ?? 0,
fetchError: client.lastFetchErrors[account.name]
?? (loaded ? null : 'History did not reload — the account stays out of trading until it does'),
});
}
return NextResponse.json({ error: 'Account not found' }, { status: 404 });
}