DXtrade sizing: volatility only (12h default hold); fees and every-day only for DXtrade

The symbol map's fixed notional per contract is gone: every DXtrade leg is
sized from the instrument's recent volatility for the firm's target hold
(blank = 12 h), and a candidate without a volatility isn't picked. Percent
commission and every-day trading are ignored on non-DXtrade firms, which keep
measured per-contract fees and the CME calendar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-26 17:29:32 -05:00
co-authored by Claude Opus 5.5
parent 28f465508d
commit d01f03b293
9 changed files with 87 additions and 100 deletions
+7 -8
View File
@@ -14,6 +14,7 @@ import { getAccountConfig } from './account-type-resolver';
import {
combineCmeSessions, computeDayPlan, groupLegFraction, pickLegAmount, resolveEffectiveConfig, currentTradeDateCT,
obfuscationStopDollars, takeProfitPlan, percentFee, dllRoom, dllStopDollars, dllViable, stopTicks, sizeNotional,
DEFAULT_TARGET_HOLD_HOURS,
isNoTradeTime, isPastSessionClose,
BIG_DAY_THRESHOLD, LEG_COMPLETION_EPSILON, MAX_LEGS_PER_DAY, POINT_VALUES,
FALLBACK_COMMISSION_PER_CONTRACT, MAX_COMMISSION_PER_CONTRACT, DLL_MIN_STOP,
@@ -947,9 +948,9 @@ async function flattenAndFail(
* the batch and copy-trade, so every fee, stop and sizing rule lives once.
*
* Size: futures trade one contract per $1,000 of leg; a platform that prices
* positions (stepNotional) sizes by notional instead — volatility sizing for
* the firm's target hold, or the symbol map's fixed notional — under the
* account type's notional cap.
* positions (stepNotional) sizes by notional instead — whatever makes the
* leg's expected hold the firm's target, from the instrument's recent
* volatility — under the account type's notional cap.
*
* Fees: a percent-of-notional firm's fee is computed off the fill; otherwise
* it is read off the realized-P&L change a second after it.
@@ -1016,15 +1017,14 @@ async function fireLeg(
leg: legAmount,
stopDollarsAt,
volatilityPerHour: legContract.volatilityPerHour,
targetHoldHours: firm.targetHoldHours ?? 0,
targetHoldHours: firm.targetHoldHours || DEFAULT_TARGET_HOLD_HOURS,
commissionPct: pct,
spreadFraction: legContract.spreadFraction,
financingPctPerDay: firm.financingPctPerDay ?? 0,
fixedNotional: legContract.fixedNotional ?? 0,
maxNotional: cfg.maxNotional ?? 0,
});
if (!sizing) {
throw new Error(`no position size for ${legContract.name} — set a notional per contract in the symbol map, or a target hold on the firm`);
throw new Error(`no position size for ${legContract.name} — its recent volatility is unknown`);
}
contracts = Math.max(1, Math.round(sizing.notional / stepNotional));
const maxNotional = cfg.maxNotional ?? 0;
@@ -1118,8 +1118,7 @@ async function fireLeg(
const instrumentLabel = legContract.stepNotional || item.drain ? legContract.name : symbolLabel;
const sizeNote = sizing
? ` | notional $${Math.round(contracts * (legContract.stepNotional ?? 0))} (${sizing.mode}`
+ `${sizing.predictedHoldHours !== null ? `, hold ~${sizing.predictedHoldHours.toFixed(1)}h` : ''}`
? ` | notional $${Math.round(contracts * (legContract.stepNotional ?? 0))} (hold ~${sizing.predictedHoldHours.toFixed(1)}h`
+ `${sizing.winProbability !== null ? `, win ${Math.round(sizing.winProbability * 100)}%` : ''})`
: '';
const stopNote = stopPrice !== null