Report each symbol's next-leg stats to the master; drop per-symbol commission

- lib/leg-estimate: an account's next leg sized on every surveyed
  candidate (planLegSize, ranked by rankForTarget) — notional, its
  multiple of the account and the broker's maximum, expected hold, cost
  and win chance. The dashboard's hold estimate and the reporter share it.
- The reporter sends it per DXtrade firm as symbolStats, for one live
  account from the largest prefix group with a leg left to trade.
- The symbol map's per-symbol commission is gone: the firm's applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-26 18:41:01 -05:00
co-authored by Claude Opus 5.5
parent a2cd3a94c9
commit 169fe19fad
8 changed files with 236 additions and 51 deletions
+20 -39
View File
@@ -3,13 +3,13 @@ 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, type PlatformContract } from '@/lib/platform-client';
import { historyFresh } from '@/lib/platform-client';
import { rankNextLeg } from '@/lib/leg-estimate';
import type { AccountState } from '@/types';
import {
combineCmeSessions, computeDayPlan, cycleCompletion, resolveEffectiveConfig, currentTradeDateCT,
obfuscationStopDollars, dllRoom as computeDllRoom, dllStopDollars, botStopsActive, planLegSize, rankForTarget,
obfuscationStopDollars, dllRoom as computeDllRoom, dllStopDollars, botStopsActive,
maxLossRoom, lastStand as isLastStand,
DEFAULT_TARGET_HOLD_HOURS,
LEG_COMPLETION_EPSILON,
} from '@/lib/trading-logic';
@@ -133,44 +133,25 @@ 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 each
// instrument the client last surveyed — at its leverage and
// measured volatility — and take the one the batch would:
// the cheapest that reaches the target hold. The next leg
// opens flat, so while one is open (its margin tied up) it
// sizes against the balance.
const candidates = (client as { lastCandidates?: PlatformContract[] }).lastCandidates ?? [];
if (plan && plan.remainingToday > 0 && candidates.length > 0) {
const obf = obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday);
const target = f.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS;
const sized = candidates.flatMap((c) => {
const p = planLegSize({
leg: plan.remainingToday,
contract: c,
commissionPct: c.commissionPct ?? f.commissionPct ?? 0,
targetHoldHours: target,
financingPctPerDay: f.financingPctPerDay ?? 0,
dailyLossLimit: dll,
dllRoom: dllRoom ?? Infinity,
maxLossRoom: floorRoom,
obfuscationDollars: obf,
availableFunds: client.positions[acc.id] ? cash.amount : (cash.availableFunds ?? cash.amount),
});
return p.sizing && p.cost !== null && c.volatilityPerHour
? [{ c, sizing: p.sizing, holdHours: p.sizing.predictedHoldHours, cost: p.cost, volatility: c.volatilityPerHour }]
: [];
});
const best = sized[rankForTarget(sized, target)[0]];
// DXtrade: the next leg sized on every surveyed instrument as
// fireLeg would, and the one a batch would pick.
if (plan && plan.remainingToday > 0) {
const best = rankNextLeg({
client, accountId: acc.id, cfg, firm: f, leg: plan.remainingToday, dllRoom,
obfuscationDollars: obfuscationStopDollars(cfg, dailyPnL, plan, combined.tradeDate, plan.remainingToday),
cash,
})[0];
if (best) {
holdEstimate = {
hours: Math.round(best.holdHours * 10) / 10,
symbol: String(best.c.id),
notional: Math.round(best.sizing.notional),
volatilityPerDay: best.volatility * Math.sqrt(24),
winProbability: best.sizing.winProbability,
swapReserve: best.sizing.swapReserve,
leverage: best.c.marginRate ? Math.round(10 / best.c.marginRate) / 10 : null,
cost: Math.round(best.cost),
hours: best.holdHours,
symbol: best.symbol,
notional: best.notional,
multiplier: best.multiplier,
volatilityPerDay: best.volatilityPerDay,
winProbability: best.winProbability,
swapReserve: best.swapReserve,
leverage: best.leverage,
cost: best.cost,
};
}
}