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>
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
/**
|
|
* Queuing an AutoBuyer run.
|
|
*
|
|
* Lifted out of POST /api/autobuyer/automations so the master dashboard's
|
|
* control link can queue the same run over its WebSocket without going back
|
|
* through HTTP — and, more to the point, without a second copy of the checks
|
|
* that decide how many times a purchase fires.
|
|
*
|
|
* Queuing is always allowed. Nothing arms the AutoBuyer any more, so the only
|
|
* thing standing between a queued run and a moving mouse is whether
|
|
* clicker/runner.py is up to claim it.
|
|
*/
|
|
|
|
import { createRun } from './db';
|
|
import { findAutomation, resolveSteps } from './automations';
|
|
import { wakeAllFirms } from './clients';
|
|
|
|
export class QueueRunError extends Error {
|
|
constructor(message: string, readonly status: number) {
|
|
super(message);
|
|
this.name = 'QueueRunError';
|
|
}
|
|
}
|
|
|
|
export interface QueuedRun {
|
|
runId: number;
|
|
totalSteps: number;
|
|
inputs: Record<string, number>;
|
|
}
|
|
|
|
export function queueRun(automationId: string, supplied: Record<string, unknown> = {}): QueuedRun {
|
|
const found = findAutomation(automationId);
|
|
if (!found) {
|
|
throw new QueueRunError(`No automation "${automationId}"`, 404);
|
|
}
|
|
if (found.automation.steps.length === 0) {
|
|
throw new QueueRunError(`"${found.automation.label}" has no steps yet`, 400);
|
|
}
|
|
// Clamp every input to what the automation declared. The count decides how
|
|
// many times a purchase runs, so it is not taken on trust from the client.
|
|
const inputs: Record<string, number> = {};
|
|
for (const declared of found.automation.inputs ?? []) {
|
|
const raw = Number(supplied[declared.id]);
|
|
const value = Number.isFinite(raw) ? Math.floor(raw) : declared.default;
|
|
inputs[declared.id] = Math.min(declared.max, Math.max(declared.min, value));
|
|
}
|
|
|
|
let resolved;
|
|
try {
|
|
resolved = resolveSteps(found.firm, found.automation, inputs);
|
|
} catch (err: any) {
|
|
throw new QueueRunError(err?.message ?? 'Could not expand the steps', 400);
|
|
}
|
|
|
|
const run = createRun(automationId, resolved.length, inputs);
|
|
// A purchase brings a new account: a resting session (lib/clients.ts)
|
|
// logs back in now so it is seen, and traded if it can be.
|
|
wakeAllFirms('AutoBuyer run');
|
|
return { runId: run.id, totalSteps: run.total_steps, inputs };
|
|
}
|