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:
co-authored by
Claude Opus 5.5
parent
49c4725e0c
commit
28f465508d
+26
-8
@@ -188,9 +188,19 @@ function AccountTypeCell({ account, firm, onAssigned }: {
|
||||
);
|
||||
}
|
||||
|
||||
/** What a firm's saved login is called in the dashboard's prompts. */
|
||||
function credentialNoun(platform: FirmConfig['platform']): string {
|
||||
switch (platform) {
|
||||
case 'topstepx': return 'TopstepX API key';
|
||||
case 'dxtrade': return 'DXtrade login';
|
||||
case 'tradovate': return 'Tradovate login';
|
||||
}
|
||||
}
|
||||
|
||||
/** Inline credential entry, shown to the right of a firm that has none.
|
||||
* Tradovate wants username + password; TopstepX wants the API key (username
|
||||
* optional for now, though login needs it eventually — password unused). */
|
||||
* Tradovate and DXtrade want username + password; TopstepX wants the API key
|
||||
* (username optional for now, though login needs it eventually — password
|
||||
* unused). */
|
||||
function CredentialForm({ firm, onSaved }: { firm: FirmConfig; onSaved: () => void }) {
|
||||
const isTopstepX = firm.platform === 'topstepx';
|
||||
const [username, setUsername] = useState('');
|
||||
@@ -231,7 +241,7 @@ function CredentialForm({ firm, onSaved }: { firm: FirmConfig; onSaved: () => vo
|
||||
<div className="flex items-center gap-1.5" onClick={(e) => e.stopPropagation()}>
|
||||
<input
|
||||
className={cls}
|
||||
placeholder={isTopstepX ? 'Username (optional)' : 'Tradovate username'}
|
||||
placeholder={isTopstepX ? 'Username (optional)' : firm.platform === 'dxtrade' ? 'DXtrade username' : 'Tradovate username'}
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
@@ -375,7 +385,10 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
|
||||
<span className="text-xs text-slate-400">
|
||||
${fmt(account.dailyTarget.amount)} today
|
||||
{account.nextStop !== null && (
|
||||
<span className="text-amber-600" title="The next trade's stop — dollars it may lose before booking the day"> · stop ${fmt(account.nextStop)}</span>
|
||||
<span className="text-amber-600" title="The next trade's stop — dollars of price movement it may lose before booking the day (fees come on top; the daily-loss stop already reserves for them)"> · stop ${fmt(account.nextStop)}</span>
|
||||
)}
|
||||
{typeof account.dllRoom === 'number' && (
|
||||
<span className="text-slate-500" title="Hard daily loss limit: what the firm's day may still lose before a breach. 10% of the limit is never spent."> · DLL room ${fmt(account.dllRoom)}</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
@@ -392,7 +405,7 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
|
||||
: account.positionDirection
|
||||
? <span className={`inline-block px-2.5 py-0.5 rounded-full text-xs font-semibold ${account.positionDirection === 'long' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>{account.positionDirection === 'long' ? 'Long' : 'Short'}</span>
|
||||
: account.personalDll
|
||||
? <span className="inline-block px-2.5 py-0.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-700" title="Booked the day red via its own stop — make-back rides next session">Personal DLL</span>
|
||||
? <span className="inline-block px-2.5 py-0.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-700" title="Booked the day red via its own stop (obfuscation or daily-loss) — trading resumes next session">Personal DLL</span>
|
||||
: account.targetHit
|
||||
? <span className="inline-block px-2.5 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-700">Target Hit</span>
|
||||
: <span className="inline-block px-2.5 py-0.5 rounded-full text-xs font-semibold bg-slate-100 text-slate-500">Flat</span>}
|
||||
@@ -478,6 +491,11 @@ function FirmRows({ state, firm, hideDead, privacy, sortKey, sortDir, tradeSymbo
|
||||
TopstepX
|
||||
</span>
|
||||
)}
|
||||
{firm.platform === 'dxtrade' && (
|
||||
<span className="text-xs bg-emerald-100 text-emerald-700 px-1.5 py-0.5 rounded font-semibold">
|
||||
DXtrade
|
||||
</span>
|
||||
)}
|
||||
{firm.platform === 'topstepx' && hasCredentials && !firm.username && (
|
||||
<span
|
||||
className="text-xs bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded font-semibold"
|
||||
@@ -506,7 +524,7 @@ function FirmRows({ state, firm, hideDead, privacy, sortKey, sortDir, tradeSymbo
|
||||
<td colSpan={5} className="px-4 py-2.5">
|
||||
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<span className="text-xs text-slate-500">
|
||||
Remove the saved {firm.platform === 'topstepx' ? 'TopstepX API key' : 'Tradovate login'} for {firm.firm}? Its accounts disappear until you enter one again.
|
||||
Remove the saved {credentialNoun(firm.platform)} for {firm.firm}? Its accounts disappear until you enter one again.
|
||||
</span>
|
||||
<button
|
||||
onClick={removeCredentials}
|
||||
@@ -532,7 +550,7 @@ function FirmRows({ state, firm, hideDead, privacy, sortKey, sortDir, tradeSymbo
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setConfirmCreds(true); }}
|
||||
className="text-slate-400 hover:text-red-500 hover:bg-red-50 p-1 rounded-md transition-colors inline-flex"
|
||||
title={firm.platform === 'topstepx' ? 'Remove saved TopstepX API key' : 'Remove saved Tradovate login'}
|
||||
title={`Remove saved ${credentialNoun(firm.platform)}`}
|
||||
>
|
||||
<KeyOffIcon />
|
||||
</button>
|
||||
@@ -554,7 +572,7 @@ function FirmRows({ state, firm, hideDead, privacy, sortKey, sortDir, tradeSymbo
|
||||
state.accounts.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={7} className="px-4 py-5 pl-12 text-sm text-slate-400 italic">
|
||||
{state.connected ? 'No accounts at this firm' : 'Connecting to Tradovate…'}
|
||||
{state.connected ? 'No accounts at this firm' : 'Connecting…'}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user