Instruments: each firm's list from the master, and a session-aware futures draw
- lib/instrument-specs.ts (pure, tested): the CME specs — point value, tick size, one-tick spread, Yahoo ticker — replacing POINT_VALUES; the draw's maths; the 12-hour hold cap; the micro for drain and extra-day legs; reading a firm's list (the master's, or the old rules against an older master: this machine's enabled set less bans, the symbol map). - lib/instruments.ts: the one module the trading paths ask. Measures every future's daily move from Yahoo's hourly candles once per trade date, and shows entry cost (a tick of spread plus Tradovate's fee parameters or TopstepX's last measured fill) without ever choosing by it. - Futures Auto draws one root per batch from the batch firm's list: movement per dollar of spread early in the session with a 10% floor each, sliding to the fastest movers by the last entry; nothing expected to hold 12h or more; micros only if nothing else is listed. Every draw logs its odds. - The batch is chosen before its instrument, and a group that can't trade anything yields to the next. Copy trade matches the held contract against the listed roots and won't copy an instrument it can't identify. Manual legs, manual orders and drain/extra-day legs follow the lists. - DXtrade firms rank their own list whatever root was drawn. - Capabilities, the dashboard's trade bar and contract resolves follow the lists; the trade bar and scheduler default to Auto. The master link reconnects when master_dashboard_url changes, and the catalog drops ?since after a switch so two masters' versions can't collide. - Reports carry each futures firm's instrumentStats for the master's Firms page; GET /api/instruments shows them (?simulate=N runs the draw, no orders); the Settings page shows the lists read-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
302e9b4911
commit
02544fbdf5
@@ -1,4 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getCachedFirm } from '@/lib/firm-catalog';
|
||||
import { firmInstruments, isFuturesFirm } from '@/lib/instruments';
|
||||
import { getClients } from '@/lib/clients';
|
||||
|
||||
export async function POST(
|
||||
@@ -26,6 +28,13 @@ export async function POST(
|
||||
const client = clients.get(firmId);
|
||||
if (!client) return NextResponse.json({ error: 'Client not found for firm' }, { status: 404 });
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getCachedFirm } from '@/lib/firm-catalog';
|
||||
import { firmInstruments, instrumentsSource } from '@/lib/instruments';
|
||||
import { getCredentials } from '@/lib/db';
|
||||
|
||||
/** One firm from the cached catalog. Read-only: firms and their account types
|
||||
@@ -22,7 +23,8 @@ export async function GET(
|
||||
firm: firm.name,
|
||||
username: creds?.username ?? '',
|
||||
hasCredentials: !!creds,
|
||||
bannedSymbols: firm.bannedSymbols,
|
||||
instruments: firmInstruments(firm),
|
||||
instrumentsSource: instrumentsSource(firm),
|
||||
accounts: firm.accountTypes,
|
||||
catalogUpdatedAt: firm.updatedAt,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { firmInstruments, instrumentsSource } from '@/lib/instruments';
|
||||
import { getCachedCatalog, hasPlatformCredentials } from '@/lib/firm-catalog';
|
||||
import { getCredentials } from '@/lib/db';
|
||||
import type { FirmConfig } from '@/types';
|
||||
@@ -25,7 +26,8 @@ export async function GET() {
|
||||
platform: firm.platform,
|
||||
username: creds?.username ?? '',
|
||||
hasCredentials,
|
||||
bannedSymbols: firm.bannedSymbols,
|
||||
instruments: firmInstruments(firm),
|
||||
instrumentsSource: instrumentsSource(firm),
|
||||
accounts: firm.accountTypes.map((a) => ({
|
||||
id: a.id,
|
||||
label: a.label,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getClients } from '@/lib/clients';
|
||||
import { getInstruments } from '@/lib/db';
|
||||
import { futuresRootsHere } from '@/lib/instruments';
|
||||
import { resolveContracts, getAllCachedContracts } from '@/lib/contract-resolver';
|
||||
|
||||
/**
|
||||
@@ -15,7 +15,7 @@ export async function GET() {
|
||||
* POST /api/instruments/contracts
|
||||
* Triggers a fresh resolve via Tradovate + Yahoo Finance.
|
||||
* Body (optional): { symbols: string[] } — when provided, resolves only those symbols.
|
||||
* Otherwise resolves all enabled instruments.
|
||||
* Otherwise resolves every root this machine's futures firms list.
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
@@ -35,7 +35,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const symbols = requestedSymbols?.length
|
||||
? requestedSymbols
|
||||
: getInstruments().filter((i) => i.enabled).map((i) => i.symbol);
|
||||
: futuresRootsHere();
|
||||
|
||||
const resolved = await resolveContracts(symbols, accessToken);
|
||||
return NextResponse.json(resolved);
|
||||
|
||||
@@ -1,6 +1,38 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getInstruments } from '@/lib/db';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getClients } from '@/lib/clients';
|
||||
import { getCachedCatalog } from '@/lib/firm-catalog';
|
||||
import {
|
||||
ensureMarket, firmInstruments, futuresRootsHere, instrumentStatsFor, instrumentsSource, isFuturesFirm, simulateDraws,
|
||||
} from '@/lib/instruments';
|
||||
import { CME_ROOTS } from '@/lib/instrument-specs';
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json(getInstruments());
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/** The leg the overview sizes holds for: $1,000, stopless, one contract. */
|
||||
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: entry
|
||||
* cost, 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.
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
await ensureMarket(CME_ROOTS, 8_000);
|
||||
const clients = getClients();
|
||||
const n = Math.min(100_000, Math.max(0, Math.floor(Number(req.nextUrl.searchParams.get('simulate')) || 0)));
|
||||
const firms = getCachedCatalog().map((firm) => {
|
||||
const instruments = firmInstruments(firm);
|
||||
const futures = isFuturesFirm(firm);
|
||||
return {
|
||||
id: firm.id,
|
||||
name: firm.name,
|
||||
platform: firm.platform,
|
||||
source: instrumentsSource(firm),
|
||||
instruments,
|
||||
...(futures ? { stats: instrumentStatsFor(firm, clients.get(firm.id), { ...EXAMPLE_LEG, account: 'a $1,000 leg' }) } : {}),
|
||||
...(futures && n > 0 ? { simulated: simulateDraws(instruments, EXAMPLE_LEG, n) } : {}),
|
||||
};
|
||||
});
|
||||
return NextResponse.json({ roots: futuresRootsHere(), firms });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { runTrade, startScheduler } from '@/lib/auto-trade';
|
||||
import { errorMessage } from '@/lib/platform-client';
|
||||
import { POINT_VALUES } from '@/lib/trading-logic';
|
||||
import { CME_SPECS } from '@/lib/instrument-specs';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
@@ -12,7 +12,7 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'Missing required fields: action, symbol' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (symbol !== 'Auto' && !POINT_VALUES[symbol]) {
|
||||
if (symbol !== 'Auto' && !CME_SPECS[symbol]) {
|
||||
return NextResponse.json({ error: `Unknown symbol: ${symbol}` }, { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user