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
+31 -38
View File
@@ -1266,6 +1266,9 @@ export function botStopsActive(cfg: { firstDayStop: boolean; minDayStop: boolean
// ── position size ────────────────────────────────────────────────────────────
/** The hold a leg is sized for when the firm doesn't set one. */
export const DEFAULT_TARGET_HOLD_HOURS = 12;
/**
* How much notional a leg should carry, on platforms that price it (DXTrade).
*
@@ -1278,63 +1281,53 @@ export function botStopsActive(cfg: { firstDayStop: boolean; minDayStop: boolean
* financing adds a floor: below √(f·A·B/(σ²·(2c + s))), a bigger position is
* both cheaper and faster.
*
* Fixed mode (no target hold, no σ, or a stopless leg): the symbol map's
* notional per $1,000 of leg, like futures' one contract per $1,000.
* Returns null when neither mode can size the leg.
* A stopless leg (no daily loss limit, no obfuscation stop) is sized as if
* its stop sat as far away as its target. Returns null without a volatility.
*/
export function sizeNotional(o: {
leg: number;
/** Stop distance in dollars at a given notional (fees move with it). */
stopDollarsAt: (notional: number) => number;
volatilityPerHour?: number;
/** 0 = DEFAULT_TARGET_HOLD_HOURS. */
targetHoldHours: number;
/** Per side, percent. */
commissionPct: number;
spreadFraction?: number;
financingPctPerDay?: number;
/** Notional per $1,000 of leg for fixed sizing; 0 = none. */
fixedNotional: number;
/** Hard cap on the position; 0 = none. */
maxNotional: number;
}): { notional: number; mode: 'volatility' | 'fixed'; predictedHoldHours: number | null; winProbability: number | null } | null {
}): { notional: number; predictedHoldHours: number; winProbability: number | null } | null {
const sigma = o.volatilityPerHour ?? 0;
if (!(sigma > 0)) return null;
const hold = o.targetHoldHours > 0 ? o.targetHoldHours : DEFAULT_TARGET_HOLD_HOURS;
const c = Math.max(0, o.commissionPct) / 100;
const s = Math.max(0, o.spreadFraction ?? 0);
const sigma = o.volatilityPerHour ?? 0;
const fixed = o.fixedNotional > 0 ? Math.max(1, Math.ceil(o.leg / 1000)) * o.fixedNotional : 0;
const cap = (n: number) => (o.maxNotional > 0 ? Math.min(n, o.maxNotional) : n);
const describe = (n: number) => {
const fh = Math.max(0, o.financingPctPerDay ?? 0) / 100 / 24;
const distances = (n: number) => {
const a = o.leg + 2 * c * n;
const b = o.stopDollarsAt(n);
if (!(sigma > 0) || !Number.isFinite(b) || b <= 0 || n <= 0) {
return { predictedHoldHours: null, winProbability: Number.isFinite(b) && b > 0 ? b / (a + b) : null };
}
return { predictedHoldHours: (a / n) * (b / n) / (sigma * sigma), winProbability: b / (a + b) };
const stop = o.stopDollarsAt(n);
const stopped = Number.isFinite(stop) && stop > 0;
return { a, b: stopped ? stop : a, stopped };
};
if (o.targetHoldHours > 0 && sigma > 0) {
let n = fixed > 0 ? fixed : o.leg * 20;
let ok = true;
// Fees depend on N and the daily-loss stop depends on fees: a couple of
// fixed-point passes settle it (the correction is second order).
for (let pass = 0; pass < 3; pass++) {
const a = o.leg + 2 * c * n;
const b = o.stopDollarsAt(n);
if (!Number.isFinite(b) || b <= 0) { ok = false; break; }
const nTime = Math.sqrt((a * b) / (sigma * sigma * o.targetHoldHours));
const fh = Math.max(0, o.financingPctPerDay ?? 0) / 100 / 24;
const nCost = fh > 0 ? Math.sqrt((fh * a * b) / (sigma * sigma * (2 * c + s))) : 0;
n = Math.max(nTime, nCost);
}
if (ok && Number.isFinite(n) && n > 0) {
const notional = cap(n);
return { notional, mode: 'volatility', ...describe(notional) };
}
// Fees depend on N and the daily-loss stop depends on fees: a couple of
// fixed-point passes settle it (the correction is second order).
let n = o.leg * 20;
for (let pass = 0; pass < 3; pass++) {
const { a, b } = distances(n);
const nTime = Math.sqrt((a * b) / (sigma * sigma * hold));
const nCost = fh > 0 ? Math.sqrt((fh * a * b) / (sigma * sigma * (2 * c + s))) : 0;
n = Math.max(nTime, nCost);
}
if (fixed > 0) {
const notional = cap(fixed);
return { notional, mode: 'fixed', ...describe(notional) };
}
return null;
if (!Number.isFinite(n) || n <= 0) return null;
const notional = o.maxNotional > 0 ? Math.min(n, o.maxNotional) : n;
const { a, b, stopped } = distances(notional);
return {
notional,
predictedHoldHours: (a / notional) * (b / notional) / (sigma * sigma),
winProbability: stopped ? b / (a + b) : null,
};
}
// ── trading hours ────────────────────────────────────────────────────────────