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:
co-authored by
Claude Opus 5.5
parent
28f465508d
commit
d01f03b293
+31
-38
@@ -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 ────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user