DXtrade: volatility measured once a day, next-leg hold on the dashboard, closed accounts dead

Each instrument's volatility is measured at the first good sync (or pick) of
a trade date and held for the day, so a day's sizing and hold estimate stay
put. The dashboard shows each DXtrade account's next-leg hold ("hold ~Nh")
with its symbol, notional, volatility and win odds. Accounts the firm has
closed (TERMINATED, EXPIRED, NO_TRADING) read as dead instead of inactive,
and skip the books check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-26 17:40:42 -05:00
co-authored by Claude Opus 5.5
parent 9fc1ed6908
commit 6876223fa4
4 changed files with 94 additions and 7 deletions
+40 -2
View File
@@ -3,10 +3,12 @@ import { loadDailyPnL } from '@/lib/db';
import { getClients } from '@/lib/clients';
import { getCachedCatalog } from '@/lib/firm-catalog';
import { getAccountConfig, getInferredSize, matchAccountType } from '@/lib/account-type-resolver';
import { historyFresh } from '@/lib/platform-client';
import { historyFresh, type PlatformContract } from '@/lib/platform-client';
import type { AccountState } from '@/types';
import {
combineCmeSessions, computeDayPlan, cycleCompletion, resolveEffectiveConfig, currentTradeDateCT,
obfuscationStopDollars, dllRoom as computeDllRoom, dllStopDollars, botStopsActive,
obfuscationStopDollars, dllRoom as computeDllRoom, dllStopDollars, botStopsActive, sizeNotional,
DEFAULT_TARGET_HOLD_HOURS,
LEG_COMPLETION_EPSILON,
} from '@/lib/trading-logic';
@@ -86,6 +88,7 @@ export async function GET() {
let effectiveProfitTarget: number | null = null;
let nextStop: number | null = null;
let dllRoom: number | null = null;
let holdEstimate: AccountState['holdEstimate'] = null;
if (cfg && effective && !isDead) {
const equityProfit = cash.amount - cfg.accountSize;
const plan = computeDayPlan(
@@ -121,6 +124,40 @@ export async function GET() {
if (Number.isFinite(stop) && stop > 0) nextStop = Math.round(stop * 100) / 100;
}
// DXtrade: size the next leg the way fireLeg would, on the
// instrument the selector last picked and today's measured
// volatility, to show how long it is expected to hold.
const pick = (client as { lastPickedContract?: PlatformContract | null }).lastPickedContract;
if (plan && plan.remainingToday > 0 && pick?.stepNotional && pick.volatilityPerHour) {
const pct = pick.commissionPct ?? f.commissionPct ?? 0;
const obf = obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday);
const room = dllRoom;
const sizing = sizeNotional({
leg: plan.remainingToday,
stopDollarsAt: (n) => Math.min(
Number.isFinite(obf) && obf > 0 ? obf : Infinity,
room !== null && dll > 0
? dllStopDollars({ room, dailyLossLimit: dll, entryFee: (pct / 100) * n, commissionPct: pct })
: Infinity,
),
volatilityPerHour: pick.volatilityPerHour,
targetHoldHours: f.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS,
commissionPct: pct,
spreadFraction: pick.spreadFraction,
financingPctPerDay: f.financingPctPerDay ?? 0,
maxNotional: cfg.maxNotional ?? 0,
});
if (sizing) {
holdEstimate = {
hours: Math.round(sizing.predictedHoldHours * 10) / 10,
symbol: String(pick.id),
notional: Math.round(sizing.notional),
volatilityPerDay: pick.volatilityPerHour * Math.sqrt(24),
winProbability: sizing.winProbability,
};
}
}
// Effective profit target = max(stage target, consistency realTarget).
// If a big day forces the consistency rule, the account must reach maxDay/consistency
// in total trading profit for the stage, not just profitTarget.
@@ -228,6 +265,7 @@ export async function GET() {
dailyTarget,
nextStop,
dllRoom,
holdEstimate,
remainingToday,
effectiveProfitTarget,
dailyPnL,
+9
View File
@@ -390,6 +390,15 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
{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>
)}
{account.holdEstimate && (
<span
className="text-slate-500"
title={`Next leg on ${account.holdEstimate.symbol}: $${Math.round(account.holdEstimate.notional).toLocaleString()} notional at today's measured volatility of ${(account.holdEstimate.volatilityPerDay * 100).toFixed(2)}%/day`
+ (account.holdEstimate.winProbability !== null ? `, wins ~${Math.round(account.holdEstimate.winProbability * 100)}% of the time` : '')}
>
{' · hold ~'}{account.holdEstimate.hours < 48 ? `${Math.round(account.holdEstimate.hours)}h` : `${(account.holdEstimate.hours / 24).toFixed(1)}d`}
</span>
)}
</span>
)}
</div>