Live futures spreads off one client, and the master's max entry spread

One quote feed per machine: a TopstepX client's market hub, else a
Tradovate client's market-data socket, subscribed to every listed root's
front month and the drain micro. A supervisor keeps it on a working client,
dropping one that fails or goes quiet while the market trades.

The Auto draw weighs each instrument by its live spread (the typical tick
when no fresh quote is at hand). No futures entry pays more than the
master's max entry spread ($15 until set) to cross the book: the draw
leaves such an instrument out, an explicit symbol sits the tick out, and
fireLeg and the extra-day trade check it last before sending. The Settings
page and the master's stats show the live spreads and the feed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-27 15:06:44 -05:00
co-authored by Claude Opus 5.5
parent 1782c16145
commit db1d12327a
13 changed files with 747 additions and 33 deletions
+8 -5
View File
@@ -1,8 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
import { getCachedCatalog } from '@/lib/firm-catalog';
import {
ensureMarket, firmInstruments, futuresRootsHere, instrumentStatsFor, instrumentsSource, isFuturesFirm, simulateDraws,
ensureMarket, firmInstruments, futuresRootsHere, instrumentStatsFor, instrumentsSource, isFuturesFirm, maxEntrySpread, simulateDraws,
} from '@/lib/instruments';
import { quoteStatus } from '@/lib/live-quotes';
import { CME_ROOTS } from '@/lib/instrument-specs';
export const dynamic = 'force-dynamic';
@@ -12,9 +13,11 @@ const EXAMPLE_LEG = { leg: 1000, stopDollars: Infinity, maxPositionSize: 0 };
/**
* GET /api/instruments — what each firm trades (the master's lists) and, for
* futures firms, how the Auto draw sees every instrument right now: a tick
* of spread, one contract's daily move, the hold of a $1,000 leg, the chance.
* ?simulate=N adds N draws from the real draw code — no orders.
* futures firms, how the Auto draw sees every instrument right now: its
* spread (live off the quote feed, else a tick), one contract's daily move,
* the hold of a $1,000 leg, the chance. `quotes` is the feed itself and
* `maxEntrySpreadDollars` the master's entry limit. ?simulate=N adds N draws
* from the real draw code — no orders.
*/
export async function GET(req: NextRequest) {
await ensureMarket(CME_ROOTS, 8_000);
@@ -32,5 +35,5 @@ export async function GET(req: NextRequest) {
...(futures && n > 0 ? { simulated: simulateDraws(instruments, EXAMPLE_LEG, n) } : {}),
};
});
return NextResponse.json({ roots: futuresRootsHere(), firms });
return NextResponse.json({ roots: futuresRootsHere(), maxEntrySpreadDollars: maxEntrySpread(), quotes: quoteStatus(), firms });
}
+21 -1
View File
@@ -7,6 +7,8 @@ import Link from 'next/link';
* futures firms, how the Auto draw sees each instrument. */
interface InstrumentsOverview {
roots: string[];
maxEntrySpreadDollars?: number;
quotes?: { source: string | null; platform: string | null; lastQuoteAt: string | null; lastError: string | null };
firms: {
id: string;
name: string;
@@ -15,11 +17,14 @@ interface InstrumentsOverview {
instruments: string[];
stats?: {
maxHoldHours: number;
quoteSource?: string | null;
sessionProgress: number | null;
instruments: {
symbol: string;
listed: boolean;
spreadDollars: number;
spreadLive?: boolean;
spreadBlocked?: boolean;
dailyMoveDollars: number | null;
holdHours: number | null;
disabled: boolean;
@@ -446,7 +451,17 @@ export default function SettingsPage() {
{masterUrl ? <a className="text-blue-500 hover:underline" href={`${masterUrl.replace(/\/+$/, '')}/firms`} target="_blank" rel="noreferrer">Firms page</a> : 'Firms page'}.
Futures &ldquo;Auto&rdquo; draws one per batch: cheaper per move early in the session (every instrument gets a floor),
the fastest near the last entry, never one expected to hold 12h or more. Holds below are for a $1,000 leg.
Spreads are live off one futures client&apos;s quotes (a tick when there are none); no entry pays more than
{overview?.maxEntrySpreadDollars ? ` $${overview.maxEntrySpreadDollars}` : ' the master\'s limit'} per contract to cross the spread.
</p>
{overview?.quotes && (
<p className="text-xs mb-3 text-slate-500">
{overview.quotes.source
? <>Live quotes from <span className="font-semibold">{overview.quotes.source}</span> ({overview.quotes.platform})
{overview.quotes.lastQuoteAt ? `, last ${new Date(overview.quotes.lastQuoteAt).toLocaleTimeString()}` : ', none yet'}</>
: <span className="text-amber-700">No live quotes — spreads are the typical tick{overview.quotes.lastError ? ` (${overview.quotes.lastError})` : ''}</span>}
</p>
)}
{!overview ? (
<div className="bg-white border border-slate-200 rounded-xl px-4 py-8 text-center text-sm text-slate-400 italic shadow-sm">Loading…</div>
@@ -486,8 +501,13 @@ export default function SettingsPage() {
{c?.name ?? '—'}
{c?.alternative && <span className="ml-2 text-xs bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded font-sans">rolled from {c.alternative}</span>}
</td>
<td className="px-4 py-2 text-right tabular-nums text-slate-600" title="One tick of spread on one contract — what entering costs, commissions aside">
<td
className={`px-4 py-2 text-right tabular-nums ${x?.spreadBlocked ? 'text-amber-600' : 'text-slate-600'}`}
title="Crossing the spread on one contract — what entering costs, commissions aside. Live off the quote feed, else the typical tick"
>
{x ? `$${x.spreadDollars.toFixed(2)}` : '—'}
{x && <span className="text-xs text-slate-400"> {x.spreadLive ? 'live' : 'typical'}</span>}
{x?.spreadBlocked && ' · over limit'}
</td>
<td className="px-4 py-2 text-right tabular-nums text-slate-600">
{x?.dailyMoveDollars != null ? `$${x.dailyMoveDollars.toLocaleString()}` : '—'}