DXtrade platform for Tradeify247: percent fees, hard daily-loss stops, volatility sizing, every-day lanes

- DXtrade client (lib/dxtrade-class.ts): REST session login with lockout-safe
  backoff, 60s sync of accounts/metrics/positions/orders/transfers, history and
  fund transactions from cash transactions, day-start balance at the 22:00 UTC
  reset, CLOSE-by-positionCode exits (native OCO group, else a stop-first
  emulated pair), a cross-account hedge guard, stray-order reaper.
- Instrument selector: the firm's symbol map lists candidate symbols per CME
  root; the cheapest tradable one per unit of recent hourly volatility wins
  (spread + 2x commission [+ financing] / sigma).
- Fees: a firm's commission % of notional prices every fee off the fill; the
  take-profit is solved in closed form so the leg nets its target.
- Hard daily loss limit per account type: every leg carries a stop keeping the
  firm's day inside the limit less a 10% reserve and both fees (rounded in);
  a green day never widens it; no room or a refused bracket flattens.
- Notional sizing where the platform prices positions: target-hold volatility
  sizing or the symbol map's fixed notional, under a per-type notional cap.
- Every-day firms trade weekends/holidays; the scheduler runs lanes (futures,
  one per DXtrade firm) with their own hours, flat gate, mutex and batches.
- The catalog sync skips platforms this build doesn't know instead of treating
  them as Tradovate. One shared fireLeg replaces the duplicated batch/copy code.
- npm run check: fee/stop/sizing/hours/selector checks; scripts/dxtrade-smoke.mjs
  confirms the API against a real login before trading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-26 16:04:15 -05:00
co-authored by Claude Opus 5.5
parent 49c4725e0c
commit 28f465508d
18 changed files with 3962 additions and 534 deletions
+13 -11
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
import { getCachedFirm } from '@/lib/firm-catalog';
import { getCachedFirm, hasPlatformCredentials } from '@/lib/firm-catalog';
import { setCredentials, clearCredentials } from '@/lib/db';
import { reconcileClients, removeClient } from '@/lib/clients';
import { scheduleBackupPush } from '@/lib/backup-sync';
@@ -12,10 +12,11 @@ type Params = { params: Promise<{ id: string }> };
* (lib/backup-sync.ts); they are still absent from the catalog and the
* control socket.
*
* Tradovate firms need username + password. TopstepX firms need the API key;
* username is optional for now (the loginKey call still wants it, so the
* client sits idle with a clear error until one is added) and password is
* unused. */
* Tradovate and DXTrade firms need username + password (DXTrade's server URL
* and login domain are set on the master, per firm). TopstepX firms need the
* API key; username is optional for now (the loginKey call still wants it,
* so the client sits idle with a clear error until one is added) and
* password is unused. */
export async function PUT(req: NextRequest, { params }: Params) {
const { id } = await params;
@@ -27,12 +28,13 @@ export async function PUT(req: NextRequest, { params }: Params) {
const password = body.password?.trim() ?? '';
const apiKey = body.apiKey?.trim() ?? '';
if (firm.platform === 'topstepx') {
if (!apiKey) {
return NextResponse.json({ error: 'apiKey is required for a TopstepX firm' }, { status: 400 });
}
} else if (!username || !password) {
return NextResponse.json({ error: 'username and password are required' }, { status: 400 });
if (!hasPlatformCredentials(firm.platform, { username, password, apiKey })) {
const error = firm.platform === 'topstepx'
? 'apiKey is required for a TopstepX firm'
: firm.platform === 'dxtrade'
? 'username and password are required for a DXtrade firm'
: 'username and password are required';
return NextResponse.json({ error }, { status: 400 });
}
try {
+5 -4
View File
@@ -1,5 +1,5 @@
import { NextResponse } from 'next/server';
import { getCachedCatalog } from '@/lib/firm-catalog';
import { getCachedCatalog, hasPlatformCredentials } from '@/lib/firm-catalog';
import { getCredentials } from '@/lib/db';
import type { FirmConfig } from '@/types';
@@ -18,9 +18,7 @@ export async function GET() {
try {
const result: FirmConfig[] = getCachedCatalog().map((firm) => {
const creds = getCredentials(firm.id);
const hasCredentials = firm.platform === 'topstepx'
? !!creds?.apiKey
: !!(creds?.username && creds?.password);
const hasCredentials = hasPlatformCredentials(firm.platform, creds);
return {
id: firm.id,
firm: firm.name,
@@ -46,6 +44,9 @@ export async function GET() {
minDayStop: a.minDayStop,
combineCmeHolidays: a.combineCmeHolidays,
withdrawalStages: a.withdrawalStages,
dailyLossLimit: a.dailyLossLimit ?? 0,
maxNotional: a.maxNotional ?? 0,
tradesEveryDay: a.tradesEveryDay === true,
})),
catalogUpdatedAt: firm.updatedAt,
};