Files
autofirmer-expanded/app/api/firms/[id]/orders/route.ts
T
Brandon LiandClaude Opus 5.5 5eb0ea7760 Broker sessions rest while nothing needs them
A firm's client used to stay logged in around the clock, renewing on a fixed
timer overnight and at weekends. Now, once a firm has nothing open, nothing
pending and nothing left to trade before its next session for a random
15–45 minutes, its client disconnects and the dashboard shows it Resting.
It logs back in a random 20–50 minutes before the next session opens, at a
check-in every 3–7 Central daytime hours, or at once when trading starts, a
manual leg, copy trade, manual order or AutoBuyer run needs it. Token
renewals fall at random points (Tradovate 15–45 min before expiry, TopstepX
every 45–150 min). Settings → Trading → Rest idle broker sessions
(session_rest) turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:28:47 -05:00

60 lines
2.8 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { getCachedFirm } from '@/lib/firm-catalog';
import { firmInstruments, isFuturesFirm } from '@/lib/instruments';
import { getClients, isWaking, wakeFirm } from '@/lib/clients';
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id: firmId } = await params;
const body = await req.json() as {
accountId: number;
symbol: string; // product name, e.g. "NQ"
qty: number;
action: 'Buy' | 'Sell';
orderType?: 'Market' | 'Limit';
price?: number;
};
const { accountId, symbol, qty, action, orderType = 'Market', price } = body;
if (!accountId || !symbol || !qty || !action) {
return NextResponse.json({ error: 'Missing required fields: accountId, symbol, qty, action' }, { status: 400 });
}
const clients = getClients();
const client = clients.get(firmId);
if (!client) return NextResponse.json({ error: 'Client not found for firm' }, { status: 404 });
// A resting session (lib/clients.ts) holds no login: start one, and
// let the caller come back once it's up.
if (wakeFirm(firmId, 'manual order') || isWaking(firmId)) {
return NextResponse.json({ error: "the firm's session is logging back in — try again in a minute" }, { status: 503 });
}
// A futures firm trades only what its instrument list (master Firms
// page) names.
const firm = getCachedFirm(firmId);
if (firm && isFuturesFirm(firm) && !firmInstruments(firm).includes(symbol)) {
return NextResponse.json({ error: `${symbol} isn't on ${firm.name}'s instrument list` }, { status: 400 });
}
// 1. Find the active contract chosen by the shared resolver
const contract = await client.findFrontMonthContract(symbol);
if (!contract) {
return NextResponse.json({ error: `Could not find an active contract for ${symbol}` }, { status: 404 });
}
console.log(`[order] active contract: ${contract.name} (id=${contract.id})`);
// 2. Place the order — resolves with the fill event (includes commission/fees)
const fill = await client.sendOrder(accountId, contract.name, qty, action, orderType, price);
console.log(`[order] fill received: price=${fill.price} qty=${fill.qty} commission=$${fill.commission} perContract=$${fill.perContractFee}`);
return NextResponse.json({ contract: { id: contract.id, name: contract.name }, fill });
} catch (err: any) {
console.error('[POST /api/firms/[id]/orders]', err);
return NextResponse.json({ error: err?.message ?? 'Order failed' }, { status: 500 });
}
}