- DXtrade client (lib/dxtrade-class.ts): REST session login with lockout-safe backoff, 60s sync of accounts/metrics/positions/orders/transfers, history and fund transactions from cash transactions, day-start balance at the 22:00 UTC reset, CLOSE-by-positionCode exits (native OCO group, else a stop-first emulated pair), a cross-account hedge guard, stray-order reaper. - Instrument selector: the firm's symbol map lists candidate symbols per CME root; the cheapest tradable one per unit of recent hourly volatility wins (spread + 2x commission [+ financing] / sigma). - Fees: a firm's commission % of notional prices every fee off the fill; the take-profit is solved in closed form so the leg nets its target. - Hard daily loss limit per account type: every leg carries a stop keeping the firm's day inside the limit less a 10% reserve and both fees (rounded in); a green day never widens it; no room or a refused bracket flattens. - Notional sizing where the platform prices positions: target-hold volatility sizing or the symbol map's fixed notional, under a per-type notional cap. - Every-day firms trade weekends/holidays; the scheduler runs lanes (futures, one per DXtrade firm) with their own hours, flat gate, mutex and batches. - The catalog sync skips platforms this build doesn't know instead of treating them as Tradovate. One shared fireLeg replaces the duplicated batch/copy code. - npm run check: fee/stop/sizing/hours/selector checks; scripts/dxtrade-smoke.mjs confirms the API against a real login before trading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.6 KiB
TypeScript
59 lines
2.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getCachedFirm, hasPlatformCredentials } from '@/lib/firm-catalog';
|
|
import { setCredentials, clearCredentials } from '@/lib/db';
|
|
import { reconcileClients, removeClient } from '@/lib/clients';
|
|
import { scheduleBackupPush } from '@/lib/backup-sync';
|
|
|
|
type Params = { params: Promise<{ id: string }> };
|
|
|
|
/** The platform login for one firm. This is the only firm data this machine
|
|
* owns — everything else comes from the master catalog. The one path these
|
|
* credentials travel is the encrypted, bearer-gated backup to the master
|
|
* (lib/backup-sync.ts); they are still absent from the catalog and the
|
|
* control socket.
|
|
*
|
|
* Tradovate and DXTrade firms need username + password (DXTrade's server URL
|
|
* and login domain are set on the master, per firm). TopstepX firms need the
|
|
* API key; username is optional for now (the loginKey call still wants it,
|
|
* so the client sits idle with a clear error until one is added) and
|
|
* password is unused. */
|
|
export async function PUT(req: NextRequest, { params }: Params) {
|
|
const { id } = await params;
|
|
|
|
const firm = getCachedFirm(id);
|
|
if (!firm) return NextResponse.json({ error: 'Firm not found' }, { status: 404 });
|
|
|
|
const body = await req.json() as { username?: string; password?: string; apiKey?: string };
|
|
const username = body.username?.trim() ?? '';
|
|
const password = body.password?.trim() ?? '';
|
|
const apiKey = body.apiKey?.trim() ?? '';
|
|
|
|
if (!hasPlatformCredentials(firm.platform, { username, password, apiKey })) {
|
|
const error = firm.platform === 'topstepx'
|
|
? 'apiKey is required for a TopstepX firm'
|
|
: firm.platform === 'dxtrade'
|
|
? 'username and password are required for a DXtrade firm'
|
|
: 'username and password are required';
|
|
return NextResponse.json({ error }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
setCredentials(id, firm.name, username, password, apiKey);
|
|
// Picks up the new login immediately rather than at the next restart.
|
|
reconcileClients();
|
|
scheduleBackupPush();
|
|
return NextResponse.json({ ok: true });
|
|
} catch (err) {
|
|
console.error('[PUT /api/firms/:id/credentials]', err);
|
|
return NextResponse.json({ error: 'Failed to save credentials' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function DELETE(_req: NextRequest, { params }: Params) {
|
|
const { id } = await params;
|
|
const cleared = clearCredentials(id);
|
|
removeClient(id);
|
|
scheduleBackupPush();
|
|
return NextResponse.json({ ok: cleared });
|
|
}
|