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:
co-authored by
Claude Opus 5.5
parent
a2cd3a94c9
commit
169fe19fad
+20
-39
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -396,8 +396,8 @@ function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
|
||||
{account.holdEstimate && (
|
||||
<span
|
||||
className="text-slate-500"
|
||||
title={`Next leg on ${account.holdEstimate.symbol}: $${Math.round(account.holdEstimate.notional).toLocaleString()} notional`
|
||||
+ (account.holdEstimate.leverage !== null ? ` (${account.holdEstimate.leverage}:1 max)` : '')
|
||||
title={`Next leg on ${account.holdEstimate.symbol}: $${Math.round(account.holdEstimate.notional).toLocaleString()} notional, ${account.holdEstimate.multiplier}× the account`
|
||||
+ (account.holdEstimate.leverage !== null ? ` (${account.holdEstimate.leverage}× max)` : '')
|
||||
+ ` at a measured volatility of ${(account.holdEstimate.volatilityPerDay * 100).toFixed(2)}%/day over the past week`
|
||||
+ (account.holdEstimate.winProbability !== null ? `, wins ~${Math.round(account.holdEstimate.winProbability * 100)}% of the time` : '')
|
||||
+ (account.holdEstimate.swapReserve > 0 ? `; its take-profit carries ~$${fmt(account.holdEstimate.swapReserve)} of expected swap` : '')
|
||||
|
||||
Reference in New Issue
Block a user