Add auto-trade scheduler with batch locking, commission gross-up, and sync gate

- Auto-trade scheduler fires every 60s; uses Promise.allSettled batch so no new trades fire while any position from the current batch is open
- Commission gross-up: read entryCommission from cash.realizedPnL after fill (fallback 2.5×contracts), grossTarget = target + 2×entryCommission
- Sync gate: TradovateClient.syncComplete flag; scheduler skips tick until every client finishes initial position/balance sync
- Contracts formula changed to Math.ceil so $1500 target = 2 contracts
- Removed all fee caching (perContractFees, recentFills, fillFee handler) from tradovate-class.ts
- Removed firm_fees table, getFirmFees, upsertFirmFee from db.ts
- Deleted instrument-configs API routes; removed Fees UI from firm settings page
- /api/instruments returns full {symbol, enabled}[] objects; dashboard filters to enabled-only for trade selector
- Added auto-trade, debug, orders, settings, and trade API routes
- Instrument selector on dashboard now driven by enabled instruments from DB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Senofy
2026-03-09 03:31:47 -05:00
co-authored by Claude Sonnet 4.6
parent 532c2e2279
commit b2a1bdd1c3
19 changed files with 1050 additions and 218 deletions
-44
View File
@@ -5,13 +5,6 @@ import { useRouter, useParams } from 'next/navigation';
const SIZE_PRESETS = [5_000, 10_000, 25_000, 50_000, 75_000, 100_000, 150_000];
interface FirmFee {
firmId: number;
symbol: string;
allinFee: number;
roundtripFee: number;
}
interface AccountConfig {
id: number;
prefix: string;
@@ -89,7 +82,6 @@ export default function FirmSettingsPage() {
const [firmName, setFirmName] = useState('');
const [rows, setRows] = useState<RowState[]>([]);
const [fees, setFees] = useState<FirmFee[]>([]);
const [loadError, setLoadError] = useState('');
useEffect(() => {
@@ -103,11 +95,6 @@ export default function FirmSettingsPage() {
setRows(firm.accounts.map(initRow));
})
.catch(() => setLoadError('Failed to load firm'));
fetch(`/api/firms/${id}/instrument-configs`)
.then((r) => r.json() as Promise<FirmFee[]>)
.then(setFees)
.catch(() => {});
}, [id]);
const updateRow = (rowId: number, patch: Partial<RowState>) => {
@@ -428,37 +415,6 @@ export default function FirmSettingsPage() {
</table>
</div>
{/* Fees */}
<h2 className="text-sm font-semibold uppercase tracking-wider text-slate-500 mt-8 mb-3">
Fees
</h2>
<div className="bg-white border border-slate-200 rounded-xl overflow-hidden shadow-sm">
{fees.length === 0 ? (
<div className="px-4 py-8 text-center text-sm text-slate-400 italic">
No fees loaded yet
</div>
) : (
<table className="w-full text-sm border-collapse">
<thead>
<tr className="border-b border-slate-200 bg-slate-50">
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-slate-400">Symbol</th>
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-slate-400">All-In Fee</th>
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-slate-400">Roundtrip Fee</th>
</tr>
</thead>
<tbody>
{fees.map((fee) => (
<tr key={fee.symbol} className="border-b border-slate-100 last:border-0">
<td className="px-4 py-2.5 font-mono font-semibold text-slate-800">{fee.symbol}</td>
<td className="px-4 py-2.5 font-mono text-slate-600">${fee.allinFee.toFixed(4)}</td>
<td className="px-4 py-2.5 font-mono text-slate-600">${fee.roundtripFee.toFixed(4)}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
</div>
);