Auto-fetch contract on symbol enable; hide contract when disabled

- POST /api/instruments/contracts now accepts optional { symbols[] } body
  to resolve a subset rather than all enabled instruments
- Settings toggle() fires a targeted resolve when enabling a symbol,
  merging the result into contracts state without a full page refresh
- Active Contract cell is hidden (null) when the instrument is disabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Senofy
2026-03-12 03:34:51 -05:00
co-authored by Claude Sonnet 4.6
parent 6d3e8b6065
commit cade280b2d
2 changed files with 29 additions and 10 deletions
+11 -6
View File
@@ -1,4 +1,4 @@
import { NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getClients } from '@/lib/clients'; import { getClients } from '@/lib/clients';
import { getInstruments } from '@/lib/db'; import { getInstruments } from '@/lib/db';
import { resolveContracts, getAllCachedContracts } from '@/lib/contract-resolver'; import { resolveContracts, getAllCachedContracts } from '@/lib/contract-resolver';
@@ -13,10 +13,11 @@ export async function GET() {
/** /**
* POST /api/instruments/contracts * POST /api/instruments/contracts
* Triggers a fresh resolve of all enabled instruments via Tradovate + yfinance. * Triggers a fresh resolve via Tradovate + Yahoo Finance.
* Returns the resolved contracts with volume data. * Body (optional): { symbols: string[] } — when provided, resolves only those symbols.
* Otherwise resolves all enabled instruments.
*/ */
export async function POST() { export async function POST(req: NextRequest) {
try { try {
const clients = getClients(); const clients = getClients();
// Find first client with a valid token // Find first client with a valid token
@@ -29,8 +30,12 @@ export async function POST() {
return NextResponse.json({ error: 'No authenticated client available' }, { status: 503 }); return NextResponse.json({ error: 'No authenticated client available' }, { status: 503 });
} }
const instruments = getInstruments().filter((i) => i.enabled); const body = await req.json().catch(() => ({}));
const symbols = instruments.map((i) => i.symbol); const requestedSymbols: string[] | undefined = Array.isArray(body?.symbols) ? body.symbols : undefined;
const symbols = requestedSymbols?.length
? requestedSymbols
: getInstruments().filter((i) => i.enabled).map((i) => i.symbol);
const resolved = await resolveContracts(symbols, accessToken); const resolved = await resolveContracts(symbols, accessToken);
return NextResponse.json(resolved); return NextResponse.json(resolved);
+18 -4
View File
@@ -67,6 +67,22 @@ export default function SettingsPage() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled }), body: JSON.stringify({ enabled }),
}); });
// When enabling a symbol, auto-resolve its active contract
if (enabled) {
fetch('/api/instruments/contracts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ symbols: [symbol] }),
})
.then((r) => r.json())
.then((fresh: Record<string, ResolvedContract | null>) => {
if (!fresh.error) {
setContracts((prev) => ({ ...prev, ...fresh }));
}
})
.catch(() => {});
}
} }
async function saveSettings() { async function saveSettings() {
@@ -161,7 +177,7 @@ export default function SettingsPage() {
{instr.symbol} {instr.symbol}
</td> </td>
<td className="px-4 py-2.5"> <td className="px-4 py-2.5">
{c ? ( {instr.enabled && c ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className={`font-mono text-sm ${wasRolled ? 'text-amber-600 font-semibold' : 'text-slate-600'}`}> <span className={`font-mono text-sm ${wasRolled ? 'text-amber-600 font-semibold' : 'text-slate-600'}`}>
{c.name} {c.name}
@@ -177,9 +193,7 @@ export default function SettingsPage() {
</span> </span>
)} )}
</div> </div>
) : ( ) : null}
<span className="text-xs text-slate-300 italic"></span>
)}
</td> </td>
<td className="px-4 py-2.5 text-right"> <td className="px-4 py-2.5 text-right">
<button <button