'use client'; import { useEffect, useState } from 'react'; import Link from 'next/link'; interface Instrument { symbol: string; enabled: boolean; } interface ResolvedContract { name: string; alternative?: string; } interface AppSettings { max_concurrent_accounts: string | null; master_dashboard_url: string | null; instance_name: string | null; trading_hours: string | null; } export default function SettingsPage() { const [instruments, setInstruments] = useState([]); const [contracts, setContracts] = useState>({}); const [maxConcurrent, setMaxConcurrent] = useState('5'); const [tradingHours, setTradingHours] = useState('full_cme'); const [masterUrl, setMasterUrl] = useState(''); const [instanceName, setInstanceName] = useState(''); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); useEffect(() => { fetch('/api/instruments') .then((r) => r.json()) .then(setInstruments); fetch('/api/settings') .then((r) => r.json()) .then((s: AppSettings) => { if (s.max_concurrent_accounts != null) { setMaxConcurrent(s.max_concurrent_accounts); } if (s.trading_hours != null) { setTradingHours(s.trading_hours); } if (s.master_dashboard_url != null) { setMasterUrl(s.master_dashboard_url); } if (s.instance_name != null) { setInstanceName(s.instance_name); } }); // Load cached contracts; if cache is empty, auto-resolve fetch('/api/instruments/contracts') .then((r) => r.json()) .then((data: Record) => { if (data.error) return; const hasData = Object.values(data).some((v) => v !== null); if (hasData) { setContracts(data); } else { // Cache empty — trigger a fresh resolve automatically fetch('/api/instruments/contracts', { method: 'POST' }) .then((r) => r.json()) .then((fresh) => { if (!fresh.error) setContracts(fresh); }) .catch(() => {}); } }) .catch(() => {}); }, []); async function toggle(symbol: string, enabled: boolean) { setInstruments((prev) => prev.map((i) => (i.symbol === symbol ? { ...i, enabled } : i)) ); await fetch(`/api/instruments/${symbol}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, 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) => { if (!fresh.error) { setContracts((prev) => ({ ...prev, ...fresh })); } }) .catch(() => {}); } } async function saveSettings() { setSaving(true); setSaved(false); await fetch('/api/settings', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ max_concurrent_accounts: maxConcurrent, trading_hours: tradingHours, master_dashboard_url: masterUrl, instance_name: instanceName }), }); setSaving(false); setSaved(true); setTimeout(() => setSaved(false), 2000); } return (
← Back

Settings

{/* ── Trading ── */}

Trading

Max concurrent accounts

How many accounts trade in parallel per /api/trade call

setMaxConcurrent(e.target.value)} className="w-20 rounded-lg border border-slate-200 bg-slate-50 px-3 py-1.5 text-sm text-right font-mono text-slate-800 focus:outline-none focus:ring-2 focus:ring-blue-500" />

Allowed trading hours

5 min buffer

{/* ── Master Dashboard ── */}

Master Dashboard

Instance Name

Identifier for this autotrader on the master dashboard

setInstanceName(e.target.value)} className="w-48 rounded-lg border border-slate-200 bg-slate-50 px-3 py-1.5 text-sm font-mono text-slate-800 focus:outline-none focus:ring-2 focus:ring-blue-500" />

Dashboard URL

Reports state to this URL every 30 seconds

setMasterUrl(e.target.value)} className="w-72 rounded-lg border border-slate-200 bg-slate-50 px-3 py-1.5 text-sm font-mono text-slate-800 focus:outline-none focus:ring-2 focus:ring-blue-500" />
{/* ── Instruments ── */}

Instruments

{instruments.length === 0 ? (
Loading…
) : ( {instruments.map((instr) => { const c = contracts[instr.symbol]; const wasRolled = !!c?.alternative; return ( ); })}
Symbol Active Contract Enabled
{instr.symbol} {instr.enabled && c ? (
{c.name} {wasRolled && ( rolled from {c.alternative} )}
) : null}
)}
); }